/* BdfServer - Copyright (c) 2015-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 fr.exemole.bdfserver.tools.ui.components;

import fr.exemole.bdfserver.api.BdfServerConstants;
import fr.exemole.bdfserver.api.ui.CommentUi;
import fr.exemole.bdfserver.api.ui.DataUi;
import fr.exemole.bdfserver.api.ui.FieldUi;
import fr.exemole.bdfserver.api.ui.IncludeUi;
import fr.exemole.bdfserver.tools.ui.UiUtils;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.include.ExtendedIncludeKey;
import net.fichotheque.include.IncludeKey;
import net.mapeadores.util.attr.AttributeUtils;
import net.mapeadores.util.html.HtmlCleaner;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.logging.MessageHandler;
import net.mapeadores.util.text.Label;
import net.mapeadores.util.text.LabelUtils;
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;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 *
 * @author Vincent Calame
 */
public final class UiDOMUtils {

    private UiDOMUtils() {

    }

    public static CommentUi readCommentUi(Element element, HtmlCleaner htmlCleaner, MessageHandler messageHandler, String xpath) {
        String name = element.getAttribute("name");
        if (name.length() == 0) {
            DomMessages.emptyAttribute(messageHandler, xpath, "name");
            return null;
        }
        try {
            CommentUi.checkCommentName(name);
        } catch (ParseException pe) {
            DomMessages.wrongAttributeValue(messageHandler, xpath, "name", name);
            return null;
        }
        CommentUiBuilder commentUiBuilder = CommentUiBuilder.init(htmlCleaner, name);
        String locationString = element.getAttribute("location");
        if (locationString.length() != 0) {
            int location = UiUtils.maskStringToInt(locationString);
            commentUiBuilder.setLocation(location);
        }
        DOMUtils.readChildren(element, new CommentUiConsumer(commentUiBuilder, messageHandler));
        return commentUiBuilder.toCommentUi();
    }

    public static DataUi readDataUi(Element element, MessageHandler messageHandler, String xpath) {
        String name = element.getAttribute("name");
        if (name.length() == 0) {
            DomMessages.emptyAttribute(messageHandler, xpath, "name");
            return null;
        }
        try {
            DataUi.checkDataName(name);
        } catch (ParseException pe) {
            DomMessages.wrongAttributeValue(messageHandler, xpath, "name", name);
            return null;
        }
        String type = element.getAttribute("type");
        if (type.length() == 0) {
            DomMessages.emptyAttribute(messageHandler, xpath, "type");
            return null;
        }
        DataUiBuilder dataUiBuilder = DataUiBuilder.init(name, type);
        DOMUtils.readChildren(element, new DataUiConsumer(dataUiBuilder, messageHandler));
        return dataUiBuilder.toDataUi();
    }

    public static FieldUi readFieldUi(Element element, MessageHandler messageHandler, String xpath) {
        String fieldKeyString = element.getAttribute("field-key");
        if (fieldKeyString.length() == 0) {
            DomMessages.emptyAttribute(messageHandler, xpath, "field-key");
            return null;
        }
        FieldKey fieldKey;
        try {
            fieldKey = FieldKey.parse(fieldKeyString);
        } catch (ParseException pe) {
            DomMessages.wrongAttributeValue(messageHandler, xpath, "field-key", fieldKeyString);
            return null;
        }
        FieldUiBuilder fieldUiBuilder = FieldUiBuilder.init(fieldKey);
        DOMUtils.readChildren(element, new FieldUiConsumer(fieldKey, fieldUiBuilder, messageHandler));
        return fieldUiBuilder.toFieldUi();
    }

