/* FichothequeLib_API - Copyright (c) 2007-2024 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.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.croisement.Croisement;
import net.fichotheque.croisement.Croisements;
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.SelectionUtils;
import net.mapeadores.util.conditions.ConditionsConstants;
import net.mapeadores.util.conditions.ConditionsUtils;
import net.mapeadores.util.conditions.TextTestEngine;
import net.mapeadores.util.exceptions.SwitchException;


/**
 *
 * @author Vincent Calame
 */
public final class MotcleConditionPredicateFactory {

    private final static Test ALWAYS_TEST = new ConstantTest(true);

    private MotcleConditionPredicateFactory() {

    }

    public static Predicate<FicheMeta> newInstance(SelectionContext selectionContext, MotcleCondition motcleCondition) {
        Test[] testArray;
        List<Test> testList = new ArrayList<Test>();
        for (MotcleCondition.Entry entry : motcleCondition.getEntryList()) {
            Test test = toTest(selectionContext, entry);
            if (test != null) {
                testList.add(test);
            }
        }
        int size = testList.size();
        if (size == 0) {
            testArray = new Test[1];
            testArray[0] = ALWAYS_TEST;
        } else {
            testArray = testList.toArray(new Test[size]);
        }
        return new ResultPredicate(motcleCondition.getLogicalOperator(), testArray);
    }


    private static class ResultPredicate implements Predicate<FicheMeta> {

        private final boolean isOr;
        private final Test[] testArray;

        private ResultPredicate(String logicalOperator, Test[] testArray) {
            this.isOr = (logicalOperator.equals(ConditionsConstants.LOGICALOPERATOR_OR));
            this.testArray = testArray;
        }

        @Override
        public boolean test(FicheMeta ficheMeta) {
            if (isOr) {
                for (Test test : testArray) {
                    if (test.isSelected(ficheMeta)) {
                        return true;
                    }
                }
                return false;
            } else {
                for (Test test : testArray) {
                    if (!test.isSelected(ficheMeta)) {
                        return false;
                    }
                }
                return true;
            }
        }

    }

    private static Test toTest(SelectionContext selectionContext, MotcleCondition.Entry entry) {
        MotcleQuery motcleQuery = entry.getMotcleQuery();
        MotcleQuery.ContentCondition contentCondition = motcleQuery.getContentCondition();
        CroisementCondition croisementCondition = entry.getCroisementCondition();
        boolean invert = false;
        boolean withConditions = motcleQuery.hasOtherConditions(false);
        boolean withSelector = ((croisementCondition != null) || (withConditions));
        if (contentCondition == null) {
            if (withSelector) {
                return new MotcleSelectorTest(invert, selectionContext, entry);
            } else {
                if (motcleQuery.isEmpty()) {
                    return null;
                } else {
                    return new ThesaurusListTest(invert, selectionContext, entry);
                }
            }
        } else {
            int conditionType = ConditionsUtils.getConditionType(contentCondition.getTextCondition());
            switch (conditionType) {
                case ConditionsConstants.IMPOSSIBLE_CONDITION:
                    return new ConstantTest(!invert);
                case ConditionsConstants.NEUTRAL_CONDITION:
                case ConditionsConstants.UNKNOWN_CONDITION:
                    if (withSelector) {
                        return new MotcleSelectorTest(invert, selectionContext, entry);
                    } else {
                        return new ConstantTest(invert);
                    }
                case ConditionsConstants.NOTEMPTY_CONDITION:
                    if (withSelector) {
                        return new MotcleSelectorTest(invert, selectionContext, entry);
                    } else {
                        return new ThesaurusListTest(invert, selectionContext, entry);
                    }
                case ConditionsConstants.EMPTY_CONDITION:
                    if (withSelector) {
                        return new MotcleSelectorTest(!invert, selectionContext, entry);
                    } else {
                        return new ThesaurusListTest(!invert, selectionContext, entry);
                    }
                case ConditionsConstants.PARTIAL_CONDITION:
                case ConditionsConstants.PARTIALOREMPTY_CONDITION:
                    return new ContentTest(invert, selectionContext, entry, conditionType, withSelector);
                default:
                    throw new SwitchException("state = " + conditionType);
            }
        }
    }

