/* FichothequeLib_Tools - Copyright (c) 2016-2022 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.format.tokenizers;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.junction.Liaison;
import net.fichotheque.extraction.ExtractionContext;
import net.fichotheque.format.FormatContext;
import net.fichotheque.format.FormatSource;
import net.fichotheque.format.SubsetPathKey;
import net.fichotheque.format.Tokenizer;
import net.fichotheque.format.Tokens;
import net.fichotheque.include.IncludeKey;
import net.fichotheque.pointeurs.MotclePointeur;
import net.fichotheque.pointeurs.SubsetItemPointeur;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.tools.format.MultiTokensBuilder;
import net.fichotheque.utils.JunctionUtils;
import net.fichotheque.utils.pointeurs.PointeurFactory;
import net.fichotheque.exportation.table.CellConverterProvider;
import net.fichotheque.junction.Junctions;


/**
 *
 * @author Vincent Calame
 */
public class SubsetPathSourceTokenizer implements Tokenizer {

    private final Fichotheque fichotheque;
    private final SubsetPathKey subsetPathKey;
    private final Tokenizer finalTokenizer;
    private final boolean globalSelect;

    public SubsetPathSourceTokenizer(Fichotheque fichotheque, SubsetPathKey subsetPathKey, Tokenizer finalTokenizer, boolean globalSelect) {
        this.fichotheque = fichotheque;
        this.subsetPathKey = subsetPathKey;
        this.finalTokenizer = finalTokenizer;
        this.globalSelect = globalSelect;
    }

    @Override
    public Tokens tokenize(FormatSource formatSource) {
        TokenizeEngine tokenizeEngine = new TokenizeEngine(formatSource, finalTokenizer);
        tokenizeEngine.resolveLevel(formatSource.getSubsetItemPointeur(), 0);
        return tokenizeEngine.toSourceTokens();
    }


    private class TokenizeEngine {

        private final FinalFormatSource finalFormatSource;
        private final Tokenizer finalTokenizer;
        private final MultiTokensBuilder builder = new MultiTokensBuilder();
        private final Predicate<SubsetItem> predicate;
        private final Predicate<Subset> subsetAccessPredicate;


        private TokenizeEngine(FormatSource formatSource, Tokenizer finalTokenizer) {
            this.subsetAccessPredicate = formatSource.getSubsetAccessPredicate();
            this.finalTokenizer = finalTokenizer;
            this.finalFormatSource = new FinalFormatSource(formatSource);
            if (globalSelect) {
                predicate = formatSource.getGlobalPredicate();
            } else {
                predicate = null;
            }
        }

        private void resolveLevel(SubsetItemPointeur parentPointeur, int level) {
            SubsetItem subsetItem = parentPointeur.getCurrentSubsetItem();
            if (subsetItem == null) {
                return;
            }
            short type = subsetPathKey.getKeyType(level);
            switch (type) {
                case SubsetPathKey.PARENTAGE_SUBSETKEY:
                    resolveParentageLevel(parentPointeur, level);
                    break;
                case SubsetPathKey.JUNCTION_INCLUDEKEY:
                    resolveJunctionLevel(parentPointeur, level);
                    break;
                case SubsetPathKey.THESAURUS_TREE:
                    if (!(subsetItem instanceof Motcle)) {
                        throw new IllegalStateException("subsetItem must be an instance of Motcle");
                    }
                    resolveThesaurusTreeLevel((Motcle) subsetItem, level);
                    break;
            }
        }

        private void resolveThesaurusTreeLevel(Motcle motcle, int level) {
            MotclePointeur levelPointeur = PointeurFactory.newMotclePointeur(motcle.getThesaurus());
            boolean isLastLevel = (level == subsetPathKey.getLength() - 1);
            if (isLastLevel) {
                finalFormatSource.setSubsetItemPointeur(levelPointeur);
            }
            List<Motcle> motcleList = getMotcleList(motcle, (String) subsetPathKey.getKeyObject(level));
            if (motcleList.isEmpty()) {
                return;
            }
            for (Motcle other : motcleList) {
                levelPointeur.setCurrentSubsetItem(other);
                if (isLastLevel) {
                    builder.add(finalTokenizer.tokenize(finalFormatSource));
                } else {
                    resolveLevel(levelPointeur, level + 1);
                }
            }
        }

        private List<Motcle> getMotcleList(Motcle motcle, String type) {
            List<Motcle> result = new ArrayList<Motcle>();
            switch (type) {
                case SubsetPathKey.ANCESTORS:
                    addAncestors(motcle, result);
                    break;
                case SubsetPathKey.PARENT:
                    Motcle parent = motcle.getParent();
                    if (parent != null) {
                        result.add(parent);
                    }
                    break;
                case SubsetPathKey.CHILDREN:
                    for (Motcle child : motcle.getChildList()) {
                        result.add(child);
                    }
                    break;
                case SubsetPathKey.DESCENDANTS:
                    addDescendants(motcle, result);
                    break;
                case SubsetPathKey.SIBLINGS:
                    addSiblings(motcle, result);
                    break;
            }
            return result;
        }

