/* FichothequeLib_Xml - Copyright (c) 2009-2025 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.fichotheque.xml.extraction;

import java.io.IOException;
import java.text.ParseException;
import net.fichotheque.Fichotheque;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
import net.fichotheque.addenda.Addenda;
import net.fichotheque.addenda.Document;
import net.fichotheque.addenda.Version;
import net.fichotheque.album.Album;
import net.fichotheque.album.Illustration;
import net.fichotheque.album.metadata.AlbumDim;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.fiche.Atts;
import net.fichotheque.corpus.fiche.Cdatadiv;
import net.fichotheque.corpus.fiche.Code;
import net.fichotheque.corpus.fiche.Div;
import net.fichotheque.corpus.fiche.FicheAPI;
import net.fichotheque.corpus.fiche.FicheBlock;
import net.fichotheque.corpus.fiche.FicheBlocks;
import net.fichotheque.corpus.fiche.FicheItem;
import net.fichotheque.corpus.fiche.H;
import net.fichotheque.corpus.fiche.Insert;
import net.fichotheque.corpus.fiche.Item;
import net.fichotheque.corpus.fiche.Li;
import net.fichotheque.corpus.fiche.Ln;
import net.fichotheque.corpus.fiche.P;
import net.fichotheque.corpus.fiche.ParaItem;
import net.fichotheque.corpus.fiche.S;
import net.fichotheque.corpus.fiche.Table;
import net.fichotheque.corpus.fiche.Td;
import net.fichotheque.corpus.fiche.TextContent;
import net.fichotheque.corpus.fiche.Tr;
import net.fichotheque.corpus.fiche.Ul;
import net.fichotheque.corpus.fiche.ZoneBlock;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.extraction.ExtractParameters;
import net.fichotheque.extraction.ExtractionContext;
import net.fichotheque.extraction.IrefConverter;
import net.fichotheque.extraction.def.MotcleFilter;
import net.fichotheque.namespaces.BdfSpace;
import net.fichotheque.namespaces.ExtractionSpace;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.utils.ExtractionUtils;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.ThesaurusUtils;
import net.mapeadores.util.attr.Attribute;
import net.mapeadores.util.attr.Attributes;
import net.mapeadores.util.awt.ResizeInfo;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.text.StringUtils;
import net.mapeadores.util.xml.XMLPart;
import net.mapeadores.util.xml.XMLUtils;
import net.mapeadores.util.xml.XMLWriter;


/**
 *
 * @author Vincent Calame
 */
public class FicheBlockXMLPart extends XMLPart {

    private final ExtractParameters extractParameters;
    private final MotcleXMLPart motcleXMLPart;
    private final ExtractionContext extractionContext;
    private final int extractVersion;
    private final String imageBaseUrl;
    private final String audioBaseUrl;
    private Corpus defaultCorpus;
    private IrefConverter currentIrefConverter = ExtractionUtils.SAME_CONVERTER;

    public FicheBlockXMLPart(XMLWriter xmlWriter, ExtractParameters extractParameters) {
        super(xmlWriter);
        this.extractParameters = extractParameters;
        this.extractionContext = extractParameters.getExtractionContext();
        this.extractVersion = extractParameters.getExtractVersion();
        Attributes fichothequeMetadataAttributes = extractionContext.getFichotheque().getFichothequeMetadata().getAttributes();
        this.imageBaseUrl = fichothequeMetadataAttributes.getFirstValue(BdfSpace.IMAGEBASEURL_KEY);
        this.audioBaseUrl = fichothequeMetadataAttributes.getFirstValue(BdfSpace.AUDIOBASEURL_KEY);
        this.motcleXMLPart = new MotcleXMLPart(xmlWriter, extractParameters);
    }

    public void setDefaultCorpus(Corpus defaultCorpus) {
        this.defaultCorpus = defaultCorpus;
    }

    public void addFicheBlocks(FicheBlocks ficheBlocks, IrefConverter irefConverter) throws IOException {
        this.currentIrefConverter = irefConverter;
        addFicheBlocks(ficheBlocks, "");
    }

    private void addFicheBlocks(FicheBlocks ficheBlocks, String positionPrefix) throws IOException {
        int size = ficheBlocks.size();
        for (int i = 0; i < size; i++) {
            addFicheBlock(ficheBlocks.get(i), positionPrefix, i + 1);
        }
    }

