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

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.FichothequeEditor;
import net.fichotheque.FichothequeQuestioner;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.include.ExtendedIncludeKey;
import net.fichotheque.include.IncludeKey;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.tools.junction.JunctionChangeEngine;
import net.fichotheque.tools.junction.TieBuffer;
import net.fichotheque.tools.parsers.junction.TieBufferParser;
import net.fichotheque.tools.parsers.junction.WeightMotcleToken;
import net.fichotheque.utils.ThesaurusUtils;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.primitives.Range;
import net.mapeadores.util.primitives.RangeUtils;
import net.mapeadores.util.request.RequestMap;
import net.mapeadores.util.text.StringUtils;
import net.fichotheque.junction.JunctionChanges;


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

    private final static int MAX_RANGELENGTH = 50;
    private final Set<IncludeKey> mainScope = new HashSet<IncludeKey>();
    private final List<Buffer> mainBufferList = new ArrayList<Buffer>();
    private final Set<IncludeKey> masterScope = new HashSet<IncludeKey>();
    private final List<Buffer> masterBufferList = new ArrayList<Buffer>();
    private final List<String[]> liageValuesList = new ArrayList<String[]>();
    private final FicheMeta mainFicheMeta;
    private final Lang workingLang;
    private final ParseContext parseContext;
    private final FichothequeEditor fichothequeEditor;
    private final Fichotheque fichotheque;
    private final FichothequeQuestioner fichothequeQuestioner;
    private final SubsetItem masterSubsetItem;
    private final Predicate<Subset> subsetAccessPredicate;
    private JunctionChangeEngine mainChange;
    private JunctionChangeEngine appendChange;

    private JunctionParseEngine(FicheMeta ficheMeta, ParseContext parseContext, Lang workingLang) {
        this.mainFicheMeta = ficheMeta;
        this.parseContext = parseContext;
        this.fichothequeEditor = parseContext.getFichothequeEditor();
        this.fichotheque = parseContext.getFichotheque();
        this.fichothequeQuestioner = parseContext.getFichothequeQuestioner();
        this.workingLang = workingLang;
        this.subsetAccessPredicate = parseContext.getSubsetAccessPredicate();
        Subset masterSubset = ficheMeta.getCorpus().getMasterSubset();
        if ((masterSubset != null) && (subsetAccessPredicate.test(masterSubset))) {
            this.masterSubsetItem = masterSubset.getSubsetItemById(ficheMeta.getId());
        } else {
            this.masterSubsetItem = null;
        }
    }

    JunctionChangeEngine getMainChange() {
        return mainChange;
    }

    JunctionChangeEngine getAppendChange() {
        if (appendChange == null) {
            appendChange = JunctionChangeEngine.appendEngine(mainFicheMeta);
        }
        return appendChange;
    }

    private void addLiage(String[] values) {
        liageValuesList.add(values);
    }

    private void add(ExtendedIncludeKey includeKey, String[] values) {
        Subset subset = fichotheque.getSubset(includeKey.getSubsetKey());
        if (subset == null) {
            return;
        }
        if (!subsetAccessPredicate.test(subset)) {
            return;
        }
        if (includeKey.isMaster()) {
            if (masterSubsetItem != null) {
                IncludeKey root = includeKey.getRootIncludeKey();
                masterScope.add(root);
                masterBufferList.add(new Buffer(subset, root, values));
            }
        } else {
            IncludeKey root = includeKey.getRootIncludeKey();
            mainScope.add(root);
            mainBufferList.add(new Buffer(subset, root, values));
        }
    }

    private void run() {
        if (!liageValuesList.isEmpty()) {
            for (Corpus corpus : fichotheque.getCorpusList()) {
                if (subsetAccessPredicate.test(corpus)) {
                    mainScope.add(IncludeKey.newInstance(corpus.getSubsetKey()));
                }
            }
        }
        mainChange = JunctionChangeEngine.clearExistingEngine(mainFicheMeta, mainScope);
        for (Buffer buffer : mainBufferList) {
            if (buffer.isThesaurus()) {
                parseThesaurus(mainChange, buffer);
            } else {
                parseSubset(mainChange, buffer);
            }
        }
        for (String[] values : liageValuesList) {
            parseLiage(values);
        }
        JunctionChanges junctionChanges = mainChange.toJunctionChanges();
        if (appendChange != null) {
            junctionChanges = new FusionJunctionChanges(junctionChanges, appendChange.toJunctionChanges());
        }
        fichothequeEditor.getJunctionEditor().updateJunctions(mainFicheMeta, junctionChanges);
        if (!masterScope.isEmpty()) {
            JunctionChangeEngine masterChange = JunctionChangeEngine.clearExistingEngine(masterSubsetItem, masterScope);
            for (Buffer buffer : masterBufferList) {
                if (buffer.isThesaurus()) {
                    parseThesaurus(masterChange, buffer);
                } else {
                    parseSubset(masterChange, buffer);
                }
            }
            fichothequeEditor.getJunctionEditor().updateJunctions(masterSubsetItem, masterChange.toJunctionChanges());
        }
    }

    private void parseLiage(String[] values) {
        Subset defaultSubset = mainFicheMeta.getCorpus();
        for (String value : values) {
            String[] tokens = StringUtils.getTechnicalTokens(value, false);
            for (String token : tokens) {
                try {
                    TieBuffer tieBuffer = TieBufferParser.parse(fichothequeQuestioner, token, SubsetKey.CATEGORY_CORPUS, defaultSubset, "");
                    if (subsetAccessPredicate.test(tieBuffer.getSubsetItem().getSubset())) {
                        mainChange.addTie(tieBuffer);
                    }
                } catch (ParseException pe) {
                }
            }
        }
    }

    private void parseThesaurus(JunctionChangeEngine junctionChangeEngine, Buffer buffer) {
        Thesaurus thesaurus = (Thesaurus) buffer.subset;
        IncludeKey includeKey = buffer.includeKey;
        Lang thesaurusLang = ThesaurusUtils.checkDisponibility(parseContext.getThesaurusLangChecker(), thesaurus, workingLang);
        boolean withIdalpha = thesaurus.isIdalphaType();
        DynamicEdit.Parameters parameters = new DynamicEdit.Parameters(parseContext, this, thesaurus, includeKey, thesaurusLang);
        DynamicEdit dynamicEdit = DynamicEditFactory.newInstance(parameters);
        int weightFilter = includeKey.getWeightFilter();
        String mode = includeKey.getMode();
        for (String value : buffer.values) {
            String[] tokens = StringUtils.getTechnicalTokens(value, ';', false);
            for (String token : tokens) {
                WeightMotcleToken motcleToken = WeightMotcleToken.parse(token, withIdalpha, weightFilter);
                if (motcleToken != null) {
                    Motcle motcle = WeightMotcleToken.getMotcle(motcleToken, thesaurus, thesaurusLang);
                    if (motcle != null) {
                        junctionChangeEngine.addTie(motcle, mode, motcleToken.getWeight());
                    } else if (dynamicEdit != null) {
                        dynamicEdit.editToken(motcleToken);
                    }
                }
            }
        }
    }

    private void parseSubset(JunctionChangeEngine junctionChangeEngine, Buffer buffer) {
        Subset subset = buffer.subset;
        IncludeKey includeKey = buffer.includeKey;
        int weightFilter = includeKey.getWeightFilter();
        String mode = includeKey.getMode();
        for (String value : buffer.values) {
            String[] tokens = StringUtils.getTechnicalTokens(value, false);
            for (String token : tokens) {
                if (weightFilter > 0) {
                    try {
                        Range range = RangeUtils.parseRange(token);
                        if (range != null) {
                            if (range.length() <= MAX_RANGELENGTH) {
                                for (int id = range.min(); id <= range.max(); id++) {
                                    SubsetItem subsetItem = subset.getSubsetItemById(id);
                                    if (subsetItem != null) {
                                        junctionChangeEngine.addTie(new TieBuffer(subsetItem, mode, weightFilter));
                                    }
                                }
                            } else {
                                int count = 0;
                                for (SubsetItem subsetItem : subset.getSubsetItemList()) {
                                    if (range.contains(subsetItem.getId())) {
                                        junctionChangeEngine.addTie(new TieBuffer(subsetItem, mode, weightFilter));
                                        count++;
                                    }
                                    if (count == MAX_RANGELENGTH) {
                                        break;
                                    }
                                }
                            }
                        }
                    } catch (ParseException pe) {

                    }
                } else {
                    try {
                        TieBuffer tieBuffer = TieBufferParser.parseId(subset, token, mode, weightFilter);
                        junctionChangeEngine.addTie(tieBuffer);
                    } catch (ParseException pe) {
                    }
                }
            }
        }
    }

    public static void run(FicheMeta ficheMeta, RequestMap requestMap, ParseContext parseContext, Lang workingLang) {
        JunctionParseEngine engine = new JunctionParseEngine(ficheMeta, parseContext, workingLang);
        for (String paramName : requestMap.getParameterNameSet()) {
            if (paramName.contains(":")) {
                continue;
            }
            if (paramName.equals(FichothequeConstants.LIAGE_NAME)) {
                engine.addLiage(requestMap.getParameterValues(paramName));
            } else {
                try {
                    ExtendedIncludeKey extendedIncludeKey = ExtendedIncludeKey.parse(paramName);
                    engine.add(extendedIncludeKey, requestMap.getParameterValues(paramName));
                } catch (ParseException pe) {
                }
            }
        }
        engine.run();
    }


    private static class Buffer {

        private final Subset subset;
        private final IncludeKey includeKey;
        private final String[] values;

        private Buffer(Subset subset, IncludeKey includeKey, String[] values) {
            this.subset = subset;
            this.includeKey = includeKey;
            this.values = values;
        }

        private boolean isThesaurus() {
            return subset.getSubsetKey().isThesaurusSubset();
        }

    }


    private static class FusionJunctionChanges implements JunctionChanges {

        private final List<SubsetItem> removedList = new ArrayList<SubsetItem>();
        private final List<JunctionChanges.Entry> entryList = new ArrayList<JunctionChanges.Entry>();

        private FusionJunctionChanges(JunctionChanges first, JunctionChanges second) {
            add(first);
            add(second);
        }

        @Override
        public List<SubsetItem> getRemovedList() {
            return Collections.unmodifiableList(removedList);
        }

        @Override
        public List<Entry> getEntryList() {
            return Collections.unmodifiableList(entryList);
        }

        private void add(JunctionChanges junctionChanges) {
            removedList.addAll(junctionChanges.getRemovedList());
            entryList.addAll(junctionChanges.getEntryList());
        }

    }

}
