/* FichothequeLib_Tools - Copyright (c) 2007-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.tools.dom;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.corpus.metadata.CorpusField;
import net.fichotheque.corpus.metadata.CorpusMetadata;
import net.fichotheque.corpus.metadata.CorpusMetadataEditor;
import net.fichotheque.corpus.metadata.ExistingFieldKeyException;
import net.fichotheque.corpus.metadata.FieldGeneration;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.corpus.metadata.FieldOptionConstants;
import net.fichotheque.corpus.metadata.FieldOptionException;
import net.fichotheque.tools.corpus.FieldGenerationParser;
import net.fichotheque.utils.FieldOptionUtils;
import net.mapeadores.util.attr.AttributesBuilder;
import net.mapeadores.util.exceptions.ImplementationException;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.LocalisationUtils;
import net.mapeadores.util.logging.MessageHandler;
import net.mapeadores.util.logging.LogUtils;
import net.mapeadores.util.money.ExtendedCurrency;
import net.mapeadores.util.text.Label;
import net.mapeadores.util.text.LabelUtils;
import net.mapeadores.util.xml.DOMUtils;
import net.mapeadores.util.xml.DomMessages;
import net.mapeadores.util.xml.XMLUtils;
import org.w3c.dom.Element;


/**
 * Lit le format XML de corpus-metadata à partir du DOM.
 *
 * @author Vincent Calame
 */
public class CorpusMetadataDOMReader extends AbstractMetadataDOMReader {

    private final CorpusMetadataEditor corpusMetadataEditor;

    public CorpusMetadataDOMReader(CorpusMetadataEditor corpusMetadataEditor, MessageHandler messageHandler) {
        super(corpusMetadataEditor, messageHandler, "corpus");
        this.corpusMetadataEditor = corpusMetadataEditor;
    }

    public void fillMetadata(Element element) {
        AttributesBuilder attributesBuilder = new AttributesBuilder();
        RootConsumer rootConsumer = new RootConsumer(attributesBuilder, "/" + element.getTagName());
        DOMUtils.readChildren(element, rootConsumer);
        rootConsumer.flush();
        flush(attributesBuilder);
    }

    private FieldKey getFieldKey(short category, Element element) {
        String fieldName = element.getAttribute("name");
        if (fieldName.length() == 0) {
            fieldName = element.getAttribute("type");
        }
        if (fieldName.length() == 0) {
            DomMessages.emptyAttribute(messageHandler, element.getTagName(), "name");
            return null;
        }
        try {
            return FieldKey.parse(category, fieldName);
        } catch (ParseException e) {
            DomMessages.wrongAttributeValue(messageHandler, element.getTagName(), "name", fieldName);
            return null;
        }
    }

    private String getFicheItemType(FieldKey fieldKey, Element element) {
        String ficheItemType = element.getAttribute("fiche-item");
        if (ficheItemType.length() > 0) {
            try {
                return CorpusField.checkFicheItemType(ficheItemType);
            } catch (IllegalArgumentException iae) {
                addFichothequeError("_ error.wrong.ficheitemtype", ficheItemType + " (" + fieldKey.getKeyString() + ")");
                return CorpusField.ITEM_FIELD;
            }
        }
        return CorpusField.ITEM_FIELD;
    }


    private CorpusField getCorpusField(Element element) {
        String fieldkey_string = element.getAttribute("field-key");
        if (fieldkey_string.length() == 0) {
            DomMessages.emptyAttribute(messageHandler, element.getTagName(), "field-key");
            return null;
        }
        FieldKey fieldKey;
        try {
            fieldKey = FieldKey.parse(fieldkey_string);
        } catch (ParseException pe) {
            DomMessages.wrongAttributeValue(messageHandler, element.getTagName(), "field-key", fieldkey_string);
            return null;
        }
        CorpusField corpusField = ((CorpusMetadata) corpusMetadataEditor.getMetadata()).getCorpusField(fieldKey);
        if (corpusField == null) {
            addFichothequeError("_ error.unknown.fieldkey", fieldKey);
        }
        return corpusField;
    }

