/* FichothequeLib_Xml - Copyright (c) 2006-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.xml.extraction;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.function.Predicate;
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.FicheMeta;
import net.fichotheque.croisement.Croisement;
import net.fichotheque.croisement.Croisements;
import net.fichotheque.externalsource.ExternalSourceDef;
import net.fichotheque.extraction.ExtractParameters;
import net.fichotheque.extraction.ExtractionContext;
import net.fichotheque.extraction.def.CorpusExtractDef;
import net.fichotheque.extraction.def.MotcleFilter;
import net.fichotheque.extraction.def.TagNameInfo;
import net.fichotheque.extraction.filterunit.CorpusExtractFilterUnit;
import net.fichotheque.extraction.filterunit.FicheParentageFilterUnit;
import net.fichotheque.extraction.filterunit.FilterUnit;
import net.fichotheque.extraction.run.CorpusExtractor;
import net.fichotheque.extraction.run.FicheExtractInfo;
import net.fichotheque.extraction.run.MotcleExtractInfo;
import net.fichotheque.namespaces.BdfSpace;
import net.fichotheque.namespaces.ExtractionSpace;
import net.fichotheque.pointeurs.MotclePointeur;
import net.fichotheque.selection.FicheSelector;
import net.fichotheque.selection.SelectionContext;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.MotcleIcon;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.thesaurus.policies.DynamicEditPolicy;
import net.fichotheque.utils.EligibilityUtils;
import net.fichotheque.utils.ThesaurusUtils;
import net.fichotheque.utils.pointeurs.PointeurFactory;
import net.fichotheque.utils.selection.FicheSelectorBuilder;
import net.fichotheque.utils.selection.SelectionContextBuilder;
import net.mapeadores.util.attr.Attribute;
import net.mapeadores.util.text.LabelUtils;
import net.mapeadores.util.xml.XMLPart;
import net.mapeadores.util.xml.XMLWriter;


/**
 *
 * @author Vincent Calame
 */
public class MotcleXMLPart extends XMLPart {

    private final static List<SatelliteInfo> EMPTY_SATELLITELIST = Collections.emptyList();
    private final FilterUnitXMLPart filterUnitPart;
    private final ExtractionContext extractionContext;
    private final int extractVersion;
    private final Predicate<FicheMeta> globalFichePredicate;
    private final TagNames tagNames;

    public MotcleXMLPart(XMLWriter xmlWriter, ExtractParameters extractParameters) {
        super(xmlWriter);
        this.extractionContext = extractParameters.getExtractionContext();
        this.extractVersion = extractParameters.getExtractVersion();
        Predicate<FicheMeta> fichePredicate = extractParameters.getFichePredicate();
        if (fichePredicate == null) {
            fichePredicate = EligibilityUtils.ALL_FICHE_PREDICATE;
        }
        this.globalFichePredicate = fichePredicate;
        this.filterUnitPart = new FilterUnitXMLPart(xmlWriter, extractParameters, globalFichePredicate);
        this.tagNames = new TagNames(extractVersion);
    }

