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

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.fichotheque.SubsetKey;
import net.fichotheque.croisement.Croisement;
import net.fichotheque.selection.CroisementCondition;
import net.fichotheque.selection.MotcleCondition;
import net.fichotheque.selection.MotcleQuery;
import net.fichotheque.selection.MotcleSelector;
import net.fichotheque.selection.SelectionContext;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.SelectionUtils;
import net.mapeadores.util.annotation.Nullable;


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

    private final SelectionContext selectionContext;
    private final Map<SubsetKey, List<MotcleSelectEngine>> listMap = new HashMap<SubsetKey, List<MotcleSelectEngine>>();
    private final List<Thesaurus> thesaurusList = new ArrayList<Thesaurus>();
    private MotcleSelectEngine firstEngine;
    private int engineCount = 0;

    public MotcleSelectorBuilder(SelectionContext selectionContext) {
        this.selectionContext = selectionContext;
    }

    public MotcleSelectorBuilder add(MotcleQuery motcleQuery) {
        return add(motcleQuery, null);
    }

    public MotcleSelectorBuilder add(MotcleQuery motcleQuery, CroisementCondition croisementCondition) {
        Thesaurus[] thesaurusArray = SelectionUtils.toThesaurusArray(selectionContext.getFichotheque(), motcleQuery, selectionContext.getSubsetAccessPredicate());
        MotcleSelectEngine motcleSelectEngine = new MotcleSelectEngine(motcleQuery, croisementCondition, selectionContext);
        if (firstEngine == null) {
            firstEngine = motcleSelectEngine;
        }
        engineCount++;
        for (Thesaurus thesaurus : thesaurusArray) {
            List<MotcleSelectEngine> list = checkThesaurus(thesaurus);
            list.add(motcleSelectEngine);
        }
        return this;
    }

    public MotcleSelectorBuilder addAll(Collection<MotcleCondition.Entry> entries) {
        for (MotcleCondition.Entry entry : entries) {
            add(entry.getMotcleQuery(), entry.getCroisementCondition());
        }
        return this;
    }

    public MotcleSelector toMotcleSelector() {
        switch (engineCount) {
            case 0: {
                List<Thesaurus> finalThesaurusList = FichothequeUtils.wrap(FichothequeUtils.toThesaurusArray(selectionContext.getFichotheque(), selectionContext.getSubsetAccessPredicate()));
                return new UniqueMotcleSelector(finalThesaurusList, null, new MotcleSelectEngine(SelectionUtils.EMPTY_MOTCLEQUERY, null, selectionContext));
            }
            case 1: {
                List<Thesaurus> finalThesaurusList = FichothequeUtils.wrap(thesaurusList.toArray(new Thesaurus[thesaurusList.size()]));
                return new UniqueMotcleSelector(finalThesaurusList, new HashSet<SubsetKey>(listMap.keySet()), firstEngine);
            }
            default: {
                List<Thesaurus> finalThesaurusList = FichothequeUtils.wrap(thesaurusList.toArray(new Thesaurus[thesaurusList.size()]));
                Map<SubsetKey, MotcleSelectEngine[]> arrayMap = new HashMap<SubsetKey, MotcleSelectEngine[]>();
                for (Map.Entry<SubsetKey, List<MotcleSelectEngine>> entry : listMap.entrySet()) {
                    List<MotcleSelectEngine> list = entry.getValue();
                    MotcleSelectEngine[] array = list.toArray(new MotcleSelectEngine[list.size()]);
                    arrayMap.put(entry.getKey(), array);
                }
                return new MultiMotcleSelector(finalThesaurusList, arrayMap);
            }
        }
    }

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


    private List<MotcleSelectEngine> checkThesaurus(Thesaurus thesaurus) {
        List<MotcleSelectEngine> currentList = listMap.get(thesaurus.getSubsetKey());
        if (currentList == null) {
            currentList = new ArrayList<MotcleSelectEngine>();
            thesaurusList.add(thesaurus);
            listMap.put(thesaurus.getSubsetKey(), currentList);
        }
        return currentList;
    }


    private static class MultiMotcleSelector implements MotcleSelector {

        private final List<Thesaurus> thesaurusList;
        private final Map<SubsetKey, MotcleSelectEngine[]> arrayMap;

        private MultiMotcleSelector(List<Thesaurus> thesaurusList, Map<SubsetKey, MotcleSelectEngine[]> arrayMap) {
            this.thesaurusList = thesaurusList;
            this.arrayMap = arrayMap;
        }

        @Override
        public List<Thesaurus> getThesaurusList() {
            return thesaurusList;
        }

        @Override
        public boolean test(Motcle motcle) {
            MotcleSelectEngine[] array = arrayMap.get(motcle.getSubsetKey());
            if (array == null) {
                return false;
            }
            for (MotcleSelectEngine engine : array) {
                if (engine.isSelected(motcle)) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public Croisement isSelected(Motcle motcle, Croisement croisement) {
            MotcleSelectEngine[] array = arrayMap.get(motcle.getSubsetKey());
            if (array == null) {
                return null;
            }
            FilteredCroisementBuilder builder = new FilteredCroisementBuilder(croisement);
            for (MotcleSelectEngine engine : array) {
                engine.filter(motcle, builder);
            }
            return builder.toCroisement();
        }

    }


    private static class UniqueMotcleSelector implements MotcleSelector {

        private final List<Thesaurus> thesaurusList;
        private final Set<SubsetKey> keySet;
        private final MotcleSelectEngine motcleSelectEngine;

        private UniqueMotcleSelector(List<Thesaurus> thesaurusList, @Nullable Set<SubsetKey> keySet, MotcleSelectEngine motcleSelectEngine) {
            this.thesaurusList = thesaurusList;
            this.keySet = keySet;
            this.motcleSelectEngine = motcleSelectEngine;
        }

        @Override
        public List<Thesaurus> getThesaurusList() {
            return thesaurusList;
        }

        @Override
        public boolean test(Motcle motcle) {
            if ((keySet != null) && (!keySet.contains(motcle.getSubsetKey()))) {
                return false;
            }
            return motcleSelectEngine.isSelected(motcle);
        }

        @Override
        public Croisement isSelected(Motcle motcle, Croisement croisement) {
            if ((keySet != null) && (!keySet.contains(motcle.getSubsetKey()))) {
                return null;
            }
            FilteredCroisementBuilder builder = new FilteredCroisementBuilder(croisement);
            motcleSelectEngine.filter(motcle, builder);
            return builder.toCroisement();
        }

    }

}