    private void addFichothequeError(String messageKey, Object messageValue) {
        messageHandler.addMessage(FichothequeConstants.SEVERE_FICHOTHEQUE, LocalisationUtils.toMessage(messageKey, messageValue));
    }


    private class RootConsumer implements Consumer<Element> {

        private final AttributesBuilder attributesBuilder;
        private final String parentXpath;
        private final StringBuilder fieldGenerationRawBuf = new StringBuilder();
        private Element fieldsElement = null;
        private Element optionsElement = null;
        private Element l10nElement = null;

        private RootConsumer(AttributesBuilder attributesBuilder, String parentXpath) {
            this.attributesBuilder = attributesBuilder;
            this.parentXpath = parentXpath;
        }

        @Override
        public void accept(Element element) {
            if (readCommonElement(element, attributesBuilder, parentXpath)) {
                return;
            }
            String tagname = element.getTagName();
            if ((fieldsElement == null) && (tagname.equals("fields"))) {
                fieldsElement = element;
            } else if (tagname.equals("field-generation")) {
                fieldGenerationRawBuf.append(XMLUtils.getRawData(element));
            } else if ((optionsElement == null) && (tagname.equals("options"))) {
                optionsElement = element;
            } else if ((l10nElement == null) && (tagname.equals("l10n"))) {
                l10nElement = element;
            } else {
                DomMessages.unknownTagWarning(messageHandler, tagname);
            }
        }

        private void flush() {
            DOMUtils.readChildren(fieldsElement, new FieldsConsumer());
            if (optionsElement != null) {
                DOMUtils.readChildren(optionsElement, new OptionsConsumer(fieldGenerationRawBuf));
            }
            if (l10nElement != null) {
                DOMUtils.readChildren(l10nElement, new L10nConsumer());
            }
            if (fieldGenerationRawBuf.length() > 0) {
                FieldGeneration fieldGeneration = FieldGenerationParser.parse(fieldGenerationRawBuf.toString(), LogUtils.NULL_LINEMESSAGEHANDLER);
                corpusMetadataEditor.setFieldGeneration(fieldGeneration);
            }
        }

    }


    private class FieldsConsumer implements Consumer<Element> {

        private FieldsConsumer() {

        }

        @Override
        public void accept(Element element) {
            String tagname = element.getTagName();
            FieldKey fieldKey = null;
            String ficheItemType = "";
            switch (tagname) {
                case "subtitle":
                case "soustitre":
                    fieldKey = FieldKey.SUBTITLE;
                    break;
                case "texte":
                    fieldKey = FieldKey.build(FieldKey.SECTION_CATEGORY, "texte");
                    break;
                case "section":
                case "annexe":
                    fieldKey = getFieldKey(FieldKey.SECTION_CATEGORY, element);
                    break;
                case "prop":
                case "propriete":
                case "property":
                    fieldKey = getFieldKey(FieldKey.PROP_CATEGORY, element);
                    if (fieldKey != null) {
                        ficheItemType = getFicheItemType(fieldKey, element);
                    }
                    break;
                case "info":
                case "information":
                    fieldKey = getFieldKey(FieldKey.INFO_CATEGORY, element);
                    if (fieldKey != null) {
                        ficheItemType = getFicheItemType(fieldKey, element);
                    }
                    break;
                default:
                    DomMessages.unknownTagWarning(messageHandler, tagname);
            }
            if (fieldKey != null) {
                try {
                    corpusMetadataEditor.createCorpusField(fieldKey, ficheItemType);
                } catch (ExistingFieldKeyException efke) {
                    addFichothequeError("_ error.existing.fieldkey", fieldKey.getKeyString());
                }
            }
        }

    }


    private class L10nConsumer implements Consumer<Element> {

        private L10nConsumer() {

        }