    public void addMotcle(MotcleExtractInfo motcleExtractInfo) throws IOException {
        Motcle motcle = motcleExtractInfo.getMotcle();
        MotcleFilter motcleFilter = motcleExtractInfo.getMotcleFilter();
        String tagName = getTagName(motcleFilter);
        Croisement croisement = motcleExtractInfo.getCroisement();
        if (tagName != null) {
            startOpenTag(tagName);
            addAttribute("thesaurus", motcle.getSubsetName());
            addAttribute("id", String.valueOf(motcle.getId()));
            String idalpha = motcle.getIdalpha();
            if (idalpha != null) {
                addAttribute("idalpha", idalpha);
                if (motcle.getThesaurus().getThesaurusMetadata().isBracketsIdalphaStyle()) {
                    addAttribute("style", "brackets");
                }
            }
            int weight = ExtractionXMLUtils.getMainWeight(croisement);
            if (weight > 0) {
                addAttribute(tagNames.getWeightAttributeName(), String.valueOf(weight));
            }
            boolean externalTest = addExternalSourceAttributes(motcle);
            if (!externalTest) {
                Attribute urlAttribute = motcle.getAttributes().getAttribute(BdfSpace.URL_KEY);
                if (urlAttribute != null) {
                    addAttribute("url", urlAttribute.getFirstValue());
                }
            }
            List<SatelliteInfo> satelliteList = getSatelliteList(motcle);
            if ((motcleFilter.isNoneFiltering()) && (satelliteList.isEmpty())) {
                closeEmptyTag();
                return;
            }
            endOpenTag();
            addSatelliteList(satelliteList);
            ExtractionXMLUtils.writeCroisement(this, croisement, tagNames);
        } else if (motcleFilter.isNoneFiltering()) {
            return;
        }
        if (motcleFilter.withIcon()) {
            MotcleIcon motcleIcon = motcle.getMotcleIcon();
            if (motcleIcon != null) {
                addMotcleIcon(motcleIcon);
            }
        }
        if (motcleFilter.withLevel()) {
            addSimpleElement("level", String.valueOf(motcle.getLevel()));
        }
        if (motcleFilter.withLabels()) {
            LabelUtils.addLabels(this, motcle.getLabels());
        }
        if (motcleFilter.withFicheStylePhrase()) {
            ExtractionXMLUtils.writePhrase(this, motcle, FichothequeConstants.FICHESTYLE_PHRASE, extractionContext.getLangContext(), extractVersion);
        }
        MotcleFilter childrenFilter = motcleFilter.getChildrenFilter();
        if (childrenFilter != null) {
            for (Motcle child : motcle.getChildList()) {
                MotcleExtractInfo childExtractInfo = ExtractionXMLUtils.toMotcleExtractInfo(child, childrenFilter);
                addMotcle(childExtractInfo);
            }
        }
        MotcleFilter parentFilter = motcleFilter.getParentFilter();
        if (parentFilter != null) {
            Motcle parentMotcle = motcle.getParent();
            addLinkedMotcle(parentMotcle, motcleFilter.getParentFilter());
        }
        MotcleFilter previousFilter = motcleFilter.getPreviousFilter();
        if (previousFilter != null) {
            Motcle previousMotcle = ThesaurusUtils.getPrevious(motcle);
            addLinkedMotcle(previousMotcle, previousFilter);
        }
        MotcleFilter nextFilter = motcleFilter.getNextFilter();
        if (nextFilter != null) {
            Motcle nextMotcle = ThesaurusUtils.getNext(motcle);
            addLinkedMotcle(nextMotcle, nextFilter);
        }
        List<FilterUnit> filterUnitList = motcleFilter.getFilterUnitList();
        if (!filterUnitList.isEmpty()) {
            filterUnitPart.updatePointeur(motcle);
            filterUnitPart.addFilterUnitList(filterUnitList);
        }
        if (tagName != null) {
            closeTag(tagName);
        }
    }

    private void addMotcleIcon(MotcleIcon motcleIcon) throws IOException {
        startOpenTag("icon");
        addAttribute("char", motcleIcon.getCharIcon());
        closeEmptyTag();
    }

    private void addLinkedMotcle(Motcle linkedMotcle, MotcleFilter linkedMotcleFilter) throws IOException {
        if (linkedMotcle == null) {
            return;
        }
        addMotcle(ExtractionXMLUtils.toMotcleExtractInfo(linkedMotcle, linkedMotcleFilter));
    }


    private void addSatelliteList(List<SatelliteInfo> satelliteList) throws IOException {
        for (SatelliteInfo satelliteInfo : satelliteList) {
            String lang = satelliteInfo.lang;
            startOpenTag("satellite");
            addAttribute("corpus", satelliteInfo.corpusName);
            if (lang != null) {
                addAttribute("lang", lang);
            }
            closeEmptyTag();
        }
    }