    public static IncludeUi readSubsetIncludeUi(Element element_xml, MessageHandler messageHandler, String xpath) {
        boolean master = false;
        SubsetKey subsetKey = null;
        String mode = "";
        int weight = -1;
        NodeList liste = element_xml.getChildNodes();
        for (int i = 0; i < liste.getLength(); i++) {
            if (liste.item(i).getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) liste.item(i);
                String tagName = element.getTagName();
                switch (tagName) {
                    case "master":
                        master = true;
                        break;
                    case "subset":
                        String subsetKeyString = XMLUtils.getData(element);
                        if (subsetKeyString.length() == 0) {
                            DomMessages.emptyElement(messageHandler, tagName);
                            return null;
                        }
                        try {
                            subsetKey = SubsetKey.parse(subsetKeyString);
                        } catch (java.text.ParseException pe) {
                            DomMessages.wrongElementValue(messageHandler, tagName, subsetKeyString);
                            return null;
                        }
                        break;
                    case "weight":
                    case "poids":
                        String weightString = XMLUtils.getData(element);
                        if (weightString.length() == 0) {
                            DomMessages.emptyElement(messageHandler, tagName);
                            return null;
                        }
                        try {
                            weight = Integer.parseInt(weightString);
                        } catch (NumberFormatException nfe) {
                            DomMessages.wrongElementValue(messageHandler, tagName, weightString);
                            return null;
                        }
                        break;
                    case "mode":
                        mode = XMLUtils.getData(element);
                        if (mode.length() > 0) {
                            try {
                                StringUtils.checkTechnicalName(mode, false);
                            } catch (ParseException pe) {
                                DomMessages.wrongElementValue(messageHandler, tagName, mode);
                                return null;
                            }
                        }
                        break;

                }
            }
        }
        if (subsetKey == null) {
            DomMessages.emptyElement(messageHandler, xpath);
            return null;
        }
        try {
            ExtendedIncludeKey extendedIncludeKey = ExtendedIncludeKey.newInstance(IncludeKey.newInstance(subsetKey, mode, weight), master);
            IncludeUiBuilder includeUiBuilder = IncludeUiBuilder.initSubset(extendedIncludeKey);
            DOMUtils.readChildren(element_xml, new IncludeUiConsumer(includeUiBuilder, messageHandler));
            return includeUiBuilder.toIncludeUi();
        } catch (IllegalArgumentException iae) {
            DomMessages.wrongElementValue(messageHandler, xpath, subsetKey.getKeyString());
            return null;
        }
    }

    public static IncludeUi readSpecialIncludeUi(Element element, MessageHandler messageHandler, String xpath) {
        String name = element.getAttribute("name");
        if (name.length() == 0) {
            name = element.getAttribute("include-key");
        }
        if (name.length() == 0) {
            DomMessages.emptyAttribute(messageHandler, xpath, "name");
            return null;
        }
        IncludeUiBuilder includeUiBuilder;
        if (name.equals("association")) {
            ExtendedIncludeKey extendedIncludeKey = ExtendedIncludeKey.build("association");
            includeUiBuilder = IncludeUiBuilder.initSubset(extendedIncludeKey);
        } else {
            try {
                name = FichothequeConstants.checkSpecialIncludeName(name);
                includeUiBuilder = IncludeUiBuilder.initSpecial(name);
            } catch (IllegalArgumentException iae) {
                DomMessages.wrongAttributeValue(messageHandler, xpath, "name", name);
                return null;
            }
        }
        DOMUtils.readChildren(element, new IncludeUiConsumer(includeUiBuilder, messageHandler));
        return includeUiBuilder.toIncludeUi();
    }


    private static abstract class AbstractUiConsumer implements Consumer<Element> {

        private final UiComponentBuilder uiComponentBuilder;
        protected final MessageHandler messageHandler;

        protected AbstractUiConsumer(UiComponentBuilder uiComponentBuilder, MessageHandler messageHandler) {
            this.uiComponentBuilder = uiComponentBuilder;
            this.messageHandler = messageHandler;
        }

        protected void readStatus(Element element) {
            String status = DOMUtils.readSimpleElement(element);
            try {
                uiComponentBuilder.setStatus(status);
            } catch (IllegalArgumentException iae) {

            }
        }

        protected void readOption(Element element) {
            String name = element.getAttribute("name");
            if (name.length() > 0) {
                Object obj = readUiOption(element);
                if (obj != null) {
                    uiComponentBuilder.putOption(name, obj);
                }
            }
        }

        protected void readOldValue(Element element) {
            if (StringUtils.isTrue(element.getAttribute("mandatory"))) {
                uiComponentBuilder.setStatus(BdfServerConstants.MANDATORY_STATUS);
            }
            String oldValue = readOldDefaultValue(element);
            if (oldValue != null) {
                uiComponentBuilder.putOption(BdfServerConstants.DEFAULTVALUE_OPTION, oldValue);
            }
        }

        protected void readOldInput(Element element) {
            String xpath = "input";
            String inputType = element.getAttribute("type");
            if ((!inputType.isEmpty()) && (!inputType.equals(BdfServerConstants.INPUT_TEXT))) {
                uiComponentBuilder.putOption(BdfServerConstants.INPUTTYPE_OPTION, inputType);
            }
            String inputWidth = element.getAttribute("width");
            if (inputWidth.length() == 0) {
                inputWidth = element.getAttribute("size"); //Ancien nom de l'attribut
            }
            if (inputWidth.length() > 0) {
                try {
                    BdfServerConstants.checkWidth(inputWidth);
                    uiComponentBuilder.putOption(BdfServerConstants.INPUTWIDTH_OPTION, inputWidth);
                } catch (IllegalArgumentException iae) {
                    DomMessages.wrongAttributeValue(messageHandler, xpath, "width", inputWidth);
                }
            }
            String rowsString = element.getAttribute("rows");
            if (rowsString.length() > 0) {
                try {
                    int row = Integer.parseInt(rowsString);
                    uiComponentBuilder.putOption(BdfServerConstants.INPUTROWS_OPTION, String.valueOf(row));
                } catch (NumberFormatException nfe) {
                    DomMessages.wrongIntegerAttributeValue(messageHandler, xpath, "rows", rowsString);
                } catch (IllegalArgumentException iae) {
                    DomMessages.wrongAttributeValue(messageHandler, xpath, "rows", rowsString);
                }
            }
        }

        private Object readUiOption(Element element) {
            String value = element.getAttribute("value");
            if (value.length() > 0) {
                return value;
            }
            UiOptionConsumer uiOptionConsumer = new UiOptionConsumer(messageHandler);
            DOMUtils.readChildren(element, uiOptionConsumer);
            if (uiOptionConsumer.raw != null) {
                return uiOptionConsumer.raw;
            }
            return StringUtils.toMultiStringable(uiOptionConsumer.valueList);
        }

        private String readOldDefaultValue(Element element) {
            String defaultValue = element.getAttribute("default");
            if (defaultValue.length() != 0) {
                return defaultValue;
            }
            OldValueConsumer oldValueConsumer = new OldValueConsumer(messageHandler);
            DOMUtils.readChildren(element, oldValueConsumer);
            return oldValueConsumer.defaultValue;
        }


    }


    private static class FieldUiConsumer extends AbstractUiConsumer {

        private final FieldKey fieldKey;
        private final FieldUiBuilder fieldUiBuilder;

        private FieldUiConsumer(FieldKey fieldKey, FieldUiBuilder fieldUiBuilder, MessageHandler messageHandler) {
            super(fieldUiBuilder, messageHandler);
            this.fieldKey = fieldKey;
            this.fieldUiBuilder = fieldUiBuilder;
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            switch (tagName) {
                case "status":
                    readStatus(element);
                    break;
                case "input": {
                    if (!fieldKey.equals(FieldKey.LANG)) {
                        readOldInput(element);
                    }
                    break;
                }
                case "value": {
                    readOldValue(element);
                    break;
                }
                case "attr": {
                    AttributeUtils.readAttrElement(fieldUiBuilder.getAttributeChangeBuilder(), element);
                    break;
                }
                case "option": {
                    readOption(element);
                    break;
                }
            }
        }

    }


    private static class IncludeUiConsumer extends AbstractUiConsumer {

        private final IncludeUiBuilder includeUiBuilder;

        private IncludeUiConsumer(IncludeUiBuilder includeUiBuilder, MessageHandler messageHandler) {
            super(includeUiBuilder, messageHandler);
            this.includeUiBuilder = includeUiBuilder;
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            switch (tagName) {
                case "status":
                    readStatus(element);
                    break;
                case "custom": {
                    DOMUtils.readChildren(element, new CustomConsumer(includeUiBuilder, messageHandler));
                    break;
                }
                case "input": {
                    readOldInput(element);
                    break;
                }
                case "value": {
                    readOldValue(element);
                    break;
                }
                case "limit": {
                    if (StringUtils.isTrue(element.getAttribute("nonew"))) {
                        includeUiBuilder.setStatus(BdfServerConstants.OBSOLETE_STATUS);
                    }
                    break;
                }
                case "attr": {
                    AttributeUtils.readAttrElement(includeUiBuilder.getAttributeChangeBuilder(), element);
                    break;
                }
                case "option": {
                    readOption(element);
                    break;
                }
            }
        }

    }


    private static class CommentUiConsumer implements Consumer<Element> {

        private final CommentUiBuilder commentUiBuilder;
        private final MessageHandler messageHandler;

        private CommentUiConsumer(CommentUiBuilder commentUiBuilder, MessageHandler messageHandler) {
            this.commentUiBuilder = commentUiBuilder;
            this.messageHandler = messageHandler;
        }

        @Override
        public void accept(Element element
        ) {
            String tagName = element.getTagName();
            switch (tagName) {
                case "html":
                case "lib": {
                    String langString = element.getAttribute("xml:lang");
                    if (langString.length() > 0) {
                        try {
                            Lang lang = Lang.parse(langString);
                            String htmlString = XMLUtils.getRawData(element);
                            commentUiBuilder.putHtml(lang, htmlString);
                        } catch (ParseException pe) {
                            DomMessages.wrongLangAttribute(messageHandler, tagName, langString);
                        }
                    } else {
                        DomMessages.emptyAttribute(messageHandler, tagName, "xml:lang");
                    }
                    break;
                }
                case "attr": {
                    AttributeUtils.readAttrElement(commentUiBuilder.getAttributeChangeBuilder(), element);
                    break;
                }
                default:
                    DomMessages.unknownTagWarning(messageHandler, tagName);
            }
        }

    }


    private static class DataUiConsumer implements Consumer<Element> {

        private final DataUiBuilder dataUiBuilder;
        private final MessageHandler messageHandler;

        private DataUiConsumer(DataUiBuilder dataUiBuilder, MessageHandler messageHandler) {
            this.dataUiBuilder = dataUiBuilder;
            this.messageHandler = messageHandler;
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            switch (tagName) {
                case "param": {
                    String name = element.getAttribute("name");
                    if (name.length() > 0) {
                        String value = DOMUtils.readSimpleElement(element);
                        dataUiBuilder.getExternalSourceDefBuilder().addParam(name, value);
                    }
                    break;
                }
                case "label": {
                    try {
                        Label label = LabelUtils.readLabel(element);
                        if (label != null) {
                            dataUiBuilder.getLabelChangeBuilder().putLabel(label);
                        }
                    } catch (ParseException ile) {
                        DomMessages.wrongLangAttribute(messageHandler, tagName, element.getAttribute("xml:lang"));
                    }
                    break;
                }
                case "attr": {
                    AttributeUtils.readAttrElement(dataUiBuilder.getAttributeChangeBuilder(), element);
                    break;
                }
                default:
                    DomMessages.unknownTagWarning(messageHandler, tagName);
            }
        }

    }


    private static class CustomConsumer implements Consumer<Element> {

        private final IncludeUiBuilder includeUiBuilder;
        private final MessageHandler messageHandler;


        private CustomConsumer(IncludeUiBuilder includeUiBuilder, MessageHandler messageHandler) {
            this.includeUiBuilder = includeUiBuilder;
            this.messageHandler = messageHandler;
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            switch (tagName) {
                case "label":
                case "lib": {
                    try {
                        Label label = LabelUtils.readLabel(element);
                        if (label != null) {
                            includeUiBuilder.getLabelChangeBuilder().putLabel(label);
                        }
                    } catch (ParseException ile) {
                        DomMessages.wrongLangAttribute(messageHandler, tagName, element.getAttribute("xml:lang"));
                    }
                    break;
                }
                default:
                    DomMessages.unknownTagWarning(messageHandler, tagName);
            }
        }

    }


    private static class OldValueConsumer implements Consumer<Element> {

        private final MessageHandler messageHandler;
        private String defaultValue = null;


        private OldValueConsumer(MessageHandler messageHandler) {
            this.messageHandler = messageHandler;
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            switch (tagName) {
                case "default":
                    defaultValue = XMLUtils.getRawData(element);
                    break;
                default:
                    DomMessages.unknownTagWarning(messageHandler, tagName);
            }
        }

    }


    private static class UiOptionConsumer implements Consumer<Element> {

        private final MessageHandler messageHandler;
        private String raw = null;
        private List<String> valueList = new ArrayList<String>();


        private UiOptionConsumer(MessageHandler messageHandler) {
            this.messageHandler = messageHandler;
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            switch (tagName) {
                case "raw":
                    raw = XMLUtils.getRawData(element);
                    break;
                case "value":
                    String value = DOMUtils.readSimpleElement(element);
                    if (value.length() > 0) {
                        valueList.add(value);
                    }
                    break;
                default:
                    DomMessages.unknownTagWarning(messageHandler, tagName);

            }
        }

    }

}