    public void addTextContent(TextContent content, IrefConverter irefConverter) throws IOException {
        for (Object obj : content) {
            if (obj instanceof String) {
                addText((String) obj);
            } else if (obj instanceof S) {
                addS((S) obj, irefConverter);
            }
        }
    }

    private void addFicheBlock(FicheBlock ficheBlock, String positionPrefix, int position) throws IOException {
        if (ficheBlock instanceof P) {
            addP((P) ficheBlock, positionPrefix, position);
        } else if (ficheBlock instanceof H) {
            addH((H) ficheBlock, positionPrefix, position);
        } else if (ficheBlock instanceof Ul) {
            addUl((Ul) ficheBlock, positionPrefix, position);
        } else if (ficheBlock instanceof Code) {
            addCode((Code) ficheBlock, positionPrefix, position);
        } else if (ficheBlock instanceof Table) {
            addTable((Table) ficheBlock, positionPrefix, position);
        } else if (ficheBlock instanceof Insert) {
            addInsert((Insert) ficheBlock, positionPrefix, position);
        } else if (ficheBlock instanceof Div) {
            addDiv((Div) ficheBlock, positionPrefix, position);
        } else if (ficheBlock instanceof Cdatadiv) {
            addCdataDiv((Cdatadiv) ficheBlock, positionPrefix, position);
        } else {
            throw new IllegalArgumentException("unknown FicheBlock Implementation");
        }
    }

    private void addP(P p, String positionPrefix, int position) throws IOException {
        startOpenTag("p");
        addAttribute("type", P.typeToString(p.getType()));
        addAttribute("source", p.getSource());
        addAtts(p);
        addPositionAttribute(positionPrefix, position);
        endOpenTag();
        addTextContent(p, currentIrefConverter);
        closeTag("p", false);
    }

    private void addH(H h, String positionPrefix, int position) throws IOException {
        startOpenTag("h");
        addAttribute("level", String.valueOf(h.getLevel()));
        addAtts(h);
        addPositionAttribute(positionPrefix, position);
        endOpenTag();
        addTextContent(h, currentIrefConverter);
        closeTag("h", false);
    }

    private void addPositionAttribute(String positionPrefix, int position) throws IOException {
        if ((extractParameters.isWithPosition()) && (positionPrefix != null)) {
            addAttribute("att-data-bdf-position", positionPrefix + position);
        }
    }

    private void addUl(Ul ul, String positionPrefix, int position) throws IOException {
        String childPositionPrefix = appendPositionPrefix(positionPrefix, position);
        startOpenTag("ul");
        addAtts(ul);
        endOpenTag();
        int childPosition = 0;
        for (Li li : ul) {
            childPosition++;
            addLi(li, childPositionPrefix, childPosition);
        }
        closeTag("ul");
    }

    private void addLi(Li li, String positionPrefix, int position) throws IOException {
        String childPositionPrefix = appendPositionPrefix(positionPrefix, position);
        startOpenTag("li");
        addAtts(li.getAtts());
        endOpenTag();
        addFicheBlocks(li, childPositionPrefix);
        closeTag("li");
    }

    private void addCode(Code code, String positionPrefix, int position) throws IOException {
        startOpenTag("code");
        addAttribute("type", Code.typeToString(code.getType()));
        addAtts(code);
        addPositionAttribute(positionPrefix, position);
        endOpenTag();
        addZoneBlockElements(code);
        for (Ln ln : code) {
            addLn(ln);
        }
        closeTag("code");
    }

    private void addLn(Ln ln) throws IOException {
        int indentation = ln.getIndentation();
        startOpenTag("ln");
        if (indentation > 0) {
            addAttribute("indent", String.valueOf(indentation));
        }
        addAtts(ln.getAtts());
        endOpenTag();
        addText(ln.getValue());
        closeTag("ln", false);
    }

    private void addDiv(Div div, String positionPrefix, int position) throws IOException {
        String childPositionPrefix = appendPositionPrefix(positionPrefix, position);
        startOpenTag("div");
        XMLUtils.addXmlLangAttribute(this, div.getLang());
        addAtts(div);
        endOpenTag();
        addZoneBlockElements(div);
        openTag("fbl");
        addFicheBlocks(div, childPositionPrefix);
        closeTag("fbl");
        closeTag("div");
    }