        @Override
        public void accept(Element element) {
            String tagname = element.getTagName();
            if (tagname.equals("field")) {
                CorpusField corpusField = getCorpusField(element);
                if (corpusField == null) {
                    return;
                }
                FieldL10nConsumer fieldL10nHandler = new FieldL10nConsumer(corpusField);
                DOMUtils.readChildren(element, fieldL10nHandler);
            } else if (tagname.equals("intitule")) {
                FichothequeDOMUtils.readIntitule(element, corpusMetadataEditor, messageHandler, "corpus");
            }
        }

    }


    private class FieldL10nConsumer implements Consumer<Element> {

        private final CorpusField corpusField;

        private FieldL10nConsumer(CorpusField corpusField) {
            this.corpusField = corpusField;
        }

        @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) {
                        corpusMetadataEditor.putFieldLabel(corpusField, label);
                    }
                } catch (ParseException ile) {
                }
            }
        }

    }


    private class OptionsConsumer implements Consumer<Element> {

        private final StringBuilder fieldGenerationSourceBuf;

        private OptionsConsumer(StringBuilder fieldGenerationSourceBuf) {
            this.fieldGenerationSourceBuf = fieldGenerationSourceBuf;
        }

        @Override
        public void accept(Element element) {
            String tagname = element.getTagName();
            if (tagname.equals("option")) {
                String name = element.getAttribute("name");
                if (name.length() == 0) {
                    DomMessages.emptyAttribute(messageHandler, "option", "name");
                    return;
                }
                CorpusField corpusField = getCorpusField(element);
                if (corpusField == null) {
                    return;
                }
                if (name.equals("titreGenerationFormat")) {
                    initOldFieldGeneration(corpusField, element, fieldGenerationSourceBuf);
                    return;
                }
                name = checkOptionName(name);
                if (name.isEmpty()) {
                    return;
                }
                if (!FieldOptionUtils.testFieldOption(name, corpusField)) {
                    addFichothequeError("_ error.unsupported.fieldoption", name);
                    return;
                }
                switch (name) {
                    case FieldOptionConstants.CURRENCYARRAY_OPTION:
                        initCurrencies(corpusField, element);
                        break;
                    case FieldOptionConstants.GEOLOCALISATIONFIELD_OPTION:
                        initGeolocalisationField(corpusField);
                        break;
                    case FieldOptionConstants.ADDRESSFIELDARRAY_OPTION:
                        initAddressFieldArray(corpusField, element);
                        break;
                    case FieldOptionConstants.LANGARRAY_OPTION:
                        initLangs(corpusField, element);
                        break;
                    default:
                        initStringOption(corpusField, element, name);
                }
            } else {
                DomMessages.unknownTagWarning(messageHandler, tagname);
            }
        }

        private void initGeolocalisationField(CorpusField corpusField) {
            corpusMetadataEditor.setGeolocalisationField(corpusField);
        }

        private void initOldFieldGeneration(CorpusField corpusField, Element el, StringBuilder fieldGenerationSourceBuf) {
            List<String> valueList = readValueList(el);
            if (valueList == null) {
                return;
            }
            if (fieldGenerationSourceBuf.length() > 0) {
                fieldGenerationSourceBuf.append("\n\n");
            }
            fieldGenerationSourceBuf.append(corpusField.getFieldString());
            fieldGenerationSourceBuf.append('\n');
            int size = valueList.size();
            fieldGenerationSourceBuf.append(valueList.get(0));
            fieldGenerationSourceBuf.append('\n');
            if (size > 1) {
                String value2 = valueList.get(1).trim();
                if (value2.length() == 0) {
                    fieldGenerationSourceBuf.append("-");
                } else {
                    fieldGenerationSourceBuf.append(value2);
                }
                fieldGenerationSourceBuf.append('\n');
                if (size > 2) {
                    fieldGenerationSourceBuf.append(valueList.get(2));
                }
            }
        }

        private void initAddressFieldArray(CorpusField corpusField, Element el) {
            List<String> valueList = readValueList(el);
            if (valueList == null) {
                return;
            }
            List<FieldKey> fieldKeyList = new ArrayList<FieldKey>();
            for (String value : valueList) {
                boolean done;
                try {
                    FieldKey otherFieldKey = FieldKey.parse(value);
                    CorpusField otherCorpusField = ((CorpusMetadata) corpusMetadataEditor.getMetadata()).getCorpusField(otherFieldKey);
                    if (otherCorpusField != null) {
                        fieldKeyList.add(otherFieldKey);
                        done = true;
                    } else {
                        done = false;
                    }
                } catch (ParseException pe) {
                    done = false;
                }
                if (!done) {
                    DomMessages.wrongElementValue(messageHandler, "value", value);
                }
            }
            try {
                corpusMetadataEditor.setFieldOption(corpusField, FieldOptionConstants.ADDRESSFIELDARRAY_OPTION, fieldKeyList.toArray(new FieldKey[fieldKeyList.size()]));
            } catch (FieldOptionException foe) {
                throw new ImplementationException(foe);
            }
        }

        private void initStringOption(CorpusField corpusField, Element el, String name) {
            String value = el.getAttribute("value");
            if (value.length() > 0) {
                try {
                    corpusMetadataEditor.setFieldOption(corpusField, name, value);
                } catch (FieldOptionException foe) {
                    DomMessages.wrongAttributeValue(messageHandler, el.getTagName(), "value", value);
                }
            }
        }

        private void initCurrencies(CorpusField corpusField, Element el) {
            List<String> valueList = readValueList(el);
            if (valueList == null) {
                return;
            }
            List<ExtendedCurrency> currencyList = new ArrayList<ExtendedCurrency>();
            for (String value : valueList) {
                try {
                    currencyList.add(ExtendedCurrency.parse(value));
                } catch (ParseException pe) {
                    DomMessages.wrongElementValue(messageHandler, "value", value);
                }
            }
            if (currencyList.isEmpty()) {
                return;
            }
            try {
                corpusMetadataEditor.setFieldOption(corpusField, FieldOptionConstants.CURRENCYARRAY_OPTION, currencyList.toArray(new ExtendedCurrency[currencyList.size()]));
            } catch (FieldOptionException foe) {
                throw new ImplementationException(foe);
            }
        }

        private void initLangs(CorpusField corpusField, Element el) {
            List<String> valueList = readValueList(el);
            if (valueList == null) {
                return;
            }
            List<Lang> langList = new ArrayList<Lang>();
            for (String value : valueList) {
                try {
                    langList.add(Lang.parse(value));
                } catch (ParseException pe) {
                    DomMessages.wrongElementValue(messageHandler, "value", value);
                }
            }
            if (langList.isEmpty()) {
                return;
            }
            try {
                corpusMetadataEditor.setFieldOption(corpusField, FieldOptionConstants.LANGARRAY_OPTION, langList.toArray(new Lang[langList.size()]));
            } catch (FieldOptionException foe) {
                throw new ImplementationException(foe);
            }
        }

        private List<String> readValueList(Element element) {
            List<String> valueList = new ArrayList<String>();
            DOMUtils.readChildren(element, new ValueConsumer(valueList));
            int size = valueList.size();
            if (size == 0) {
                DomMessages.missingChildTag(messageHandler, element.getTagName(), "value");
                return null;
            }
            return valueList;
        }

    }


    private class ValueConsumer implements Consumer<Element> {

        private final List<String> valueList;

        private ValueConsumer(List<String> valueList) {
            this.valueList = valueList;
        }

        @Override
        public void accept(Element el) {
            String tagname = el.getTagName();
            if (tagname.equals("value")) {
                valueList.add(XMLUtils.getData(el));
            } else {
                DomMessages.unknownTagWarning(messageHandler, tagname);
            }
        }

    }

    private static String checkOptionName(String name) {
        switch (name) {
            case "imageSource":
            case "albumKey":
                return "";
            default:
                return FieldOptionConstants.checkAlias(name);
        }
    }

}
