/* FichothequeLib_Tools - Copyright (c) 2013-2021 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.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.croisement.CroisementChanges;
import net.fichotheque.include.ExtendedIncludeKey;
import net.fichotheque.include.IncludeKey;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.tools.croisement.CroisementChangeEngine;
import net.fichotheque.tools.croisement.LienBuffer;
import net.fichotheque.tools.parsers.croisement.LienBufferParser;
import net.fichotheque.tools.parsers.croisement.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;


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

    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 SubsetItem masterSubsetItem;
    private final Predicate<Subset> subsetAccessPredicate;
    private CroisementChangeEngine mainChange;
    private CroisementChangeEngine appendChange;

    private CroisementParseEngine(FicheMeta ficheMeta, ParseContext parseContext, Lang workingLang) {
        this.mainFicheMeta = ficheMeta;
        this.parseContext = parseContext;
        this.fichothequeEditor = parseContext.getFichothequeEditor();
        this.fichotheque = parseContext.getFichotheque();
        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;
        }
    }

    CroisementChangeEngine getMainChange() {
        return mainChange;
    }

    CroisementChangeEngine getAppendChange() {
        if (appendChange == null) {
            appendChange = CroisementChangeEngine.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 = CroisementChangeEngine.clearExistingEngine(mainFicheMeta, mainScope);
        for (Buffer buffer : mainBufferList) {
            if (buffer.isThesaurus()) {
                parseThesaurus(mainChange, buffer);
            } else {
                parseSubset(mainChange, buffer);
            }
        }
        for (String[] values : liageValuesList) {
            parseLiage(values);
        }
        CroisementChanges croisementChanges = mainChange.toCroisementChanges();
        if (appendChange != null) {
            croisementChanges = new FusionCroisementChanges(croisementChanges, appendChange.toCroisementChanges());
        }
        fichothequeEditor.getCroisementEditor().updateCroisements(mainFicheMeta, croisementChanges);
        if (!masterScope.isEmpty()) {
            CroisementChangeEngine masterChange = CroisementChangeEngine.clearExistingEngine(masterSubsetItem, masterScope);
            for (Buffer buffer : masterBufferList) {
                if (buffer.isThesaurus()) {
                    parseThesaurus(masterChange, buffer);
                } else {
                    parseSubset(masterChange, buffer);
                }
            }
            fichothequeEditor.getCroisementEditor().updateCroisements(masterSubsetItem, masterChange.toCroisementChanges());
        }
    }

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

    private void parseThesaurus(CroisementChangeEngine croisementChangeEngine, 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) {
                        croisementChangeEngine.addLien(motcle, mode, motcleToken.getWeight());
                    } else if (dynamicEdit != null) {
                        dynamicEdit.editToken(motcleToken);
                    }
                }
            }
        }
    }

    private void parseSubset(CroisementChangeEngine croisementChangeEngine, 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) {
                                        croisementChangeEngine.addLien(new LienBuffer(subsetItem, mode, weightFilter));
                                    }
                                }
                            } else {
                                int count = 0;
                                for (SubsetItem subsetItem : subset.getSubsetItemList()) {
                                    if (range.contains(subsetItem.getId())) {
                                        croisementChangeEngine.addLien(new LienBuffer(subsetItem, mode, weightFilter));
                                        count++;
                                    }
                                    if (count == MAX_RANGELENGTH) {
                                        break;
                                    }
                                }
                            }
                        }
                    } catch (ParseException pe) {

                    }
                } else {
                    try {
                        LienBuffer lienBuffer = LienBufferParser.parseId(subset, token, mode, weightFilter);
                        croisementChangeEngine.addLien(lienBuffer);
                    } catch (ParseException pe) {
                    }
                }
            }
        }
    }

    public static void run(FicheMeta ficheMeta, RequestMap requestMap, ParseContext parseContext, Lang workingLang) {
        CroisementParseEngine engine = new CroisementParseEngine(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 FusionCroisementChanges implements CroisementChanges {

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

        private FusionCroisementChanges(CroisementChanges first, CroisementChanges 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(CroisementChanges croisementChanges) {
            removedList.addAll(croisementChanges.getRemovedList());
            entryList.addAll(croisementChanges.getEntryList());
        }

    }

}
