/* FichothequeLib_Tools - Copyright (c) 2011-2024 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.tools.exportation.scrutari.dom;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import net.fichotheque.Fichotheque;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.exportation.scrutari.CorpusScrutariDef;
import net.fichotheque.exportation.scrutari.ThesaurusScrutariDef;
import net.fichotheque.tools.exportation.scrutari.CorpusScrutariDefBuilder;
import net.fichotheque.tools.exportation.scrutari.ScrutariExportDefBuilder;
import net.fichotheque.tools.exportation.scrutari.ThesaurusScrutariDefBuilder;
import net.fichotheque.tools.exportation.scrutari.WrongFormatSourceKeyException;
import net.fichotheque.tools.selection.SelectionDOMUtils;
import net.mapeadores.util.attr.AttributeKey;
import net.mapeadores.util.attr.AttributeUtils;
import net.mapeadores.util.attr.CheckedLocalKey;
import net.mapeadores.util.attr.CheckedNameSpace;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.Langs;
import net.mapeadores.util.localisation.LangsUtils;
import net.mapeadores.util.logging.MessageHandler;
import net.mapeadores.util.text.AccoladePattern;
import net.mapeadores.util.text.Label;
import net.mapeadores.util.text.LabelChangeBuilder;
import net.mapeadores.util.text.LabelUtils;
import net.mapeadores.util.text.Labels;
import net.mapeadores.util.text.RelativePath;
import net.mapeadores.util.text.StringUtils;
import net.mapeadores.util.xml.DOMUtils;
import net.mapeadores.util.xml.DomMessages;
import net.mapeadores.util.xml.XMLUtils;
import org.w3c.dom.Element;


/**
 *
 * @author Vincent Calame
 */
public class ScrutariExportDefDOMReader {

    private final Fichotheque fichotheque;
    private final ScrutariExportDefBuilder scrutariExportDefBuilder;
    private final MessageHandler messageHandler;

    public ScrutariExportDefDOMReader(Fichotheque fichotheque, ScrutariExportDefBuilder scrutariExportDefBuilder, MessageHandler messageHandler) {
        this.fichotheque = fichotheque;
        this.scrutariExportDefBuilder = scrutariExportDefBuilder;
        this.messageHandler = messageHandler;
    }

    public ScrutariExportDefDOMReader read(Element element) {
        DOMUtils.readChildren(element, new RootConsumer());
        return this;
    }

    public static ScrutariExportDefDOMReader init(Fichotheque fichotheque, ScrutariExportDefBuilder scrutariExportDefBuilder, MessageHandler messageHandler) {
        return new ScrutariExportDefDOMReader(fichotheque, scrutariExportDefBuilder, messageHandler);
    }


    private class RootConsumer implements Consumer<Element> {