    private List<SatelliteInfo> getSatelliteList(Motcle motcle) {
        Thesaurus thesaurus = motcle.getThesaurus();
        List<Corpus> satelliteCorpusList = thesaurus.getSatelliteCorpusList();
        if (satelliteCorpusList.isEmpty()) {
            return EMPTY_SATELLITELIST;
        }
        HashSet<String> existingString = new HashSet<String>();
        for (Corpus satelliteCorpus : satelliteCorpusList) {
            FicheMeta ficheMeta = satelliteCorpus.getFicheMetaById(motcle.getId());
            if ((ficheMeta != null) && (globalFichePredicate.test(ficheMeta))) {
                existingString.add(satelliteCorpus.getSubsetName());
            }
        }
        if (existingString.isEmpty()) {
            return EMPTY_SATELLITELIST;
        }
        Attribute satelliteAttribute = motcle.getAttributes().getAttribute(ExtractionSpace.SATELLITE_ATTRIBUTEKEY);
        if (satelliteAttribute != null) {
            if (satelliteAttribute.getFirstValue().startsWith("_")) {
                return EMPTY_SATELLITELIST;
            } else {
                for (String value : satelliteAttribute) {
                    if (existingString.contains(value)) {
                        return Collections.singletonList(new SatelliteInfo(value, null));
                    }
                }
                return EMPTY_SATELLITELIST;
            }
        }
        List<SatelliteInfo> resultList = new ArrayList<SatelliteInfo>();
        satelliteAttribute = thesaurus.getThesaurusMetadata().getAttributes().getAttribute(ExtractionSpace.SATELLITE_ATTRIBUTEKEY);
        if (satelliteAttribute != null) {
            for (String value : satelliteAttribute) {
                if (existingString.contains(value)) {
                    resultList.add(new SatelliteInfo(value, null));
                }
            }
        }
        for (Attribute attribute : thesaurus.getThesaurusMetadata().getAttributes()) {
            String attributeKey = attribute.getAttributeKey().toString();
            if (attributeKey.startsWith(ExtractionSpace.SATELLITE_ATTRIBUTEKEY_PREFIX)) {
                String lang = attributeKey.substring(ExtractionSpace.SATELLITE_ATTRIBUTEKEY_PREFIX.length());
                if (!lang.isEmpty()) {
                    for (String value : attribute) {
                        if (existingString.contains(value)) {
                            resultList.add(new SatelliteInfo(value, lang));
                        }
                    }
                }
            }
        }
        return resultList;
    }

    private String getTagName(MotcleFilter motcleFilter) {
        TagNameInfo tagNameInfo = motcleFilter.getTagNameInfo();
        switch (tagNameInfo.getType()) {
            case TagNameInfo.CUSTOM_TYPE:
                return tagNameInfo.getCustomTagName();
            case TagNameInfo.NULL_TYPE:
                return null;
            default:
                switch (motcleFilter.getType()) {
                    case MotcleFilter.PARENT_TYPE:
                        return "parent";
                    case MotcleFilter.NEXT_TYPE:
                        return "next";
                    case MotcleFilter.PREVIOUS_TYPE:
                        return "previous";
                    default:
                        return "motcle";
                }
        }
    }

    private boolean addExternalSourceAttributes(Motcle motcle) throws IOException {
        DynamicEditPolicy dynamicEditPolicy = extractionContext.getPolicyProvider().getDynamicEditPolicy(motcle.getThesaurus());
        if (!(dynamicEditPolicy instanceof DynamicEditPolicy.External)) {
            return false;
        }
        ExternalSourceDef externalSourceDef = ((DynamicEditPolicy.External) dynamicEditPolicy).getExternalSourceDef();
        String fichothequeName = externalSourceDef.getParam("fichotheque");
        if (fichothequeName == null) {
            return false;
        }
        String externalCorpusName = externalSourceDef.getParam("corpus");
        if (externalCorpusName == null) {
            return false;
        }
        addAttribute("external-fichotheque", fichothequeName);
        addAttribute("external-corpus", externalCorpusName);
        return true;
    }


    private static class SatelliteInfo {

        private final String corpusName;
        private final String lang;

        private SatelliteInfo(String corpusName, String lang) {
            this.corpusName = corpusName;
            this.lang = lang;
        }


    }


    private static class FilterUnitXMLPart extends XMLPart {

        private final ExtractParameters extractParameters;
        private final ExtractionContext extractionContext;
        private final SelectionContext selectionContext;
        private final Predicate<FicheMeta> globalFichePredicate;
        private CorpusExtractXMLPart corpusExtractXMLPart = null;
        private FicheXMLPart satelliteFicheXMLPart = null;
        private MotclePointeur motclePointeur;


        private FilterUnitXMLPart(XMLWriter xmlWriter, ExtractParameters extractParameters, Predicate<FicheMeta> globalFichePredicate) {
            super(xmlWriter);
            this.extractParameters = extractParameters;
            this.extractionContext = extractParameters.getExtractionContext();
            this.globalFichePredicate = globalFichePredicate;
            this.selectionContext = SelectionContextBuilder.build(extractionContext)
                    .setSubsetAccessPredicate(extractionContext.getSubsetAccessPredicate())
                    .setFichePredicate(extractParameters.getFichePredicate())
                    .toSelectionContext();
        }

