/* BdfServer - Copyright (c) 2018-2023 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.json;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.subsettree.GroupNode;
import fr.exemole.bdfserver.api.subsettree.SubsetNode;
import fr.exemole.bdfserver.api.subsettree.SubsetTree;
import fr.exemole.bdfserver.api.ui.FieldUi;
import fr.exemole.bdfserver.api.ui.IncludeUi;
import fr.exemole.bdfserver.api.ui.SpecialIncludeUi;
import fr.exemole.bdfserver.api.ui.SubsetIncludeUi;
import fr.exemole.bdfserver.api.ui.UiComponent;
import fr.exemole.bdfserver.api.ui.UiComponents;
import fr.exemole.bdfserver.tools.L10nUtils;
import fr.exemole.bdfserver.tools.subsettree.TreeFilterEngine;
import fr.exemole.bdfserver.tools.subsettree.TreeUtils;
import java.io.IOException;
import java.util.List;
import net.fichotheque.Fichotheque;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.metadata.CorpusField;
import net.fichotheque.corpus.metadata.CorpusMetadata;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.permission.PermissionSummary;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.thesaurus.metadata.ThesaurusFieldKey;
import net.fichotheque.thesaurus.metadata.ThesaurusMetadata;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.ThesaurusUtils;
import net.mapeadores.util.exceptions.SwitchException;
import net.mapeadores.util.json.JSONWriter;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.MessageLocalisation;
import net.mapeadores.util.text.Label;


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

    private SubsetTreeJson() {

    }

    public static void properties(JSONWriter jw, Parameters parameters, short subsetCategory) throws IOException {
        SubsetTree subsetTree = parameters.getBdfServer().getTreeManager().getSubsetTree(subsetCategory);
        PermissionSummary permissionSummary = parameters.getPermissionSummary();
        if (permissionSummary != null) {
            subsetTree = TreeFilterEngine.read(permissionSummary, subsetTree);
        }
        jw.key(SubsetKey.categoryToString(subsetCategory));
        nodeArray(jw, parameters, subsetTree);
    }

    public static void nodeArray(JSONWriter jw, Parameters parameters, SubsetTree subsetTree) throws IOException {
        jw.array();
        for (SubsetTree.Node node : subsetTree.getNodeList()) {
            if (node instanceof SubsetNode) {
                writeSubsetNode(jw, parameters, (SubsetNode) node);
            } else if (node instanceof GroupNode) {
                writeGroupNode(jw, parameters, (GroupNode) node);
            }
        }
        jw.endArray();
    }

    public static Parameters initParameters(BdfServer bdfServer, Lang lang, MessageLocalisation messageLocalisation) {
        return new Parameters(bdfServer, lang, messageLocalisation);
    }


    private static void writeGroupNode(JSONWriter jw, Parameters parameters, GroupNode groupNode) throws IOException {
        jw.object();
        {
            jw.key("node")
                    .value("group");
            jw.key("name")
                    .value(groupNode.getName());
            jw.key("title")
                    .value(TreeUtils.getTitle(parameters.getBdfServer(), groupNode, parameters.getLang()));
            jw.key("array");
            jw.array();
            for (SubsetTree.Node subnode : groupNode.getSubnodeList()) {
                if (subnode instanceof SubsetNode) {
                    writeSubsetNode(jw, parameters, (SubsetNode) subnode);
                } else if (subnode instanceof GroupNode) {
                    writeGroupNode(jw, parameters, (GroupNode) subnode);
                }
            }
            jw.endArray();
        }
        jw.endObject();
    }

    private static void writeSubsetNode(JSONWriter jw, Parameters parameters, SubsetNode subsetNode) throws IOException {
        Fichotheque fichotheque = parameters.getBdfServer().getFichotheque();
        SubsetKey subsetKey = subsetNode.getSubsetKey();
        Subset subset = fichotheque.getSubset(subsetKey);
        if (subset == null) {
            return;
        }
        jw.object();
        {
            jw.key("node")
                    .value("subset");
            jw.key("key")
                    .value(subsetKey.getKeyString());
            jw.key("name")
                    .value(subsetKey.getSubsetName());
            Label titleLabel = FichothequeUtils.getTitleLabel(subset, parameters.getLang());
            if (titleLabel != null) {
                jw.key("title")
                        .value(titleLabel.getLabelString());
            }
            if (subset instanceof Corpus) {
                Subset masterSubset = ((Corpus) subset).getMasterSubset();
                if (masterSubset != null) {
                    SubsetKey masterSubsetKey = masterSubset.getSubsetKey();
                    jw.key("master");
                    jw.object();
                    {
                        jw.key("category")
                                .value(masterSubsetKey.getCategoryString());
                        jw.key("name")
                                .value(masterSubsetKey.getSubsetName());
                        if (masterSubset instanceof Thesaurus) {
                            if (((Thesaurus) masterSubset).isIdalphaType()) {
                                jw.key("type")
                                        .value("idalpha");
                            }
                        }
                    }
                    jw.endObject();
                }
            }
            if (parameters.isWithDetails()) {
                jw.key("details");
                jw.object();
                if (subset instanceof Corpus) {
                    writeCorpusDetails(jw, parameters, (Corpus) subset);
                }
                if (subset instanceof Thesaurus) {
                    writeThesaurusDetails(jw, parameters, (Thesaurus) subset);
                }
                jw.endObject();
            }
        }
        jw.endObject();
    }

    private static void writeCorpusDetails(JSONWriter jw, Parameters parameters, Corpus corpus) throws IOException {
        BdfServer bdfServer = parameters.getBdfServer();
        Lang lang = parameters.getLang();
        CorpusMetadata corpusMetadata = corpus.getCorpusMetadata();
        jw.key("ui");
        jw.array();
        UiComponents uiComponents = bdfServer.getUiManager().getMainUiComponents(corpus);
        boolean dateCreationDone = false;
        boolean dateModificationDone = false;
        for (UiComponent uiComponent : uiComponents.getUiComponentList()) {
            if (uiComponent instanceof FieldUi) {
                FieldUi fieldUi = (FieldUi) uiComponent;
                CorpusField corpusField = corpusMetadata.getCorpusField(fieldUi.getFieldKey());
                if (corpusField != null) {
                    writeCorpusField(jw, parameters, corpusField);
                }
            } else if (uiComponent instanceof IncludeUi) {
                IncludeUi includeUi = (IncludeUi) uiComponent;
                String label = L10nUtils.getIncludeTitle(bdfServer, includeUi, lang);
                if (label == null) {
                    label = includeUi.getName();
                }
                writeCorpusInclude(jw, parameters, includeUi, label);
                switch (includeUi.getName()) {
                    case FichothequeConstants.DATECREATION_NAME:
                        dateCreationDone = true;
                        break;
                    case FichothequeConstants.DATEMODIFICATION_NAME:
                        dateModificationDone = true;
                        break;
                }
            }
        }
        jw.endArray();
        jw.key("misc");
        jw.array();
        CorpusField idCorpusField = corpusMetadata.getCorpusField(FieldKey.ID);
        writeCorpusField(jw, parameters, idCorpusField);
        if (!dateCreationDone) {
            writeCorpusInclude(jw, parameters, FichothequeConstants.DATECREATION_NAME);
        }
        if (!dateModificationDone) {
            writeCorpusInclude(jw, parameters, FichothequeConstants.DATEMODIFICATION_NAME);
        }
        jw.endArray();
        Subset masterSubset = corpus.getMasterSubset();
        List<Corpus> satelliteCorpusList = corpus.getSatelliteCorpusList();
        if ((masterSubset != null) || (!satelliteCorpusList.isEmpty())) {
            jw.key("parentage");
            jw.array();
            if (masterSubset != null) {
                writeParentageDetail(jw, parameters, masterSubset);
                satelliteCorpusList = masterSubset.getSatelliteCorpusList();
            }
            for (Corpus other : satelliteCorpusList) {
                if (!other.equals(corpus)) {
                    writeParentageDetail(jw, parameters, other);
                }
            }
            jw.endArray();
        }
    }

    private static void writeThesaurusDetails(JSONWriter jw, Parameters parameters, Thesaurus thesaurus) throws IOException {
        BdfServer bdfServer = parameters.getBdfServer();
        Lang lang = parameters.getLang();
        List<ThesaurusFieldKey> coreList = ThesaurusUtils.computeCoreFieldList(thesaurus, bdfServer.getThesaurusLangChecker());
        List<Corpus> satelliteCorpusList = thesaurus.getSatelliteCorpusList();
        jw.key("core");
        jw.array();
        for (ThesaurusFieldKey thesaurusFieldKey : coreList) {
            writeThesaurusFieldKey(jw, parameters, thesaurus, thesaurusFieldKey);
        }
        jw.endArray();
        jw.key("supp");
        jw.array();
        {
            writeThesaurusFieldKey(jw, parameters, thesaurus, ThesaurusFieldKey.LEVEL);
            writeThesaurusFieldKey(jw, parameters, thesaurus, ThesaurusFieldKey.POSITION_LOCAL);
            writeThesaurusFieldKey(jw, parameters, thesaurus, ThesaurusFieldKey.POSITION_GLOBAL);
            writeThesaurusFieldKey(jw, parameters, thesaurus, ThesaurusFieldKey.THIS);
            if (!satelliteCorpusList.isEmpty()) {
                String title = L10nUtils.getSpecialIncludeTitle(bdfServer, FichothequeConstants.PARENTAGE_NAME, lang);
                if (title == null) {
                    title = FichothequeConstants.PARENTAGE_NAME;
                }
                jw.object();
                {
                    jw.key("key")
                            .value(FichothequeConstants.PARENTAGE_NAME);
                    jw.key("patternType")
                            .value("fiche");
                    jw.key("title")
                            .value(title);
                }
                jw.endObject();
            }
        }
        jw.endArray();
        if (!satelliteCorpusList.isEmpty()) {
            jw.key("parentage");
            jw.array();
            for (Corpus corpus : satelliteCorpusList) {
                writeParentageDetail(jw, parameters, corpus);
            }
            jw.endArray();
        }
    }

    /*
    * Les champs de thésaurus n'ont que {value} comme formatage disponible,
    * sauf pour this qui a le même formatage qu'un mot-clé inclus
     */
    private static void writeThesaurusFieldKey(JSONWriter jw, Parameters parameters, Thesaurus thesaurus, ThesaurusFieldKey thesaurusFieldKey) throws IOException {
        String patternType;
        if (thesaurusFieldKey.equals(ThesaurusFieldKey.THIS)) {
            patternType = getThesaurusPatternType(parameters, thesaurus.getSubsetKey());
        } else if ((thesaurusFieldKey.equals(ThesaurusFieldKey.IDALPHA)) || (thesaurusFieldKey.equals(ThesaurusFieldKey.PARENT_IDALPHA))) {
            patternType = "idalpha";
        } else {
            patternType = "value";
        }
        Lang lang = parameters.getLang();
        MessageLocalisation messageLocalisation = parameters.getMessageLocalisation();
        jw.object();
        {
            jw.key("key")
                    .value(thesaurusFieldKey.toString());
            jw.key("patternType")
                    .value(patternType);
            jw.key("title");
            String messageKey = L10nUtils.getMessageKey(thesaurusFieldKey);
            Object value = null;
            if (thesaurusFieldKey.isLabelThesaurusFieldKey()) {
                value = messageLocalisation.toString(lang.toString());
            }
            String labelString;
            if (value == null) {
                labelString = messageLocalisation.toString(messageKey);
            } else {
                labelString = messageLocalisation.toString(messageKey, value);
            }
            if (labelString != null) {
                jw.value(labelString);
            } else {
                jw.value("");
            }
        }
        jw.endObject();
    }

    private static void writeCorpusField(JSONWriter jw, Parameters parameters, CorpusField corpusField) throws IOException {
        jw.object();
        CorpusFieldJson.properties(jw, corpusField, parameters.getLang());
        jw.endObject();
    }

    private static void writeCorpusInclude(JSONWriter jw, Parameters parameters, String specialIncludeName) throws IOException {
        String patternType = getSpecialPatternType(specialIncludeName);
        String title = L10nUtils.getSpecialIncludeTitle(parameters.getBdfServer(), specialIncludeName, parameters.getLang());
        if (title == null) {
            title = specialIncludeName;
        }
        includeObject(jw, specialIncludeName, patternType, title);
    }

    private static void writeCorpusInclude(JSONWriter jw, Parameters parameters, IncludeUi includeUi, String title) throws IOException {
        String patternType;
        if (includeUi instanceof SpecialIncludeUi) {
            patternType = getSpecialPatternType(includeUi.getName());
        } else if (includeUi instanceof SubsetIncludeUi) {
            patternType = getSubsetPatternType(parameters, ((SubsetIncludeUi) includeUi).getSubsetKey());
        } else {
            patternType = "";
        }
        includeObject(jw, includeUi.getName(), patternType, title);
    }

    private static void includeObject(JSONWriter jw, String name, String patternType, String title) throws IOException {
        jw.object();
        {
            jw.key("key")
                    .value(name);
            jw.key("patternType")
                    .value(patternType);
            jw.key("title")
                    .value(title);
        }
        jw.endObject();
    }

    private static String getSubsetPatternType(Parameters parameters, SubsetKey subsetKey) {
        switch (subsetKey.getCategory()) {
            case SubsetKey.CATEGORY_CORPUS:
                return "fiche";
            case SubsetKey.CATEGORY_THESAURUS:
                return getThesaurusPatternType(parameters, subsetKey);
            case SubsetKey.CATEGORY_ALBUM:
                return "illustration";
            case SubsetKey.CATEGORY_ADDENDA:
                return "document";
            default:
                throw new SwitchException("Unknown category: " + subsetKey.getCategory());
        }
    }

    private static String getSpecialPatternType(String specialIncludeName) {
        switch (specialIncludeName) {
            case FichothequeConstants.LIAGE_NAME:
            case FichothequeConstants.PARENTAGE_NAME:
                return "fiche";
            case FichothequeConstants.DATECREATION_NAME:
            case FichothequeConstants.DATEMODIFICATION_NAME:
                return "date";
            default:
                throw new SwitchException("Unknown include name: " + specialIncludeName);

        }
    }

    private static String getThesaurusPatternType(Parameters parameters, SubsetKey subsetKey) {
        Thesaurus thesaurus = (Thesaurus) parameters.getBdfServer().getFichotheque().getSubset(subsetKey);
        if (thesaurus != null) {
            switch (thesaurus.getThesaurusMetadata().getThesaurusType()) {
                case ThesaurusMetadata.BABELIEN_TYPE:
                    return "motcle_babelien";
                case ThesaurusMetadata.IDALPHA_TYPE:
                    return "motcle_idalpha";
                case ThesaurusMetadata.MULTI_TYPE:
                    return "motcle_multi";
            }
        }
        return "motcle_idalpha";
    }

    private static void writeParentageDetail(JSONWriter jw, Parameters parameters, Subset subset) throws IOException {
        Lang lang = parameters.getLang();
        SubsetKey subsetKey = subset.getSubsetKey();
        jw.object();
        if (subsetKey.isCorpusSubset()) {
            jw.key("key")
                    .value("fiche_" + subsetKey.getSubsetName());
            jw.key("patternType")
                    .value("fiche");
            jw.key("title")
                    .value(FichothequeUtils.getTitle((Corpus) subset, lang));
        } else if (subsetKey.isThesaurusSubset()) {
            jw.key("key")
                    .value("motcle_" + subsetKey.getSubsetName());
            jw.key("patternType")
                    .value(getThesaurusPatternType(parameters, subsetKey));
            jw.key("title")
                    .value(FichothequeUtils.getTitle((Thesaurus) subset, lang));
        }
        jw.endObject();
    }


    public static class Parameters {

        private final BdfServer bdfServer;
        private final Lang lang;
        private final MessageLocalisation messageLocalisation;
        private boolean withDetails;
        private PermissionSummary permissionSummary;

        public Parameters(BdfServer bdfServer, Lang lang, MessageLocalisation messageLocalisation) {
            this.bdfServer = bdfServer;
            this.lang = lang;
            this.messageLocalisation = messageLocalisation;
        }

        public BdfServer getBdfServer() {
            return bdfServer;
        }

        public Lang getLang() {
            return lang;
        }

        public MessageLocalisation getMessageLocalisation() {
            return messageLocalisation;
        }

        public boolean isWithDetails() {
            return withDetails;
        }

        public Parameters setWithDetails(boolean withDetails) {
            this.withDetails = withDetails;
            return this;
        }

        public PermissionSummary getPermissionSummary() {
            return permissionSummary;
        }

        public Parameters setWithPermissionSummary(PermissionSummary permissionSummary) {
            this.permissionSummary = permissionSummary;
            return this;
        }

    }

}
