/* FichothequeLib_API - Copyright (c) 2006-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.List;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.fiche.DateItem;
import net.fichotheque.corpus.fiche.FicheItem;
import net.fichotheque.corpus.fiche.FicheItems;
import net.fichotheque.corpus.fiche.GeopointItem;
import net.fichotheque.corpus.metadata.CorpusField;
import net.fichotheque.corpus.metadata.CorpusMetadata;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.pointeurs.FichePointeur;
import net.fichotheque.selection.CroisementCondition;
import net.fichotheque.selection.FicheQuery;
import net.fichotheque.selection.FieldContentCondition;
import net.fichotheque.selection.FieldTest;
import net.fichotheque.selection.PeriodCondition;
import net.fichotheque.selection.RangeCondition;
import net.fichotheque.selection.SelectionContext;
import net.fichotheque.selection.UserCondition;
import net.fichotheque.sphere.Redacteur;
import net.fichotheque.sphere.Sphere;
import net.fichotheque.utils.CorpusMetadataUtils;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.SphereUtils;
import net.mapeadores.util.exceptions.SwitchException;
import net.mapeadores.util.primitives.DateFilter;
import net.mapeadores.util.primitives.DateFilterFactory;
import net.mapeadores.util.primitives.DateFilterUtils;
import net.mapeadores.util.primitives.FuzzyDate;


/**
 *
 * @author Vincent Calame
 */
class FicheSelectEngine {

    private final static FieldKey[] titreScopeArray = {FieldKey.TITLE};
    private final FicheQuery ficheQuery;
    private final Redacteur[] redacteurArray;
    private final Sphere[] sphereArray;
    private final FieldTest fieldTest;
    private final FieldKey geolocalisationFieldKey;
    private final boolean onCreationDate;
    private final boolean onModificationDate;
    private final List<FieldKey> periodFieldKeyList;
    private final DateFilter dateFilter;
    private final CroisementCondition croisementCondition;
    private final Predicate<FicheMeta> relationPredicate;
    private final String userFilter;

    private FicheSelectEngine(CorpusMetadata corpusMetadata, FicheQuery ficheQuery, CroisementCondition croisementCondition, FieldTest fieldTest, Predicate<FicheMeta> relationPredicate) {
        this.ficheQuery = ficheQuery;
        this.fieldTest = fieldTest;
        this.croisementCondition = croisementCondition;
        this.relationPredicate = relationPredicate;
        Fichotheque fichotheque = corpusMetadata.getCorpus().getFichotheque();
        UserCondition userCondition = ficheQuery.getUserCondition();
        if ((userCondition != null) && (userCondition.isSome())) {
            Object[] array = initArrays(fichotheque, userCondition);
            this.redacteurArray = (Redacteur[]) array[0];
            this.sphereArray = (Sphere[]) array[1];
        } else {
            this.redacteurArray = null;
            this.sphereArray = null;
        }
        this.userFilter = (userCondition == null) ? "" : userCondition.getFilter();
        if (ficheQuery.isWithGeoloc()) {
            CorpusField corpusField = corpusMetadata.getGeolocalisationField();
            if (corpusField != null) {
                this.geolocalisationFieldKey = corpusField.getFieldKey();
            } else {
                this.geolocalisationFieldKey = null;
            }
        } else {
            this.geolocalisationFieldKey = null;
        }
        PeriodCondition periodCondition = ficheQuery.getPeriodCondition();
        if (periodCondition != null) {
            this.dateFilter = toDateFilter(periodCondition);
            this.periodFieldKeyList = toPeriodFieldKeyList(corpusMetadata, periodCondition);
            this.onCreationDate = periodCondition.isOnCreationDate();
            this.onModificationDate = periodCondition.isOnModificationDate();
        } else {
            this.dateFilter = null;
            this.periodFieldKeyList = null;
            this.onCreationDate = false;
            this.onModificationDate = false;
        }
    }

    void filter(FichePointeur fichePointeur, FilteredCroisementBuilder builder) {
        if (!isSelected(fichePointeur)) {
            return;
        }
        builder.testCondition(croisementCondition);
    }