        private void updatePointeur(Motcle motcle) {
            Thesaurus thesaurus = motcle.getThesaurus();
            if (motclePointeur != null) {
                if (!motclePointeur.getSubset().equals(thesaurus)) {
                    motclePointeur = null;
                }
            }
            if (motclePointeur == null) {
                motclePointeur = PointeurFactory.newMotclePointeur(motcle.getThesaurus());
            }
            motclePointeur.setCurrentSubsetItem(motcle);
        }

        private void addFilterUnitList(List<FilterUnit> filterUnitList) throws IOException {
            for (FilterUnit filterUnit : filterUnitList) {
                if (filterUnit instanceof CorpusExtractFilterUnit) {
                    addCorpusExtract((CorpusExtractFilterUnit) filterUnit);
                } else if (filterUnit instanceof FicheParentageFilterUnit) {
                    addSatelliteFiche((FicheParentageFilterUnit) filterUnit);
                }
            }
        }

        private void addSatelliteFiche(FicheParentageFilterUnit filterUnit) throws IOException {
            int motcleid = motclePointeur.getCurrentSubsetItem().getId();
            List<Corpus> corpusList = toCorpusList(filterUnit);
            for (Corpus corpus : corpusList) {
                FicheMeta ficheMeta = corpus.getFicheMetaById(motcleid);
                if ((ficheMeta != null) && (globalFichePredicate.test(ficheMeta))) {
                    FicheExtractInfo ficheExtractInfo = ExtractionXMLUtils.toFicheExtractInfo(ficheMeta, filterUnit.getFicheFilter());
                    if (satelliteFicheXMLPart == null) {
                        satelliteFicheXMLPart = new FicheXMLPart(getXMLWriter(), extractParameters);
                    }
                    satelliteFicheXMLPart.addFiche(ficheExtractInfo);
                }
            }
        }

        private void addCorpusExtract(CorpusExtractFilterUnit filterUnit) throws IOException {
            CorpusExtractDef corpusExtractDef = filterUnit.getCorpusExtractDef();
            CorpusExtractor corpusExtractor = extractionContext.getExtractorProvider().getCorpusExtractor(corpusExtractDef);
            FicheSelector indexationFicheSelector = FicheSelectorBuilder.init(selectionContext).addAll(corpusExtractDef.getConditionEntryList()).toFicheSelector();
            for (Corpus corpus : indexationFicheSelector.getCorpusList()) {
                Croisements croisements;
                if (corpusExtractDef.isDescendantAxis()) {
                    croisements = motclePointeur.getDescendantAxisCroisementList(corpus);
                } else {
                    croisements = motclePointeur.getCroisements(corpus);
                }
                for (Croisements.Entry entry : croisements.getEntryList()) {
                    FicheMeta ficheMeta = (FicheMeta) entry.getSubsetItem();
                    Croisement filteredCroisement = indexationFicheSelector.isSelected(ficheMeta, entry.getCroisement());
                    if (filteredCroisement != null) {
                        corpusExtractor.add(ficheMeta, filteredCroisement);
                    }
                }
            }
            if (corpusExtractXMLPart == null) {
                corpusExtractXMLPart = new CorpusExtractXMLPart(getXMLWriter(), extractParameters);
            }
            corpusExtractXMLPart.addCorpusExtract(corpusExtractor.getCorpusExtractResult());
        }

        private List<Corpus> toCorpusList(FicheParentageFilterUnit filterUnit) {
            Thesaurus thesaurus = (Thesaurus) motclePointeur.getSubset();
            List<SubsetKey> subsetKeyList = filterUnit.getSubsetKeyList();
            if (subsetKeyList.isEmpty()) {
                return thesaurus.getSatelliteCorpusList();
            }
            Fichotheque fichotheque = thesaurus.getFichotheque();
            List<Corpus> list = new ArrayList<Corpus>();
            for (SubsetKey subsetKey : subsetKeyList) {
                Corpus corpus = (Corpus) fichotheque.getSubset(subsetKey);
                if (corpus != null) {
                    Subset masterSubset = corpus.getMasterSubset();
                    if ((masterSubset != null) && (masterSubset.equals(thesaurus))) {
                        list.add(corpus);
                    }
                }
            }
            return list;
        }


    }

}
