/* FichothequeLib_API - Copyright (c) 2020-2023 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.utils.selection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
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.Croisement;
import net.fichotheque.pointeurs.FichePointeur;
import net.fichotheque.selection.CroisementCondition;
import net.fichotheque.selection.FicheCondition;
import net.fichotheque.selection.FicheQuery;
import net.fichotheque.selection.FicheSelector;
import net.fichotheque.selection.MotcleCondition;
import net.fichotheque.selection.SelectionContext;
import net.fichotheque.selection.SubsetCondition;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.SelectionUtils;
import net.fichotheque.utils.pointeurs.PointeurFactory;


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

    private final SelectionContext selectionContext;
    private final Fichotheque fichotheque;
    private final Map<SubsetKey, UnitBuilder> unitBuilderMap = new HashMap<SubsetKey, UnitBuilder>();
    private final List<Corpus> corpusList = new ArrayList<Corpus>();
    private int engineCount = 0;

    public FicheSelectorBuilder(SelectionContext selectionContext) {
        this.selectionContext = selectionContext;
        this.fichotheque = selectionContext.getFichotheque();
    }

    public FicheSelectorBuilder add(FicheQuery ficheQuery) {
        return add(ficheQuery, null);
    }

    public FicheSelectorBuilder add(FicheQuery ficheQuery, CroisementCondition croisementCondition) {
        RelationPredicate relationPredicate = toRelationPredicate(ficheQuery);
        List<Corpus> resolvedCorpusList = resolveCorpusList(ficheQuery.getCorpusCondition());
        for (Corpus corpus : resolvedCorpusList) {
            UnitBuilder unitBuilder = checkCorpus(corpus);
            unitBuilder.add(ficheQuery, croisementCondition, relationPredicate);
        }
        engineCount++;
        return this;
    }

    private List<Corpus> resolveCorpusList(SubsetCondition corpusCondition) {
        Corpus currentCorpus = selectionContext.getCurrentCorpus();
        SubsetKey currentKey = null;
        if (currentCorpus != null) {
            currentKey = currentCorpus.getSubsetKey();
        }
        Predicate<Subset> corpusPredicate = selectionContext.getSubsetAccessPredicate();
        List<Corpus> result = new ArrayList<Corpus>();
        for (Corpus corpus : fichotheque.getCorpusList()) {
            if (corpusPredicate.test(corpus)) {
                if (corpusCondition.accept(corpus.getSubsetKey(), currentKey)) {
                    result.add(corpus);
                }

            }
        }
        return result;
    }

    public FicheSelectorBuilder addAll(Collection<FicheCondition.Entry> entries) {
        for (FicheCondition.Entry entry : entries) {
            add(entry.getFicheQuery(), entry.getCroisementCondition());
        }
        return this;
    }

    public FicheSelector toFicheSelector() {
        if (engineCount == 0) {
            return FicheSelectorBuilder.init(selectionContext).add(SelectionUtils.EMPTY_FICHEQUERY).toFicheSelector();
        }
        List<Corpus> finalCorpusList = FichothequeUtils.wrap(corpusList.toArray(new Corpus[corpusList.size()]));
        Map<SubsetKey, Unit> finalMap = new HashMap<SubsetKey, Unit>();
        for (Map.Entry<SubsetKey, UnitBuilder> entry : unitBuilderMap.entrySet()) {
            finalMap.put(entry.getKey(), entry.getValue().toUnit());
        }
        return new InternalFicheSelector(finalCorpusList, finalMap, selectionContext.getFichePredicate());
    }

    private UnitBuilder checkCorpus(Corpus corpus) {
        UnitBuilder unitBuilder = unitBuilderMap.get(corpus.getSubsetKey());
        if (unitBuilder == null) {
            unitBuilder = new UnitBuilder(corpus);
            unitBuilderMap.put(corpus.getSubsetKey(), unitBuilder);
            corpusList.add(corpus);
        }
        return unitBuilder;
    }

    private RelationPredicate toRelationPredicate(FicheQuery ficheQuery) {
        Predicate<FicheMeta> motcleConditionPredicate;
        Predicate<SubsetItem> ficheConditionPredicate;
        MotcleCondition motcleCondition = ficheQuery.getMotcleCondition();
        if (motcleCondition != null) {
            motcleConditionPredicate = MotcleConditionPredicateFactory.newInstance(selectionContext, motcleCondition);
        } else {
            motcleConditionPredicate = null;
        }
        FicheCondition ficheCondition = ficheQuery.getFicheCondition();
        if (ficheCondition != null) {
            ficheConditionPredicate = FicheConditionPredicateFactory.newInstance(selectionContext, ficheCondition);
        } else {
            ficheConditionPredicate = null;
        }
        if ((motcleConditionPredicate == null) && (ficheConditionPredicate == null)) {
            return null;
        } else {
            return new RelationPredicate(motcleConditionPredicate, ficheConditionPredicate);
        }
    }

    public static FicheSelectorBuilder init(SelectionContext selectionContext) {
        return new FicheSelectorBuilder(selectionContext);
    }


    private class UnitBuilder {

        private final Corpus corpus;
        private final List<FicheSelectEngine> engineList = new ArrayList<FicheSelectEngine>();

        private UnitBuilder(Corpus corpus) {
            this.corpus = corpus;
        }

        private void add(FicheQuery ficheQuery, CroisementCondition croisementCondition, Predicate<FicheMeta> relationPredicate) {
            engineList.add(FicheSelectEngine.newInstance(corpus.getCorpusMetadata(), ficheQuery, croisementCondition, selectionContext, relationPredicate));
        }

        private Unit toUnit() {
            FicheSelectEngine[] array = engineList.toArray(new FicheSelectEngine[engineList.size()]);
            return new Unit(PointeurFactory.newFichePointeur(corpus), array);
        }

    }


    private static class Unit {

        private final FichePointeur fichePointeur;
        private final FicheSelectEngine[] array;

        private Unit(FichePointeur fichePointeur, FicheSelectEngine[] array) {
            this.fichePointeur = fichePointeur;
            this.array = array;
        }

        private boolean isSelected(FicheMeta ficheMeta) {
            fichePointeur.setCurrentSubsetItem(ficheMeta);
            for (FicheSelectEngine ficheSelectEngine : array) {
                if (ficheSelectEngine.isSelected(fichePointeur)) {
                    return true;
                }
            }
            return false;
        }

        private Croisement isSelected(FicheMeta ficheMeta, Croisement croisement) {
            FilteredCroisementBuilder builder = new FilteredCroisementBuilder(croisement);
            fichePointeur.setCurrentSubsetItem(ficheMeta);
            for (FicheSelectEngine ficheSelectEngine : array) {
                ficheSelectEngine.filter(fichePointeur, builder);
            }
            return builder.toCroisement();
        }

    }


    private static class InternalFicheSelector implements FicheSelector {

        private final List<Corpus> corpusList;
        private final Map<SubsetKey, Unit> unitMap;
        private final Predicate<FicheMeta> initialPredicate;


        private InternalFicheSelector(List<Corpus> corpusList, Map<SubsetKey, Unit> unitMap, Predicate<FicheMeta> initialPredicate) {
            this.corpusList = corpusList;
            this.unitMap = unitMap;
            this.initialPredicate = initialPredicate;
        }

        @Override
        public List<Corpus> getCorpusList() {
            return corpusList;
        }

        @Override
        public Croisement isSelected(FicheMeta ficheMeta, Croisement croisement) {
            if (!initialPredicate.test(ficheMeta)) {
                return null;
            }
            Unit unit = unitMap.get(ficheMeta.getSubsetKey());
            if (unit == null) {
                return null;
            }
            return unit.isSelected(ficheMeta, croisement);
        }

        @Override
        public boolean test(FicheMeta ficheMeta) {
            if (!initialPredicate.test(ficheMeta)) {
                return false;
            }
            Unit unit = unitMap.get(ficheMeta.getSubsetKey());
            if (unit == null) {
                return false;
            }
            return unit.isSelected(ficheMeta);
        }

    }


    private static class RelationPredicate implements Predicate<FicheMeta> {

        private final Predicate<FicheMeta> motcleConditionPredicate;
        private final Predicate<SubsetItem> ficheConditionPredicate;

        private RelationPredicate(Predicate<FicheMeta> motcleConditionPredicate, Predicate<SubsetItem> ficheConditionPredicate) {
            this.motcleConditionPredicate = motcleConditionPredicate;
            this.ficheConditionPredicate = ficheConditionPredicate;
        }

        @Override
        public boolean test(FicheMeta ficheMeta) {
            if (motcleConditionPredicate != null) {
                if (!motcleConditionPredicate.test(ficheMeta)) {
                    return false;
                }
            }
            if (ficheConditionPredicate != null) {
                if (!ficheConditionPredicate.test(ficheMeta)) {
                    return false;
                }
            }
            return true;
        }

    }

}
