/* FichothequeLib_Tools - Copyright (c) 2012-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.tools.selection;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.selection.FicheCondition;
import net.fichotheque.selection.FicheQuery;
import net.fichotheque.selection.FieldContentCondition;
import net.fichotheque.selection.MotcleCondition;
import net.fichotheque.selection.PeriodCondition;
import net.fichotheque.selection.RangeCondition;
import net.fichotheque.selection.SubsetCondition;
import net.fichotheque.selection.UserCondition;
import net.fichotheque.utils.CorpusMetadataUtils;
import net.fichotheque.utils.SelectionUtils;
import net.fichotheque.utils.selection.FicheConditionBuilder;
import net.fichotheque.utils.selection.MotcleConditionBuilder;
import net.mapeadores.util.conditions.ConditionsConstants;
import net.mapeadores.util.conditions.TextCondition;


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

    private final Set<SubsetKey> corpusKeySet = new LinkedHashSet<SubsetKey>();
    private final Set<FieldKey> fieldKeySet = new LinkedHashSet<FieldKey>();
    private final MotcleConditionBuilder motcleConditionBuilder = MotcleConditionBuilder.init().setLogicalOperator(ConditionsConstants.LOGICALOPERATOR_AND);
    private final FicheConditionBuilder ficheConditionBuilder = FicheConditionBuilder.init().setLogicalOperator(ConditionsConstants.LOGICALOPERATOR_AND);
    private TextCondition fieldContentTextCondition = null;
    private String fieldContentScope = FieldContentCondition.FICHE_SCOPE;
    private PeriodCondition periodCondition = null;
    private RangeCondition idRangeCondition = null;
    private UserCondition userCondition = null;
    private boolean withCurrentCorpus = false;
    private boolean exclude = false;
    private String discardFilter = FicheQuery.DISCARDFILTER_ALL;
    private boolean withGeoloc = false;

    public FicheQueryBuilder() {
    }

    public FicheQueryBuilder addCorpus(Object object) {
        if (object == null) {
            return this;
        }
        if (object instanceof Corpus) {
            corpusKeySet.add(((Corpus) object).getSubsetKey());
        } else if (object instanceof SubsetKey) {
            SubsetKey subsetKey = (SubsetKey) object;
            if (subsetKey.isCorpusSubset()) {
                corpusKeySet.add(subsetKey);
            }
        } else if (object instanceof Collection) {
            for (Object obj : ((Collection) object)) {
                if (obj != null) {
                    if (obj instanceof Corpus) {
                        corpusKeySet.add(((Corpus) obj).getSubsetKey());
                    } else if (obj instanceof SubsetKey) {
                        SubsetKey subsetKey = (SubsetKey) obj;
                        if (subsetKey.isCorpusSubset()) {
                            corpusKeySet.add(subsetKey);
                        }
                    }
                }
            }
        } else if (object instanceof SubsetKey[]) {
            for (SubsetKey subsetKey : ((SubsetKey[]) object)) {
                if ((subsetKey != null) && (subsetKey.isCorpusSubset())) {
                    corpusKeySet.add(subsetKey);
                }
            }
        }
        return this;
    }

    public FicheQueryBuilder clearCorpus() {
        corpusKeySet.clear();
        return this;
    }

    public FicheQueryBuilder addMotcleConditionEntry(MotcleCondition.Entry entry) {
        if (entry == null) {
            throw new IllegalArgumentException("entry is null");
        }
        motcleConditionBuilder.addEntry(entry);
        return this;
    }

    public FicheQueryBuilder setMotcleLogicalOperator(String motcleLogicalOperator) {
        motcleConditionBuilder.setLogicalOperator(motcleLogicalOperator);
        return this;
    }

    public FicheQueryBuilder addFicheConditionQuery(FicheCondition.Entry entry) {
        if (entry == null) {
            throw new IllegalArgumentException("ficheQuery is null");
        }
        ficheConditionBuilder.addEntry(entry);
        return this;
    }

    public FicheQueryBuilder setFicheLogicalOperator(String ficheLogicalOperator) {
        ficheConditionBuilder.setLogicalOperator(ficheLogicalOperator);
        return this;
    }

    public FicheQueryBuilder setWithCurrentCorpus(boolean withCurrentCorpus) {
        this.withCurrentCorpus = withCurrentCorpus;
        return this;
    }

    public FicheQueryBuilder setExclude(boolean exclude) {
        this.exclude = exclude;
        return this;
    }

    public FicheQueryBuilder setFieldContentCondition(TextCondition textCondition, String scope, Collection<FieldKey> fieldKeys) {
        this.fieldContentScope = FieldContentCondition.checkScope(scope);
        this.fieldContentTextCondition = textCondition;
        fieldKeySet.clear();
        if (scope.equals(FieldContentCondition.SELECTION_SCOPE)) {
            if (fieldKeys == null) {
                throw new IllegalArgumentException("fieldKeys is null");
            }
            if (fieldKeys.isEmpty()) {
                throw new IllegalArgumentException("fieldKeys is empty");
            }
            fieldKeySet.addAll(fieldKeys);
        }
        return this;
    }

    public FicheQueryBuilder setPeriodCondition(PeriodCondition periodCondition) {
        this.periodCondition = periodCondition;
        return this;
    }

    public FicheQueryBuilder setIdRangeCondition(RangeCondition idRangeCondition) {
        this.idRangeCondition = idRangeCondition;
        return this;
    }

    public FicheQueryBuilder setUserCondition(UserCondition userCondition) {
        this.userCondition = userCondition;
        return this;
    }

    public FicheQueryBuilder setDiscardFilter(String discardFilter) {
        this.discardFilter = FicheQuery.checkDiscardFilter(discardFilter);
        return this;
    }

    public FicheQueryBuilder setWithGeoloc(boolean withGeoloc) {
        this.withGeoloc = withGeoloc;
        return this;
    }

    public FicheQuery toFicheQuery() {
        SubsetCondition corpusCondition = SelectionUtils.toSubsetCondition(new LinkedHashSet(corpusKeySet), withCurrentCorpus, exclude);
        FieldContentCondition fieldContentCondition = null;
        if (fieldContentTextCondition != null) {
            List<FieldKey> fieldKeyList;
            if (fieldKeySet.isEmpty()) {
                fieldKeyList = CorpusMetadataUtils.EMPTY_FIELDKEYLIST;
            } else {
                fieldKeyList = CorpusMetadataUtils.wrap(fieldKeySet.toArray(new FieldKey[fieldKeySet.size()]));
            }
            fieldContentCondition = new InternalFieldContentCondition(fieldContentScope, fieldKeyList, fieldContentTextCondition);
        }
        MotcleCondition motcleCondition;
        if (motcleConditionBuilder.isEmpty()) {
            motcleCondition = null;
        } else {
            motcleCondition = motcleConditionBuilder.toMotcleCondition();
        }
        FicheCondition ficheCondition;
        if (ficheConditionBuilder.isEmpty()) {
            ficheCondition = null;
        } else {
            ficheCondition = ficheConditionBuilder.toFicheCondition();
        }
        return new InternalFicheQuery(corpusCondition, idRangeCondition, motcleCondition, ficheCondition,
                fieldContentCondition, periodCondition, userCondition, discardFilter, withGeoloc);
    }

    public static FicheQueryBuilder init() {
        return new FicheQueryBuilder();
    }


    private static class InternalFicheQuery implements FicheQuery {

        private final SubsetCondition corpusCondition;
        private final MotcleCondition motcleCondition;
        private final FicheCondition ficheCondition;
        private final FieldContentCondition fieldContentCondition;
        private final RangeCondition idRangeCondition;
        private final PeriodCondition periodCondition;
        private final UserCondition userCondition;
        private final String discardFilter;
        private final boolean withGeoloc;

        private InternalFicheQuery(SubsetCondition corpusCondition, RangeCondition idRangeCondition, MotcleCondition motcleCondition, FicheCondition ficheCondition,
                FieldContentCondition fieldContentCondition, PeriodCondition periodCondition, UserCondition userCondition, String discardFilter, boolean withGeoloc) {
            this.corpusCondition = corpusCondition;
            this.motcleCondition = motcleCondition;
            this.ficheCondition = ficheCondition;
            this.fieldContentCondition = fieldContentCondition;
            this.idRangeCondition = idRangeCondition;
            this.periodCondition = periodCondition;
            this.userCondition = userCondition;
            this.discardFilter = discardFilter;
            this.withGeoloc = withGeoloc;
        }

        @Override
        public SubsetCondition getCorpusCondition() {
            return corpusCondition;
        }

        @Override
        public MotcleCondition getMotcleCondition() {
            return motcleCondition;
        }

        @Override
        public FicheCondition getFicheCondition() {
            return ficheCondition;
        }

        @Override
        public FieldContentCondition getFieldContentCondition() {
            return fieldContentCondition;
        }

        @Override
        public PeriodCondition getPeriodCondition() {
            return periodCondition;
        }

        @Override
        public RangeCondition getIdRangeCondition() {
            return idRangeCondition;
        }

        @Override
        public UserCondition getUserCondition() {
            return userCondition;
        }

        @Override
        public String getDiscardFilter() {
            return discardFilter;
        }

        @Override
        public boolean isWithGeoloc() {
            return withGeoloc;
        }

    }


    private static class InternalFieldContentCondition implements FieldContentCondition {

        private final String scope;
        private final List<FieldKey> fieldKeyList;
        private final TextCondition textCondition;

        private InternalFieldContentCondition(String scope, List<FieldKey> fieldKeyList, TextCondition textCondition) {
            this.scope = scope;
            this.fieldKeyList = fieldKeyList;
            this.textCondition = textCondition;
        }

        @Override
        public String getScope() {
            return scope;
        }

        @Override
        public List<FieldKey> getFieldKeyList() {
            return fieldKeyList;
        }

        @Override
        public TextCondition getTextCondition() {
            return textCondition;
        }

    }

}