    private static Set<SubsetKey> toSubsetKeySet(Thesaurus[] thesaurusArray) {
        Set<SubsetKey> subsetKeySet = new HashSet<SubsetKey>();
        for (Thesaurus thesaurus : thesaurusArray) {
            subsetKeySet.add(thesaurus.getSubsetKey());
        }
        return subsetKeySet;
    }


    private static abstract class Test {

        private final boolean invert;

        protected Test(boolean invert) {
            this.invert = invert;
        }

        public boolean isSelected(FicheMeta ficheMeta) {
            boolean test = test(ficheMeta);
            if (invert) {
                return !test;
            } else {
                return test;
            }
        }

        protected abstract boolean test(FicheMeta ficheMeta);

    }


    private static class ConstantTest extends Test {

        public ConstantTest(boolean constant) {
            super(constant);
        }

        @Override
        public boolean test(FicheMeta ficheMeta) {
            return true;
        }

    }


    private static abstract class EntryTest extends Test {

        protected final Fichotheque fichotheque;
        protected final Thesaurus[] thesaurusArray;
        protected final Set<SubsetKey> subsetKeySet;
        protected final boolean withMasterTest;

        public EntryTest(boolean invert, SelectionContext selectionContext, MotcleCondition.Entry entry) {
            super(invert);
            this.fichotheque = selectionContext.getFichotheque();
            this.thesaurusArray = SelectionUtils.toThesaurusArray(fichotheque, entry.getMotcleQuery().getThesaurusCondition(), selectionContext.getSubsetAccessPredicate());
            this.withMasterTest = entry.isWithMaster();
            if (withMasterTest) {
                this.subsetKeySet = toSubsetKeySet(thesaurusArray);
            } else {
                this.subsetKeySet = null;
            }
        }

        @Override
        public boolean test(FicheMeta ficheMeta) {
            return true;
        }

        public Subset checkMasterSubset(FicheMeta ficheMeta) {
            if (!withMasterTest) {
                return null;
            }
            Subset masterSubset = ficheMeta.getCorpus().getMasterSubset();
            if (masterSubset == null) {
                return null;
            }
            if (subsetKeySet.contains(masterSubset.getSubsetKey())) {
                return masterSubset;
            } else {
                return null;
            }
        }


    }


    private static class ThesaurusListTest extends EntryTest {

        private ThesaurusListTest(boolean invert, SelectionContext selectionContext, MotcleCondition.Entry entry) {
            super(invert, selectionContext, entry);
        }

        @Override
        public boolean test(FicheMeta ficheMeta) {
            if (checkMasterSubset(ficheMeta) != null) {
                return true;
            }
            for (Thesaurus thesaurus : thesaurusArray) {
                Croisements croisements = fichotheque.getCroisements(ficheMeta, thesaurus);
                if (!croisements.isEmpty()) {
                    return true;
                }
            }
            return false;
        }

    }


    private static class MotcleSelectorTest extends EntryTest {

        private final MotcleSelector motcleSelector;
        private final boolean withCroisementCondition;

        private MotcleSelectorTest(boolean invert, SelectionContext selectionContext, MotcleCondition.Entry entry) {
            super(invert, selectionContext, entry);
            CroisementCondition croisementCondition = entry.getCroisementCondition();
            this.motcleSelector = MotcleSelectorBuilder.init(selectionContext)
                    .add(SelectionUtils.deriveWithoutContentCondition(entry.getMotcleQuery()), croisementCondition)
                    .toMotcleSelector();
            this.withCroisementCondition = (croisementCondition != null);
        }