    private void addCdataDiv(Cdatadiv cdatadiv, String positionPrefix, int position) throws IOException {
        startOpenTag("cdatadiv");
        addAtts(cdatadiv);
        addPositionAttribute(positionPrefix, position);
        endOpenTag();
        addZoneBlockElements(cdatadiv);
        startOpenTag("cdata");
        endOpenTag();
        addCData(cdatadiv.getCdata());
        closeTag("cdata", false);
        closeTag("cdatadiv");
    }

    private void addS(S s, IrefConverter irefConverter) throws IOException {
        short sType = s.getType();
        startOpenTag("s", false);
        addAttribute("type", S.typeToString(sType));
        String ref = s.getRef();
        addAttribute("ref", ref);
        if ((sType == S.IMAGE) && (ref.length() > 0) && (imageBaseUrl != null)) {
            if (!StringUtils.isAbsoluteUrlString(ref)) {
                addAttribute("base", imageBaseUrl);
            }
        }
        if (S.isIref(sType)) {
            addAttribute("iref", irefConverter.convert(ref));
        }
        boolean ignoreVariant = false;
        if ((sType == S.ANCHOR) && (s.getValue().isEmpty())) {
            addAttribute("variant", "empty");
            ignoreVariant = true;
        }
        addAtts(s, ignoreVariant);
        if (!S.isSpecialRef(sType)) {
            if (extractionContext.getLinkAnalyser().isExternalLink(ref)) {
                addAttribute("link", "external");
            }
        }
        String syntax = s.getSyntax();
        if (!syntax.isEmpty()) {
            String cdata = extractionContext.getSyntaxResolver().toCData(syntax, s.getValue());
            if (!cdata.isEmpty()) {
                addAttribute("cdata", cdata);
            }
        }
        switch (sType) {
            case S.FICHE:
                closeFicheS(s);
                break;
            case S.MOTCLE:
                closeMotcleS(s);
                break;
            default: {
                closeDefaultS(s);
            }
        }
    }

    private void closeMotcleS(S s) throws IOException {
        String ref = checkFragment(s);
        Fichotheque fichotheque = extractionContext.getFichotheque();
        Motcle motcle = null;
        try {

            motcle = ThesaurusUtils.parseGlobalIdalpha(ref, fichotheque);
        } catch (ParseException pe) {
            try {
                motcle = (Motcle) FichothequeUtils.parseGlobalId(ref, fichotheque, SubsetKey.CATEGORY_THESAURUS, null);
            } catch (ParseException pe2) {
                motcle = null;
            }
        }
        if (motcle == null) {
            addAttribute("ref-error", ref);
            closeDefaultS(s);
            return;
        }
        endOpenTag();
        MotcleFilter motcleFilter;
        String value = s.getValue();
        if (value.length() > 0) {
            addSimpleElement("value", value);
            motcleFilter = ExtractionXMLUtils.NONE_MOTCLEFILTER;
        } else {
            motcleFilter = ExtractionXMLUtils.LABELS_MOTCLEFILTER;
        }
        motcleXMLPart.addMotcle(ExtractionXMLUtils.toMotcleExtractInfo(motcle, motcleFilter));
        closeTag("s", false);
    }

    private void closeFicheS(S s) throws IOException {
        String ref = checkFragment(s);
        FicheMeta other;
        try {
            other = (FicheMeta) FichothequeUtils.parseGlobalId(ref, extractionContext.getFichotheque(), SubsetKey.CATEGORY_CORPUS, defaultCorpus);
        } catch (ParseException pe) {
            other = null;
        }
        if (other == null) {
            addAttribute("ref-error", ref);
            closeDefaultS(s);
            return;
        }
        Lang otherLang = other.getLang();
        if (otherLang != null) {
            addAttribute("lang", otherLang.toString());
        }
        addAttribute("corpus", other.getSubsetName());
        addAttribute("id", other.getId());
        Subset master = other.getCorpus().getMasterSubset();
        if ((master != null) && (master instanceof Thesaurus)) {
            String idalpha = ((Motcle) master.getSubsetItemById(other.getId())).getIdalpha();
            if (idalpha != null) {
                addAttribute("idalpha", idalpha);
            }
        }
        endOpenTag();
        String value = s.getValue();
        if (value.length() > 0) {
            addText(value);
        } else {
            addText(getLinkText(other));
        }
        closeTag("s", false);
    }