        private RootConsumer() {

        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            switch (tagName) {
                case "authority":
                    String authority = DOMUtils.readSimpleElement(element);
                    if (authority.length() > 0) {
                        try {
                            scrutariExportDefBuilder.setAuthority(authority);
                        } catch (ParseException pe) {
                            DomMessages.wrongElementValue(messageHandler, tagName, authority);
                        }
                    }
                    break;
                case "base-name":
                    String baseName = DOMUtils.readSimpleElement(element);
                    if (baseName.length() > 0) {
                        try {
                            scrutariExportDefBuilder.setBaseName(baseName);
                        } catch (ParseException pe) {
                            DomMessages.wrongElementValue(messageHandler, tagName, baseName);
                        }
                    }
                    break;
                case "base-icon":
                    scrutariExportDefBuilder.setBaseIcon(DOMUtils.readSimpleElement(element));
                    break;
                case "langs-ui":
                    DOMUtils.readChildren(element, new LangsUiConsumer());
                    break;
                case "fiche-pattern":
                    String fichePatternString = DOMUtils.readSimpleElement(element);
                    if (fichePatternString.length() > 0) {
                        try {
                            AccoladePattern pattern = new AccoladePattern(fichePatternString);
                            scrutariExportDefBuilder.setFicheHrefPattern(pattern);
                        } catch (ParseException pe) {
                            DomMessages.wrongElementValue(messageHandler, tagName, fichePatternString);
                        }
                    }
                    break;
                case "motcle-pattern":
                    String motclePatternString = DOMUtils.readSimpleElement(element);
                    if (motclePatternString.length() > 0) {
                        try {
                            AccoladePattern pattern = new AccoladePattern(motclePatternString);
                            scrutariExportDefBuilder.setMotcleHrefPattern(pattern);
                        } catch (ParseException pe) {
                            DomMessages.wrongElementValue(messageHandler, tagName, motclePatternString);
                        }
                    }
                    break;
                case "corpus-def":
                    String corpusString = element.getAttribute("corpus");
                    if (corpusString.length() == 0) {
                        DomMessages.emptyAttribute(messageHandler, tagName, "corpus");
                    } else {
                        try {
                            SubsetKey corpusKey = SubsetKey.parse(SubsetKey.CATEGORY_CORPUS, corpusString);
                            CorpusDefConsumer corpusDefConsumer = new CorpusDefConsumer(corpusKey);
                            DOMUtils.readChildren(element, corpusDefConsumer);
                            scrutariExportDefBuilder.addCorpusScrutariDef(corpusDefConsumer.toCorpusScrutariDef());
                        } catch (ParseException pe) {
                            DomMessages.wrongAttributeValue(messageHandler, tagName, "corpus", corpusString);
                        }
                    }
                    break;
                case "thesaurus-def":
                    String thesaurusString = element.getAttribute("thesaurus");
                    if (thesaurusString.length() == 0) {
                        DomMessages.emptyAttribute(messageHandler, tagName, "thesaurus");
                    } else {
                        try {
                            SubsetKey thesaurusKey = SubsetKey.parse(SubsetKey.CATEGORY_THESAURUS, thesaurusString);
                            ThesaurusDefConsumer thesaurusDefConsumer = new ThesaurusDefConsumer(thesaurusKey);
                            DOMUtils.readChildren(element, thesaurusDefConsumer);
                            scrutariExportDefBuilder.addThesaurusScrutariDef(thesaurusDefConsumer.toThesaurusScrutariDef());
                        } catch (ParseException pe) {
                            DomMessages.wrongAttributeValue(messageHandler, tagName, "thesaurus", thesaurusString);
                        }
                    }
                    break;
                case "intitule-short":
                    DOMUtils.readChildren(element, new BaseIntituleConsumer(1));
                    break;
                case "intitule-long":
                    DOMUtils.readChildren(element, new BaseIntituleConsumer(2));
                    break;
                case "target-name":
                    scrutariExportDefBuilder.setTargetName(DOMUtils.readSimpleElement(element));
                    break;
                case "target-path":
                    String targetPath = DOMUtils.readSimpleElement(element);
                    try {
                        RelativePath relativePath = RelativePath.parse(targetPath);
                        scrutariExportDefBuilder.setTargetPath(relativePath);
                    } catch (ParseException pe) {
                        DomMessages.wrongElementValue(messageHandler, tagName, targetPath);
                    }
                    break;
                case "label":
                case "lib": {
                    try {
                        LabelUtils.readLabel(element, scrutariExportDefBuilder);
                    } catch (ParseException ile) {
                        DomMessages.wrongLangAttribute(messageHandler, tagName, element.getAttribute("xml:lang"));
                    }
                    break;
                }
                case "attr":
                    AttributeUtils.readAttrElement(scrutariExportDefBuilder.getAttributesBuilder(), element);
                    break;
                case "include-token":
                    String includeToken = DOMUtils.readSimpleElement(element);
                    if (includeToken.length() > 0) {
                        scrutariExportDefBuilder.addIncludeToken(includeToken);
                    }
                    break;
                default:
                    if (!SelectionDOMUtils.readSelectionOptions(scrutariExportDefBuilder.getSelectionOptionsBuilder(), fichotheque, element)) {
                        DomMessages.unknownTagWarning(messageHandler, tagName);
                    }
            }
        }

    }