        @Override
        public boolean test(FicheMeta ficheMeta) {
            Subset masterSubset = checkMasterSubset(ficheMeta);
            if (masterSubset != null) {
                if (motcleSelector.test((Motcle) masterSubset.getSubsetItemById(ficheMeta.getId()))) {
                    return true;
                }
            }
            for (Thesaurus thesaurus : thesaurusArray) {
                Croisements croisements = fichotheque.getCroisements(ficheMeta, thesaurus);
                for (Croisements.Entry entry : croisements.getEntryList()) {
                    Motcle motcle = (Motcle) entry.getSubsetItem();
                    if (!withCroisementCondition) {
                        if (motcleSelector.test(motcle)) {
                            return true;
                        }
                    } else {
                        if (motcleSelector.isSelected(motcle, entry.getCroisement()) != null) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

    }


    private static class ContentTest extends EntryTest {

        private final TextTestEngine textTestEngine;
        private final boolean allowsEmpty;
        private final boolean withSelector;
        private final MotcleSelector motcleSelector;
        private final boolean withCroisementCondition;
        private final String contentScope;


        public ContentTest(boolean invert, SelectionContext selectionContext, MotcleCondition.Entry entry, int conditionType, boolean withSelector) {
            super(invert, selectionContext, entry);
            MotcleQuery motcleQuery = entry.getMotcleQuery();
            MotcleQuery.ContentCondition contentCondition = motcleQuery.getContentCondition();
            CroisementCondition croisementCondition = entry.getCroisementCondition();
            this.textTestEngine = TextTestEngine.newInstance(contentCondition.getTextCondition(), selectionContext.getWorkingLang());
            this.allowsEmpty = ((!withSelector) && (conditionType == ConditionsConstants.PARTIALOREMPTY_CONDITION));
            this.withSelector = withSelector;
            if (withSelector) {
                this.motcleSelector = MotcleSelectorBuilder.init(selectionContext)
                        .add(SelectionUtils.deriveWithoutContentCondition(motcleQuery), croisementCondition)
                        .toMotcleSelector();
            } else {
                this.motcleSelector = null;
            }
            this.withCroisementCondition = (croisementCondition != null);
            this.contentScope = contentCondition.getScope();
        }


        @Override
        public boolean test(FicheMeta ficheMeta) {
            boolean empty = true;
            textTestEngine.start();
            Subset masterSubset = checkMasterSubset(ficheMeta);
            if (masterSubset != null) {
                Motcle motcle = (Motcle) masterSubset.getSubsetItemById(ficheMeta.getId());
                if (checkSelector(motcle, null)) {
                    empty = false;
                    checkTextTest(motcle);
                    if (textTestEngine.canStop()) {
                        return textTestEngine.getResult();
                    }
                }
            }
            for (Thesaurus thesaurus : thesaurusArray) {
                Croisements croisements = fichotheque.getCroisements(ficheMeta, thesaurus);
                for (Croisements.Entry entry : croisements.getEntryList()) {
                    Motcle motcle = (Motcle) entry.getSubsetItem();
                    if (checkSelector(motcle, entry.getCroisement())) {
                        empty = false;
                        checkTextTest(motcle);
                        if (textTestEngine.canStop()) {
                            return textTestEngine.getResult();
                        }
                    }
                }
            }
            if (empty) {
                return allowsEmpty;
            } else {
                return textTestEngine.getResult();
            }
        }

        private boolean checkSelector(Motcle motcle, Croisement croisement) {
            if (!withSelector) {
                return true;
            }
            if ((croisement == null) || (!withCroisementCondition)) {
                return motcleSelector.test(motcle);
            } else {
                return (motcleSelector.isSelected(motcle, croisement) != null);
            }
        }

        private void checkTextTest(Motcle motcle) {
            if (!contentScope.equals(MotcleQuery.SCOPE_IDALPHA_WITHOUT)) {
                String idalpha = motcle.getIdalpha();
                if (idalpha != null) {
                    textTestEngine.addString(idalpha);
                    if (contentScope.equals(MotcleQuery.SCOPE_IDALPHA_ONLY)) {
                        return;
                    }
                }
            }
            String labelString = motcle.getLabelString(textTestEngine.getLang(), "");
            if (!labelString.isEmpty()) {
                textTestEngine.addString(labelString);
            }
        }

    }

}