    private void closeDefaultS(S s) throws IOException {
        String value = s.getValue();
        if (value.length() > 0) {
            endOpenTag();
            addText(value);
            closeTag("s", false);
        } else {
            closeEmptyTag();
        }
    }

    private String checkFragment(S s) throws IOException {
        String ref = s.getRef();
        int idx = ref.indexOf('#');
        if (idx > 0) {
            addAttribute("fragment", ref.substring(idx + 1));
            ref = ref.substring(0, idx);
        }
        return ref;
    }


    private void addTable(Table table, String positionPrefix, int position) throws IOException {
        String childPositionPrefix = appendPositionPrefix(positionPrefix, position);
        startOpenTag("table");
        addAtts(table);
        addAttribute("cols", Table.checkColCount(table));
        endOpenTag();
        addZoneBlockElements(table);
        int childPosition = 0;
        for (Tr tr : table) {
            childPosition++;
            addTr(tr, childPositionPrefix, childPosition);
        }
        closeTag("table");
    }

    private void addTr(Tr tr, String positionPrefix, int position) throws IOException {
        startOpenTag("tr");
        addAtts(tr.getAtts());
        if (!tr.isEmpty()) {
            addPositionAttribute(positionPrefix, position);
        }
        endOpenTag();
        for (Td td : tr) {
            addTd(td);
        }
        closeTag("tr");
    }

    private void addTd(Td td) throws IOException {
        startOpenTag("td");
        addAttribute("type", Td.typeToString(td.getType()));
        if (td.isNumber()) {
            addAttribute("format", "number");
        }
        addAtts(td.getAtts());
        endOpenTag();
        addTextContent(td, currentIrefConverter);
        closeTag("td", false);
    }

    private void addInsert(Insert insert, String positionPrefix, int position) throws IOException {
        startOpenTag("insert");
        addAttribute("type", Insert.typeToString(insert.getType()));
        String src = insert.getSrc();
        String ref = insert.getRef();
        addAttribute("src", src);
        if ((!src.isEmpty()) && (!StringUtils.isAbsoluteUrlString(src))) {
            if (Insert.isDocumentType(insert.getType())) {
                if (audioBaseUrl != null) {
                    addAttribute("base", audioBaseUrl);
                }
            } else {
                if (imageBaseUrl != null) {
                    addAttribute("base", imageBaseUrl);
                }
            }
        }
        addAttribute("ref", ref);
        if ((!ref.isEmpty()) && (extractionContext.getLinkAnalyser().isExternalLink(ref))) {
            addAttribute("link", "external");
        }
        addAttribute("position", Insert.positionToString(insert.getPosition()));
        int width = insert.getWidth();
        if (width >= 0) {
            addAttribute("width", String.valueOf(width));
        }
        int height = insert.getHeight();
        if (height >= 0) {
            addAttribute("height", String.valueOf(height));
        }
        SubsetKey subsetKey = insert.getSubsetKey();
        Document insertDocument = null;
        if (subsetKey != null) {
            int id = insert.getId();
            if (subsetKey.isAddendaSubset()) {
                addAttribute("addenda", subsetKey.getSubsetName());
                addAttribute("id", id);
                Addenda addenda = (Addenda) extractionContext.getFichotheque().getSubset(subsetKey);
                if (addenda != null) {
                    insertDocument = addenda.getDocumentById(id);
                    if (insertDocument != null) {
                        addAttribute("basename", insertDocument.getBasename());
                    }
                }
            } else {
                String albumDimName = insert.getAlbumDimName();
                addAttribute("album", subsetKey.getSubsetName());
                addAttribute("id", id);
                addAttribute("albumdim", albumDimName);
                boolean done = false;
                Album album = (Album) extractionContext.getFichotheque().getSubset(subsetKey);
                if (album != null) {
                    Illustration illustration = album.getIllustrationById(id);
                    if (illustration != null) {
                        addAttribute("format", illustration.getFormatTypeString());
                        int albumWidth = illustration.getOriginalWidth();
                        int albumHeight = illustration.getOriginalHeight();
                        if (albumDimName != null) {
                            AlbumDim albumDim = album.getAlbumMetadata().getAlbumDimByName(albumDimName);
                            if (albumDim != null) {
                                ResizeInfo.Check check = albumDim.getResizeInfo().checkDim(albumWidth, albumHeight);
                                albumWidth = check.getWidth();
                                albumHeight = check.getHeight();
                            }
                        }
                        addAttribute("album-width", albumWidth);
                        addAttribute("album-height", albumHeight);
                        addAttribute("album-ratio", String.valueOf(((float) albumWidth) / albumHeight));
                        done = true;
                    }
                }
                if (!done) {
                    addAttribute("format", "png");
                }
            }
        }
        if ((width > 0) && (height > 0)) {
            addAttribute("ratio", String.valueOf(((float) width) / height));
        }
        addAtts(insert);
        addPositionAttribute(positionPrefix, position);
        endOpenTag();
        if (insertDocument != null) {
            for (Version version : insertDocument.getVersionList()) {
                startOpenTag("insertversion");
                addAttribute("extension", version.getExtension());
                addAttribute("type", extractionContext.getMimeTypeResolver().getMimeType(version.getExtension()));
                closeEmptyTag();
            }
        }
        addZoneBlockElements(insert);
        addZoneBlockElement("alt", insert.getAlt());
        addZoneBlockElement("credit", insert.getCredit());
        closeTag("insert");
    }