    private class LangsUiConsumer implements Consumer<Element> {

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            if (tagName.equals("lang")) {
                String langString = DOMUtils.readSimpleElement(element);
                try {
                    Lang lang = Lang.parse(langString);
                    scrutariExportDefBuilder.addLang(lang);
                } catch (ParseException lie) {
                    DomMessages.wrongElementValue(messageHandler, tagName, langString);
                }
            } else {
                DomMessages.unknownTagWarning(messageHandler, tagName);
            }
        }

    }


    private class BaseIntituleConsumer implements Consumer<Element> {

        private final int baseIntituleType;

        private BaseIntituleConsumer(int baseIntituleType) {
            this.baseIntituleType = baseIntituleType;
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            if ((tagName.equals("label")) || (tagName.equals("lib"))) {
                try {
                    Label label = LabelUtils.readLabel(element);
                    if (label != null) {
                        scrutariExportDefBuilder.putBaseLabel(baseIntituleType, label);
                    }
                } catch (ParseException ile) {
                    DomMessages.wrongLangAttribute(messageHandler, tagName, element.getAttribute("xml:lang"));
                }
            } else {
                DomMessages.unknownTagWarning(messageHandler, tagName);
            }
        }

    }


    private class CorpusDefConsumer implements Consumer<Element> {

        private final CorpusScrutariDefBuilder builder;
        private final FormatDefConsumer formatDefConsumer;
        private final ComplementConsumer complementConsumer;
        private final StringBuilder fieldGenerationSourceBuf;

        private CorpusDefConsumer(SubsetKey corpusKey) {
            builder = new CorpusScrutariDefBuilder(corpusKey);
            formatDefConsumer = new FormatDefConsumer();
            complementConsumer = new ComplementConsumer();
            fieldGenerationSourceBuf = new StringBuilder();
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            if (tagName.equals("date")) {
                String dateString = DOMUtils.readSimpleElement(element);
                try {
                    builder.setDateKey(dateString);
                } catch (ParseException | WrongFormatSourceKeyException e) {
                    DomMessages.wrongElementValue(messageHandler, tagName, dateString);
                }
            } else if (tagName.equals("titre-format")) {
                DOMUtils.readChildren(element, formatDefConsumer);
                formatDefConsumer.append(fieldGenerationSourceBuf, "titre");
            } else if (tagName.equals("soustitre-format")) {
                DOMUtils.readChildren(element, formatDefConsumer);
                formatDefConsumer.append(fieldGenerationSourceBuf, "soustitre");
            } else if (tagName.equals("complement")) {
                complementConsumer.startNewComplement();
                DOMUtils.readChildren(element, complementConsumer);
                complementConsumer.append(fieldGenerationSourceBuf);
            } else if (tagName.equals("attribute")) {
                String nameSpaceString = element.getAttribute("ns");
                if (nameSpaceString.isEmpty()) {
                    DomMessages.emptyAttribute(messageHandler, "attribute", "ns");
                    return;
                }
                CheckedNameSpace nameSpace;
                try {
                    nameSpace = CheckedNameSpace.parse(nameSpaceString);
                } catch (ParseException pe) {
                    DomMessages.wrongAttributeValue(messageHandler, "attribute", "ns", nameSpaceString);
                    return;
                }
                String localKeyString = element.getAttribute("key");
                if (localKeyString.isEmpty()) {
                    DomMessages.emptyAttribute(messageHandler, "attribute", "key");
                    return;
                }
                CheckedLocalKey localKey;
                try {
                    localKey = CheckedLocalKey.parse(localKeyString);
                } catch (ParseException pe) {
                    DomMessages.wrongAttributeValue(messageHandler, "attribute", "key", localKeyString);
                    return;
                }
                AttributeKey attributeKey = AttributeKey.build(nameSpace, localKey);
                DOMUtils.readChildren(element, formatDefConsumer);
                formatDefConsumer.append(fieldGenerationSourceBuf, attributeKey.toString());
            } else if (tagName.equals("href-pattern")) {
                String hrefPatternString = DOMUtils.readSimpleElement(element);
                if (hrefPatternString.length() > 0) {
                    try {
                        AccoladePattern pattern = new AccoladePattern(hrefPatternString);
                        builder.setHrefPattern(pattern);
                    } catch (ParseException pe) {
                        DomMessages.wrongElementValue(messageHandler, tagName, hrefPatternString);
                    }
                }
            } else if (tagName.equals("multilang-key")) {
                String oldKey = DOMUtils.readSimpleElement(element);
                if (oldKey.equals("_default")) {
                    builder.setLanguiMode();
                } else {
                    try {
                        FieldKey fieldKey = FieldKey.parse(oldKey);
                        builder.setFieldMode(fieldKey);
                    } catch (ParseException pe) {
                        try {
                            SubsetKey subsetKey = SubsetKey.parse(oldKey);
                            if (subsetKey.isThesaurusSubset()) {
                                builder.setThesaurusMode(subsetKey);
                            }
                        } catch (ParseException pe2) {
                            Langs langs = LangsUtils.toCleanLangs(oldKey);
                            builder.setCustomMode(langs);
                        }
                    }
                }
            } else if (tagName.equals("multilang")) {
                String mode = element.getAttribute("mode");
                String param = element.getAttribute("param");
                switch (mode) {
                    case CorpusScrutariDef.LANGUI_MODE:
                        builder.setLanguiMode();
                        break;
                    case CorpusScrutariDef.FIELD_MODE:
                        try {
                        FieldKey fieldKey = FieldKey.parse(param);
                        builder.setFieldMode(fieldKey);
                    } catch (ParseException pe) {

                    }
                    break;
                    case CorpusScrutariDef.THESAURUS_MODE:
                        try {
                        SubsetKey subsetKey = SubsetKey.parse(SubsetKey.CATEGORY_THESAURUS, param);
                        builder.setThesaurusMode(subsetKey);
                    } catch (ParseException pe) {

                    }
                    break;
                    case CorpusScrutariDef.CUSTOM_MODE:
                        Langs langs = LangsUtils.toCleanLangs(param);
                        builder.setCustomMode(langs);
                        break;
                }
            } else if (tagName.equals("field-generation")) {
                fieldGenerationSourceBuf.append(XMLUtils.getRawData(element));
            } else if (tagName.equals("include-token")) {
                String includeToken = DOMUtils.readSimpleElement(element);
                if (includeToken.length() > 0) {
                    builder.addIncludeToken(includeToken);
                }
            } else {
                DomMessages.unknownTagWarning(messageHandler, tagName);
            }
        }

        public CorpusScrutariDef toCorpusScrutariDef() {
            builder.setFieldGenerationSource(fieldGenerationSourceBuf.toString());
            return builder.toCorpusScrutariDef();
        }

    }


    private class ThesaurusDefConsumer implements Consumer<Element> {

        private final ThesaurusScrutariDefBuilder builder;
        private final StringBuilder fieldGenerationSourceBuf;

        private ThesaurusDefConsumer(SubsetKey thesaurusKey) {
            builder = new ThesaurusScrutariDefBuilder(thesaurusKey);
            fieldGenerationSourceBuf = new StringBuilder();
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            if (tagName.equals("motcle-format")) {
                String motcleFormatPattern = DOMUtils.readSimpleElement(element);
                if (motcleFormatPattern.length() > 0) {
                    if (fieldGenerationSourceBuf.length() > 0) {
                        fieldGenerationSourceBuf.append("\n\n");
                    }
                    fieldGenerationSourceBuf.append("labels\nthis\n");
                    fieldGenerationSourceBuf.append(motcleFormatPattern);
                }
            } else if (tagName.equals("field-generation")) {
                if (fieldGenerationSourceBuf.length() > 0) {
                    fieldGenerationSourceBuf.append("\n\n");
                }
                fieldGenerationSourceBuf.append(XMLUtils.getRawData(element));
            } else if (tagName.equals("whole-thesaurus")) {
                builder.setWholeThesaurus(true);
            } else if (tagName.equals("include-token")) {
                String includeToken = DOMUtils.readSimpleElement(element);
                if (includeToken.length() > 0) {
                    builder.addIncludeToken(includeToken);
                }
            } else {
                DomMessages.unknownTagWarning(messageHandler, tagName);
            }
        }

        public ThesaurusScrutariDef toThesaurusScrutariDef() {
            return builder
                    .setFieldGenerationSource(fieldGenerationSourceBuf.toString())
                    .toThesaurusScrutariDef();
        }

    }


    private class FormatDefConsumer implements Consumer<Element> {

        private final List<String> valueList = new ArrayList<String>();

        private FormatDefConsumer() {
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            if (tagName.equals("value")) {
                valueList.add(DOMUtils.readSimpleElement(element));
            } else {
                DomMessages.unknownTagWarning(messageHandler, tagName);
            }
        }

        private void append(StringBuilder buf, String fieldName) {
            checkValueList(valueList);
            if (valueList.isEmpty()) {
                return;
            }
            buf.append(fieldName);
            buf.append("\n");
            addValueList(buf, valueList);
            valueList.clear();
        }

    }


    private class ComplementConsumer implements Consumer<Element> {

        private final List<String> valueList = new ArrayList<String>();
        private final LabelChangeBuilder changeLabelHolderBuilder = new LabelChangeBuilder();

        private ComplementConsumer() {
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            if (tagName.equals("value")) {
                valueList.add(DOMUtils.readSimpleElement(element));
            } else if ((tagName.equals("label")) || (tagName.equals("lib"))) {
                try {
                    Label label = LabelUtils.readLabel(element);
                    if (label != null) {
                        changeLabelHolderBuilder.putLabel(label);
                    }
                } catch (ParseException ile) {
                    DomMessages.wrongLangAttribute(messageHandler, tagName, element.getAttribute("xml:lang"));
                }
            } else {
                DomMessages.unknownTagWarning(messageHandler, tagName);
            }
        }

        private void startNewComplement() {
            valueList.clear();
            changeLabelHolderBuilder.clear();
        }

        private void append(StringBuilder buf) {
            checkValueList(valueList);
            if (valueList.isEmpty()) {
                return;
            }
            buf.append("complement");
            if (changeLabelHolderBuilder.getAddedLabelCount() > 0) {
                Labels labels = changeLabelHolderBuilder.toLabels();
                buf.append(" > ");
                boolean next = false;
                for (Label label : labels) {
                    if (next) {
                        buf.append(',');
                    } else {
                        next = true;
                    }
                    buf.append(label.getLang().toString());
                    buf.append("=\"");
                    StringUtils.escapeDoubleQuote(label.getLabelString(), buf);
                    buf.append("\"");
                }
            }
            buf.append('\n');
            addValueList(buf, valueList);
            valueList.clear();
            changeLabelHolderBuilder.clear();
        }

    }


    private static void checkValueList(List<String> valueList) {
        int size = valueList.size();
        boolean remove = true;
        for (int i = (size - 1); i >= 0; i--) {
            String value = valueList.get(i);
            if (value.length() == 0) {
                if (remove) {
                    valueList.remove(i);
                } else {
                    valueList.set(i, "-");
                }
            } else {
                remove = false;
            }
        }
    }

    private static void addValueList(StringBuilder buf, List<String> valueList) {
        for (String value : valueList) {
            buf.append(value);
            buf.append("\n");
        }
        buf.append("\n");
    }

}