        private void addSiblings(Motcle motcle, List<Motcle> motcleList) {
            Motcle parent = motcle.getParent();
            List<Motcle> siblings;
            if (parent != null) {
                siblings = parent.getChildList();
            } else {
                siblings = motcle.getThesaurus().getFirstLevelList();
            }
            for (Motcle sibling : siblings) {
                if (!sibling.equals(motcle)) {
                    motcleList.add(sibling);
                }
            }
        }

        private void addAncestors(Motcle motcle, List<Motcle> motcleList) {
            Motcle parent = motcle.getParent();
            if (parent != null) {
                motcleList.add(parent);
                addAncestors(parent, motcleList);
            }
        }

        private void addDescendants(Motcle motcle, List<Motcle> motcleList) {
            for (Motcle child : motcle.getChildList()) {
                motcleList.add(child);
                addDescendants(child, motcleList);
            }
        }

        private void resolveParentageLevel(SubsetItemPointeur parentPointeur, int level) {
            SubsetKey subsetKey = (SubsetKey) subsetPathKey.getKeyObject(level);
            SubsetItemPointeur parentagePointeur = parentPointeur.getParentagePointeur(subsetKey);
            if (!subsetAccessPredicate.test(parentagePointeur.getSubset())) {
                return;
            }
            SubsetItem subsetItem = parentagePointeur.getCurrentSubsetItem();
            if (predicate != null) {
                if ((subsetItem != null) && (!predicate.test(subsetItem))) {
                    return;
                }
            }
            if (level == subsetPathKey.getLength() - 1) {
                finalFormatSource.setSubsetItemPointeur(parentagePointeur);
                builder.add(finalTokenizer.tokenize(finalFormatSource));
            } else {
                resolveLevel(parentagePointeur, level + 1);
            }
        }

        private void resolveJunctionLevel(SubsetItemPointeur parentPointeur, int level) {
            IncludeKey includeKey = (IncludeKey) subsetPathKey.getKeyObject(level);
            Subset levelSubset = fichotheque.getSubset(includeKey.getSubsetKey());
            if (levelSubset == null) {
                return;
            }
            if (!subsetAccessPredicate.test(levelSubset)) {
                return;
            }
            String mode = includeKey.getMode();
            int weight = includeKey.getWeightFilter();
            SubsetItemPointeur levelSubsetItemPointeur = parentPointeur.getAssociatedPointeur(levelSubset);
            boolean isLastLevel = (level == subsetPathKey.getLength() - 1);
            if (isLastLevel) {
                finalFormatSource.setSubsetItemPointeur(levelSubsetItemPointeur);
            }
            Junctions junctions = parentPointeur.getJunctions(levelSubset);
            Collection<Liaison> liaisons = JunctionUtils.filter(junctions, mode, weight, predicate);
            for (Liaison liaison : liaisons) {
                levelSubsetItemPointeur.setCurrentSubsetItem(liaison.getSubsetItem());
                if (isLastLevel) {
                    builder.add(finalTokenizer.tokenize(finalFormatSource));
                } else {
                    resolveLevel(levelSubsetItemPointeur, level + 1);
                }
            }
        }

        private Tokens toSourceTokens() {
            return builder.toTokens();
        }

    }


    private static class FinalFormatSource implements FormatSource {

        private final FormatSource orginalFormatSource;
        private SubsetItemPointeur subsetItemPointeur;

        private FinalFormatSource(FormatSource orginalFormatSource) {
            this.orginalFormatSource = orginalFormatSource;
        }

        @Override
        public SubsetItemPointeur getSubsetItemPointeur() {
            return subsetItemPointeur;
        }

        @Override
        public ExtractionContext getExtractionContext() {
            return orginalFormatSource.getExtractionContext();
        }

        @Override
        public Predicate<SubsetItem> getGlobalPredicate() {
            return orginalFormatSource.getGlobalPredicate();
        }

        @Override
        public FormatContext getFormatContext() {
            return orginalFormatSource.getFormatContext();
        }

        @Override
        public CellConverterProvider getCellConverterProvider() {
            return orginalFormatSource.getCellConverterProvider();
        }

        @Override
        public FormatSource.History getHistory() {
            return orginalFormatSource.getHistory();
        }

        @Override
        public FormatSource.ExtractionInfo getExtractionInfo() {
            return orginalFormatSource.getExtractionInfo();
        }

        private void setSubsetItemPointeur(SubsetItemPointeur subsetItemPointeur) {
            this.subsetItemPointeur = subsetItemPointeur;
        }

    }

}