    private void addZoneBlockElements(ZoneBlock zoneBlock) throws IOException {
        addZoneBlockElement("numero", zoneBlock.getNumero());
        addZoneBlockElement("legende", zoneBlock.getLegende());
    }

    private void addZoneBlockElement(String name, TextContent textContent) throws IOException {
        if (!textContent.isEmpty()) {
            startOpenTag(name);
            endOpenTag();
            addTextContent(textContent, currentIrefConverter);
            closeTag(name, false);
        }
    }

    private void addAtts(FicheBlock ficheBlock) throws IOException {
        addAtts(ficheBlock.getAtts());
    }

    private void addAtts(Atts atts) throws IOException {
        boolean variantDone = false;
        int attLength = atts.size();
        for (int i = 0; i < attLength; i++) {
            String name = atts.getName(i);
            String value = atts.getValue(i);
            switch (name) {
                case "data-bdf-position":
                    break;
                case "syntax":
                    addAttribute("syntax", value);
                    break;
                case "variant":
                case "mode":
                    if (!variantDone) {
                        addAttribute("variant", value);
                        variantDone = true;
                    }
                    break;
                default:
                    addAttribute("att-" + name, value);
            }
        }
    }

    private void addAtts(S s, boolean ignoreVariant) throws IOException {
        Atts atts = s.getAtts();
        boolean syntaxDone = false;
        int attLength = atts.size();
        for (int i = 0; i < attLength; i++) {
            String name = atts.getName(i);
            String value = atts.getValue(i);
            switch (name) {
                case "data-bdf-position":
                    break;
                case "syntax":
                case "mode":
                    if (!syntaxDone) {
                        addAttribute("syntax", value);
                        syntaxDone = true;
                    }
                    break;
                case "variant":
                    if (!ignoreVariant) {
                        addAttribute("variant", value);
                    }
                    break;
                default:
                    addAttribute("att-" + name, value);
            }
        }
    }

    private String getLinkText(FicheMeta other) {
        Attribute attribute = other.getCorpus().getCorpusMetadata().getAttributes().getAttribute(ExtractionSpace.LINKTEXTSOURCE_ATTRIBUTEKEY);
        if (attribute != null) {
            FicheAPI ficheAPI = other.getFicheAPI(false);
            for (String value : attribute) {
                try {
                    FieldKey fieldKey = FieldKey.parse(value);
                    Object obj = ficheAPI.getValue(fieldKey);
                    if (obj != null) {
                        if (obj instanceof String) {
                            return ((String) obj);
                        } else if (obj instanceof FicheItem) {
                            if (obj instanceof Item) {
                                return ((Item) obj).getValue();
                            } else if (obj instanceof ParaItem) {
                                return ((ParaItem) obj).contentToString();
                            }
                        } else {
                            return "#ERR: bdfxml:linktextsource unsupported fieldkey: " + value;
                        }
                    }
                } catch (ParseException pe) {
                    return "#ERR: wrong bdfxml:linktextsource value: " + value;
                }
            }
        }
        return other.getTitle();
    }

    private String appendPositionPrefix(String positionPrefix, int position) {
        if (positionPrefix != null) {
            return positionPrefix + position + ".";
        } else {
            return null;
        }
    }

}