    boolean isSelected(FichePointeur fichePointeur) {
        FicheMeta ficheMeta = (FicheMeta) fichePointeur.getCurrentSubsetItem();
        switch (ficheQuery.getDiscardFilter()) {
            case FicheQuery.DISCARDFILTER_NONE:
                if (ficheMeta.isDiscarded()) {
                    return false;
                }
                break;
            case FicheQuery.DISCARDFILTER_ONLY:
                if (!ficheMeta.isDiscarded()) {
                    return false;
                }
                break;
        }
        RangeCondition rangeCondition = ficheQuery.getIdRangeCondition();
        if (rangeCondition != null) {
            if (!rangeCondition.accept(ficheMeta.getId())) {
                return false;
            }
        }
        switch (userFilter) {
            case UserCondition.FILTER_SOME:
                if (!isRedacteurSelected(ficheMeta)) {
                    return false;
                }
                break;
            case UserCondition.FILTER_ANY:
                if (ficheMeta.getRedacteurGlobalIdList().isEmpty()) {
                    return false;
                }
                break;
            case UserCondition.FILTER_NONE:
                if (!ficheMeta.getRedacteurGlobalIdList().isEmpty()) {
                    return false;
                }
                break;
        }
        if (relationPredicate != null) {
            if (!relationPredicate.test(ficheMeta)) {
                return false;
            }
        }
        if (fieldTest != null) {
            if (!fieldTest.success(fichePointeur)) {
                return false;
            }
        }
        if (ficheQuery.isWithGeoloc()) {
            if (geolocalisationFieldKey == null) {
                return false;
            } else {
                Object obj = fichePointeur.getValue(geolocalisationFieldKey);
                if ((obj == null) || (!(obj instanceof GeopointItem))) {
                    return false;
                }
            }
        }
        if (dateFilter != null) {
            if (!testDateFilter(fichePointeur, ficheMeta)) {
                return false;
            }
        }
        return true;
    }

