/* FichothequeLib_Tools - Copyright (c) 2017-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.corpus;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.fiche.DateItem;
import net.fichotheque.corpus.fiche.FicheItem;
import net.fichotheque.corpus.fiche.FicheItems;
import net.fichotheque.corpus.fiche.LanguageItem;
import net.fichotheque.junction.Liaison;
import net.fichotheque.include.IncludeKey;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.utils.JunctionUtils;
import net.fichotheque.utils.FicheUtils;
import net.mapeadores.util.localisation.Lang;
import net.fichotheque.junction.Junctions;


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

    private final static Lang UND = Lang.build("und");
    private final Fichotheque fichotheque;
    private final Map<IncludeKey, CorpusInfo> corpusInfoMap = new LinkedHashMap<IncludeKey, CorpusInfo>();

    public FicheDistributionEngine(Fichotheque fichotheque) {
        this.fichotheque = fichotheque;
    }

    public boolean isEmpty() {
        return corpusInfoMap.isEmpty();
    }

    public boolean addCorpus(IncludeKey corpusIncludeKey, String fixed, GroupingKey groupingFieldKey) {
        SubsetKey subsetKey = corpusIncludeKey.getSubsetKey();
        if (!subsetKey.isCorpusSubset()) {
            return false;
        }
        Corpus corpus = (Corpus) fichotheque.getSubset(subsetKey);
        if (corpus == null) {
            return false;
        }
        corpusInfoMap.put(corpusIncludeKey, new CorpusInfo(corpusIncludeKey, corpus, fixed, groupingFieldKey));
        return true;
    }

    public boolean addCorpus(IncludeKey corpusIncludeKey, IncludeKey thesaurusIncludeKey, GroupingKey groupingFieldKey) {
        SubsetKey corpusKey = corpusIncludeKey.getSubsetKey();
        if (!corpusKey.isCorpusSubset()) {
            return false;
        }
        Corpus corpus = (Corpus) fichotheque.getSubset(corpusKey);
        if (corpus == null) {
            return false;
        }
        SubsetKey thesaurusKey = thesaurusIncludeKey.getSubsetKey();
        if (!thesaurusKey.isThesaurusSubset()) {
            return false;
        }
        Thesaurus thesaurus = (Thesaurus) fichotheque.getSubset(thesaurusKey);
        if (thesaurus == null) {
            return false;
        }
        ThesaurusInfo thesaurusInfo = new ThesaurusInfo(thesaurusIncludeKey, thesaurus);
        corpusInfoMap.put(corpusIncludeKey, new CorpusInfo(corpusIncludeKey, corpus, thesaurusInfo, groupingFieldKey));
        return true;
    }

    public FicheDistribution run(SubsetItem subsetItem, Predicate<SubsetItem> predicate) {
        FicheDistribution ficheDistribution = new FicheDistribution();
        for (CorpusInfo corpusInfo : corpusInfoMap.values()) {
            Junctions junctions = fichotheque.getJunctions(subsetItem, corpusInfo.getCorpus());
            IncludeKey includeKey = corpusInfo.getIncludeKey();
            Collection<Liaison> liaisons = JunctionUtils.filter(junctions, includeKey, predicate);
            if (corpusInfo.getFixed() != null) {
                addFixed(ficheDistribution, liaisons, corpusInfo.getFixed(), corpusInfo.getGroupingKey());
            } else {
                addThesaurus(ficheDistribution, liaisons, corpusInfo.getThesaurusInfo(), corpusInfo.getGroupingKey());
            }
        }
        return ficheDistribution;
    }

    private void addFixed(FicheDistribution ficheDistribution, Collection<Liaison> liaisons, String fixedEntryName, GroupingKey groupingKey) {
        for (Liaison liaison : liaisons) {
            FicheMeta ficheMeta = (FicheMeta) liaison.getSubsetItem();
            Object date = getGroupingObject(ficheMeta, groupingKey);
            ficheDistribution.addFiche(fixedEntryName, ficheMeta, date);
        }
    }

    private void addThesaurus(FicheDistribution ficheDistribution, Collection<Liaison> liaisons, ThesaurusInfo thesaurusInfo, GroupingKey groupingKey) {
        Thesaurus thesaurus = thesaurusInfo.getThesaurus();
        IncludeKey includeKey = thesaurusInfo.getIncludeKey();
        for (Liaison liaison : liaisons) {
            FicheMeta ficheMeta = (FicheMeta) liaison.getSubsetItem();
            Object groupingObject = getGroupingObject(ficheMeta, groupingKey);
            Junctions junctions = fichotheque.getJunctions(ficheMeta, thesaurus);
            Collection<Liaison> motcleLiaisons = JunctionUtils.filter(junctions, includeKey);
            if (motcleLiaisons.isEmpty()) {
                ficheDistribution.addFiche("", ficheMeta, groupingObject);
            } else {
                for (Liaison motcleLiaison : motcleLiaisons) {
                    Motcle motcle = (Motcle) motcleLiaison.getSubsetItem();
                    String idalpha = motcle.getIdalpha();
                    if (idalpha == null) {
                        idalpha = "_" + motcle.getId();
                    }
                    ficheDistribution.addFiche(idalpha, ficheMeta, groupingObject);
                }
            }
        }
    }

    private Object getGroupingObject(FicheMeta ficheMeta, GroupingKey groupingKey) {
        if (groupingKey.isCreationDate()) {
            return ficheMeta.getCreationDate();
        }
        if (groupingKey.isLang()) {
            Lang lang = ficheMeta.getLang();
            if (lang == null) {
                lang = UND;
            }
            return lang;
        }
        Object obj = FicheUtils.getValue(ficheMeta, groupingKey.getFieldKey());
        if (obj != null) {
            if (obj instanceof FicheItem) {
                return getGroupingObject((FicheItem) obj);
            } else if (obj instanceof FicheItems) {
                return getGroupingObject(((FicheItems) obj).get(0));
            }
        }
        return null;
    }

    private Object getGroupingObject(FicheItem ficheItem) {
        if (ficheItem instanceof DateItem) {
            return ((DateItem) ficheItem).getDate();
        } else if (ficheItem instanceof LanguageItem) {
            return ((LanguageItem) ficheItem).getLang();
        } else {
            return null;
        }
    }


    private class CorpusInfo {

        private final IncludeKey includeKey;
        private final Corpus corpus;
        private final String fixed;
        private final ThesaurusInfo thesaurusInfo;
        private final GroupingKey groupingKey;

        private CorpusInfo(IncludeKey includeKey, Corpus corpus, String fixed, GroupingKey groupingKey) {
            this.includeKey = includeKey;
            this.corpus = corpus;
            this.fixed = fixed;
            this.thesaurusInfo = null;
            this.groupingKey = groupingKey;
        }

        private CorpusInfo(IncludeKey includeKey, Corpus corpus, ThesaurusInfo thesaurusInfo, GroupingKey groupingKey) {
            this.includeKey = includeKey;
            this.corpus = corpus;
            this.fixed = null;
            this.thesaurusInfo = thesaurusInfo;
            this.groupingKey = groupingKey;
        }

        private IncludeKey getIncludeKey() {
            return includeKey;
        }

        private Corpus getCorpus() {
            return corpus;
        }

        private String getFixed() {
            return fixed;
        }

        private ThesaurusInfo getThesaurusInfo() {
            return thesaurusInfo;
        }

        private GroupingKey getGroupingKey() {
            return groupingKey;
        }

    }


    private class ThesaurusInfo {


        private final IncludeKey includeKey;
        private final Thesaurus thesaurus;

        private ThesaurusInfo(IncludeKey includeKey, Thesaurus thesaurus) {
            this.includeKey = includeKey;
            this.thesaurus = thesaurus;
        }

        private IncludeKey getIncludeKey() {
            return includeKey;
        }

        private Thesaurus getThesaurus() {
            return thesaurus;
        }


    }

}