    private boolean testDateFilter(FichePointeur fichePointeur, FicheMeta ficheMeta) {
        if (onCreationDate) {
            if (dateFilter.containsDate(ficheMeta.getCreationDate())) {
                return true;
            }
        }
        if (onModificationDate) {
            if (dateFilter.containsDate(ficheMeta.getModificationDate())) {
                return true;
            }
        }
        if (periodFieldKeyList != null) {
            for (FieldKey fieldKey : periodFieldKeyList) {
                Object value = fichePointeur.getValue(fieldKey);
                if (value != null) {
                    if (value instanceof DateItem) {
                        if (dateFilter.containsDate(((DateItem) value).getDate())) {
                            return true;
                        }
                    } else if (value instanceof FicheItems) {
                        for (FicheItem ficheItem : (FicheItems) value) {
                            if (ficheItem instanceof DateItem) {
                                if (dateFilter.containsDate(((DateItem) ficheItem).getDate())) {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
        }
        return false;
    }

    static FicheSelectEngine newInstance(CorpusMetadata corpusMetadata, FicheQuery ficheQuery, CroisementCondition croisementCondition, SelectionContext selectionContext, Predicate<FicheMeta> motcleConditionPredicate) {
        FieldTest fieldTest = null;
        FieldContentCondition fieldContentCondition = ficheQuery.getFieldContentCondition();
        if (fieldContentCondition != null) {
            String scope = fieldContentCondition.getScope();
            FieldKey[] fieldKeyArray = null;
            switch (scope) {
                case FieldContentCondition.TITLE_SCOPE:
                    fieldKeyArray = titreScopeArray;
                    break;
                case FieldContentCondition.HEAD_SCOPE:
                    fieldKeyArray = CorpusMetadataUtils.getEnteteFieldKeyArray(corpusMetadata);
                    break;
                case FieldContentCondition.FICHE_SCOPE:
                    fieldKeyArray = CorpusMetadataUtils.getFicheFieldKeyArray(corpusMetadata);
                    break;
                case FieldContentCondition.SELECTION_SCOPE:
                    List<FieldKey> fieldKeyList = fieldContentCondition.getFieldKeyList();
                    fieldKeyArray = fieldKeyList.toArray(new FieldKey[fieldKeyList.size()]);
                    break;
                default:
                    throw new SwitchException("scope: " + scope);
            }
            fieldTest = FieldTestFactory.newFieldTest(fieldContentCondition.getTextCondition(), selectionContext, fieldKeyArray);
        }
        return new FicheSelectEngine(corpusMetadata, ficheQuery, croisementCondition, fieldTest, motcleConditionPredicate);
    }

    private boolean isRedacteurSelected(FicheMeta ficheMeta) {
        if (sphereArray != null) {
            for (Sphere sphere : sphereArray) {
                if (SphereUtils.ownsToSphere(ficheMeta, sphere)) {
                    return true;
                }
            }
        }
        if (redacteurArray != null) {
            for (Redacteur redacteur : redacteurArray) {
                if (ficheMeta.belongsTo(redacteur.getGlobalId())) {
                    return true;
                }
            }
        }
        return false;
    }

    private static Object[] initArrays(Fichotheque fichotheque, UserCondition userCondition) {
        List<Redacteur> redacteurList = new ArrayList<Redacteur>();
        List<Sphere> sphereList = new ArrayList<Sphere>();
        for (UserCondition.Entry entry : userCondition.getEntryList()) {
            Sphere sphere = FichothequeUtils.getSphere(fichotheque, entry.getSphereName());
            if (sphere != null) {
                if (entry instanceof UserCondition.LoginEntry) {
                    Redacteur redacteur = sphere.getRedacteurByLogin(((UserCondition.LoginEntry) entry).getLogin());
                    if (redacteur != null) {
                        redacteurList.add(redacteur);
                    }
                } else if (entry instanceof UserCondition.IdEntry) {
                    Redacteur redacteur = sphere.getRedacteurById(((UserCondition.IdEntry) entry).getId());
                    if (redacteur != null) {
                        redacteurList.add(redacteur);
                    }
                } else {
                    sphereList.add(sphere);
                }
            }
        }
        Object[] result = new Object[2];
        result[0] = redacteurList.toArray(new Redacteur[redacteurList.size()]);
        result[1] = sphereList.toArray(new Sphere[sphereList.size()]);
        return result;
    }

    private static List<FieldKey> toPeriodFieldKeyList(CorpusMetadata corpusMetadata, PeriodCondition periodCondition) {
        List<FieldKey> fieldKeyList = periodCondition.getFieldKeyList();
        if (fieldKeyList.isEmpty()) {
            return null;
        }
        List<FieldKey> list = new ArrayList<FieldKey>();
        for (FieldKey fieldKey : fieldKeyList) {
            if (corpusMetadata.getCorpusField(fieldKey) != null) {
                list.add(fieldKey);
            }
        }
        if (list.isEmpty()) {
            return null;
        }
        return list;
    }

    private static DateFilter toDateFilter(PeriodCondition periodCondition) {
        String startType = periodCondition.getStartType();
        String endType = periodCondition.getEndType();
        if (startType.equals(PeriodCondition.DATE_TYPE)) {
            FuzzyDate startDate = periodCondition.getStartDate();
            switch (endType) {
                case PeriodCondition.SAME_TYPE:
                    return DateFilterFactory.newInstance(startDate);
                case PeriodCondition.DATE_TYPE:
                    return DateFilterFactory.newInstance(startDate, periodCondition.getEndDate());
                case PeriodCondition.ANY_TYPE:
                    return DateFilterFactory.newMinimumFilter(startDate);
                default:
                    throw new SwitchException("Unknown PeriodCondition.getEndType() value: " + endType);
            }
        } else if (startType.equals(PeriodCondition.ANY_TYPE)) {
            switch (endType) {
                case PeriodCondition.SAME_TYPE:
                case PeriodCondition.ANY_TYPE:
                    return DateFilterUtils.NOTNULL_FILTER;
                case PeriodCondition.DATE_TYPE:
                    return DateFilterFactory.newMaximumFilter(periodCondition.getEndDate());
                default:
                    throw new SwitchException("Unknown PeriodCondition.getEndType() value: " + endType);
            }
        } else {
            throw new SwitchException("Unknown PeriodCondition.getStartType() value: " + startType);
        }
    }

}
