/* BdfServer - 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 fr.exemole.bdfserver.tools.interaction;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.users.BdfUser;
import java.text.ParseException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.selection.CroisementCondition;
import net.fichotheque.selection.FicheCondition;
import net.fichotheque.selection.FicheQuery;
import net.fichotheque.selection.FieldContentCondition;
import net.fichotheque.selection.MotcleCondition;
import net.fichotheque.selection.MotcleQuery;
import net.fichotheque.selection.PeriodCondition;
import net.fichotheque.selection.RangeCondition;
import net.fichotheque.sphere.Redacteur;
import net.fichotheque.tools.selection.CroisementConditionBuilder;
import net.fichotheque.tools.selection.FicheQueryBuilder;
import net.fichotheque.tools.selection.MotcleQueryBuilder;
import net.fichotheque.tools.selection.PeriodConditionBuilder;
import net.fichotheque.tools.selection.UserConditionBuilder;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.SelectionUtils;
import net.fichotheque.utils.SphereUtils;
import net.fichotheque.utils.selection.RangeConditionBuilder;
import net.mapeadores.util.conditions.ConditionsConstants;
import net.mapeadores.util.conditions.ConditionsUtils;
import net.mapeadores.util.conditions.TextCondition;
import net.mapeadores.util.primitives.FuzzyDate;
import net.mapeadores.util.primitives.RangeUtils;
import net.mapeadores.util.primitives.Ranges;
import net.mapeadores.util.request.RequestMap;
import net.mapeadores.util.text.StringUtils;


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

    public final static String ID_PARAMNAME = "id";
    public final static String CORPUS_ALL_PARAMNAME = "corpus_all";
    public final static String FICHE_LIST_PARAMNAME = "fiche_list";
    public final static String FICHE_LOGICALOPERATOR_PARAMNAME = "fiche_logicaloperator";
    public final static String FIELDCONTENT_PARAMNAME = "fieldcontent";
    public final static String FIELDCONTENT_OPERATOR_PARAMNAME = "fieldcontent_operator";
    public final static String FIELDCONTENT_SCOPE_PARAMNAME = "fieldcontent_scope";
    public final static String FIELDCONTENT_FIELDSELECTION_PARAMNAME = "fieldcontent_fieldselection";
    public final static String DISCARDFILTER_PARAMNAME = "discardfilter";
    public final static String USERS_ALL_PARAMNAME = "users_all";
    public final static String USERS_SELECTION_PARAMNAME = "users_selection";
    public final static String CORPUS_SELECTION_PARAMNAME = "corpus_selection";
    public final static String MOTCLE_ALL_PREFIX = "motcle_all_";
    public final static String MOTCLE_CONTENTOPERATOR_PREFIX = "motcle_contentoperator_";
    public final static String MOTCLE_INPUT_PREFIX = "motcle_input_";
    public final static String MOTCLE_SELECTION_PREFIX = "motcle_selection_";
    public final static String MOTCLE_SATELLITE_PREFIX = "motcle_satellite_";
    public final static String MOTCLE_LOGICALOPERATOR_PARAMNAME = "motcle_logicaloperator";
    public final static String PERIOD_START_PARAMNAME = "period_start";
    public final static String PERIOD_END_PARAMNAME = "period_end";
    public final static String PERIOD_SCOPE_PARAMNAME = "period_scope";
    public final static String PERIOD_FIELDLIST_PARAMNAME = "period_fieldlist";
    public final static String CREATION_PARAMVALUE = "creation";
    public final static String MODIFICATION_PARAMVALUE = "modification";
    public final static String FIELDLIST_PARAMVALUE = "fieldlist";
    private final static Set<FieldKey> EMPTY_FIELDKEYSET = Collections.emptySet();
    private final RequestMap requestMap;
    private final BdfServer bdfServer;
    private final BdfUser bdfUser;
    private final FicheQueryBuilder queryBuilder;

    private FicheQueryParser(RequestMap requestMap, BdfServer bdfServer, BdfUser bdfUser) {
        this.bdfServer = bdfServer;
        this.bdfUser = bdfUser;
        this.requestMap = requestMap;
        queryBuilder = new FicheQueryBuilder();
        parseId();
        parseCorpus();
        parseDiscardFilter();
        parseFieldContentCondition();
        parseUsers();
        parsePeriod();
        parseMotcles();
        parseFicheCondition();
    }

    public static FicheQuery parse(RequestMap requestMap, BdfServer bdfServer, BdfUser bdfUser) {
        FicheQueryParser parser = new FicheQueryParser(requestMap, bdfServer, bdfUser);
        return parser.queryBuilder.toFicheQuery();
    }

    private void parseId() {
        String rangeString = requestMap.getTrimedParameter(ID_PARAMNAME);
        if (!rangeString.isEmpty()) {
            Ranges ranges = RangeUtils.positiveRangeParse(rangeString);
            if (ranges != null) {
                queryBuilder.setIdRangeCondition(RangeConditionBuilder.init(ranges).toRangeCondition());
            }
        }
    }

    private void parseCorpus() {
        if (!requestMap.isTrue(CORPUS_ALL_PARAMNAME)) {
            String[] selectioncorpus = requestMap.getParameterValues(CORPUS_SELECTION_PARAMNAME);
            if ((selectioncorpus != null) && (selectioncorpus.length > 0)) {
                for (String name : selectioncorpus) {
                    try {
                        queryBuilder.addCorpus(SubsetKey.parse(SubsetKey.CATEGORY_CORPUS, name));
                    } catch (java.text.ParseException pe) {
                    }
                }
            }
        }
    }

    private void parseFieldContentCondition() {
        String contentOperator = ConditionsConstants.checkLogicalOperator(requestMap.getParameter(FIELDCONTENT_OPERATOR_PARAMNAME), ConditionsConstants.LOGICALOPERATOR_AND);
        TextCondition fieldCondition = ConditionsUtils.parseSimpleCondition(requestMap.getParameter(FIELDCONTENT_PARAMNAME), contentOperator);
        if ((fieldCondition != null) && (!ConditionsUtils.isNeutral(fieldCondition))) {
            String fieldContentScope = FieldContentCondition.TITLE_SCOPE;
            String scopeString = requestMap.getParameter(FIELDCONTENT_SCOPE_PARAMNAME);
            if (scopeString != null) {
                try {
                    fieldContentScope = FieldContentCondition.checkScope(scopeString);
                } catch (IllegalArgumentException iae) {
                }
            }
            Set<FieldKey> fieldKeySet = null;
            if (fieldContentScope.equals(FieldContentCondition.SELECTION_SCOPE)) {
                fieldKeySet = parseFieldKeySet(requestMap.getParameter(FIELDCONTENT_FIELDSELECTION_PARAMNAME));
                if (fieldKeySet.isEmpty()) {
                    fieldContentScope = FieldContentCondition.TITLE_SCOPE;
                }
            }
            queryBuilder.setFieldContentCondition(fieldCondition, fieldContentScope, fieldKeySet);
        }
    }

    private void parseDiscardFilter() {
        String discardFilter = FicheQuery.DISCARDFILTER_ALL;
        String discardParamValue = requestMap.getTrimedParameter(DISCARDFILTER_PARAMNAME);
        if (!discardParamValue.isEmpty()) {
            try {
                discardFilter = FicheQuery.checkDiscardFilter(discardParamValue);
            } catch (IllegalArgumentException iae) {

            }
        }
        queryBuilder.setDiscardFilter(discardFilter);
    }

    private void parseUsers() {
        if (requestMap.isTrue(USERS_ALL_PARAMNAME)) {
            return;
        }
        String selectionParamValue = requestMap.getTrimedParameter(USERS_SELECTION_PARAMNAME);
        if (selectionParamValue.isEmpty()) {
            return;
        }
        if (selectionParamValue.equals("*")) {
            queryBuilder.setUserCondition(SelectionUtils.ANY_USERCONDITION);
        } else if (selectionParamValue.equals("!*")) {
            queryBuilder.setUserCondition(SelectionUtils.NONE_USERCONDITION);
        } else {
            UserConditionBuilder userConditionBuilder = new UserConditionBuilder();
            SubsetKey defaultSphereKey = bdfUser.getRedacteur().getSubsetKey();
            String[] tokens = StringUtils.getTechnicalTokens(selectionParamValue, false);
            for (String token : tokens) {
                if (token.charAt(0) == '[') {
                    if (token.charAt(token.length() - 1) != ']') {
                        continue;
                    }
                    try {
                        SubsetKey sphereKey = SubsetKey.parse(SubsetKey.CATEGORY_SPHERE, token.substring(1, token.length() - 1));
                        userConditionBuilder.addSphere(sphereKey);
                    } catch (ParseException pe) {
                    }
                } else {
                    try {
                        Redacteur redacteur = SphereUtils.parse(bdfServer.getFichotheque(), token, defaultSphereKey);
                        userConditionBuilder.addId(redacteur);
                    } catch (SphereUtils.RedacteurLoginException rle) {
                    }
                }
            }
            if (!userConditionBuilder.isEmpty()) {
                queryBuilder.setUserCondition(userConditionBuilder.toUserCondition());
            }
        }
    }

    private void parsePeriod() {
        String startDateString = requestMap.getTrimedParameter(PERIOD_START_PARAMNAME);
        if (startDateString.isEmpty()) {
            return;
        }
        PeriodConditionBuilder builder = new PeriodConditionBuilder();
        if (startDateString.equals(PeriodCondition.ANY_CHAR)) {
            builder.setAnyStartDate();
        } else {
            try {
                FuzzyDate startDate = FuzzyDate.parse(startDateString);
                builder.setStartDate(startDate);
            } catch (ParseException pe) {
                return;
            }
        }
        String endDateString = requestMap.getTrimedParameter(PERIOD_END_PARAMNAME);
        if (!endDateString.isEmpty()) {
            endDateString = endDateString.trim();
            if (endDateString.equals(PeriodCondition.ANY_CHAR)) {
                builder.setAnyEndDate();
            } else {
                try {
                    FuzzyDate endDate = FuzzyDate.parse(endDateString);
                    builder.setEndDate(endDate);
                } catch (ParseException pe) {
                }
            }
        }
        String[] values = requestMap.getParameterValues(PERIOD_SCOPE_PARAMNAME);
        if (values != null) {
            for (String value : values) {
                if (value.equals(CREATION_PARAMVALUE)) {
                    builder.setOnCreationDate(true);
                } else if (value.equals(MODIFICATION_PARAMVALUE)) {
                    builder.setOnModificationDate(true);
                } else if (value.equals(FIELDLIST_PARAMVALUE)) {
                    builder.addFieldKeys(parseFieldKeySet(requestMap.getParameter(PERIOD_FIELDLIST_PARAMNAME)));
                }
            }
        }
        queryBuilder.setPeriodCondition(builder.toPeriodCondition());
    }

    private void parseMotcles() {
        queryBuilder.setMotcleLogicalOperator(getLogicalOperator(MOTCLE_LOGICALOPERATOR_PARAMNAME));
        SortedSet<Integer> suffixSet = new TreeSet<Integer>();
        for (String parameterName : requestMap.getParameterNameSet()) {
            if (parameterName.startsWith(MOTCLE_INPUT_PREFIX)) {
                int idx = parameterName.lastIndexOf('_');
                if (idx != -1) {
                    try {
                        int number = Integer.parseInt(parameterName.substring(idx + 1));
                        suffixSet.add(number);
                    } catch (NumberFormatException nfe) {
                    }
                }
            }
        }
        for (Integer itg : suffixSet) {
            MotcleCondition.Entry motcleConditionEntry = parseOptions(itg.toString());
            if (motcleConditionEntry != null) {
                queryBuilder.addMotcleConditionEntry(motcleConditionEntry);
            }
        }
    }

    private MotcleCondition.Entry parseOptions(String suffix) {
        String chmtcl = requestMap.getTrimedParameter(MOTCLE_INPUT_PREFIX + suffix);
        if (chmtcl.isEmpty()) {
            return null;
        }
        TextCondition motcleCondition = ConditionsUtils.parseCondition(chmtcl, getLogicalOperator(MOTCLE_CONTENTOPERATOR_PREFIX + suffix));
        if (ConditionsUtils.isNeutral(motcleCondition)) {
            return null;
        }
        MotcleQueryBuilder motcleQueryBuilder = MotcleQueryBuilder.init()
                .setContentCondition(motcleCondition, MotcleQuery.SCOPE_IDALPHA_ONLY);
        if (!requestMap.isTrue(MOTCLE_ALL_PREFIX + suffix)) {
            String[] selectionthesaurus = requestMap.getParameterValues(MOTCLE_SELECTION_PREFIX + suffix);
            if ((selectionthesaurus != null) && (selectionthesaurus.length > 0)) {
                String thesaurusIdList = StringUtils.implode(selectionthesaurus, ';');
                motcleQueryBuilder.addThesaurus(FichothequeUtils.toSubsetKeySet(SubsetKey.CATEGORY_THESAURUS, thesaurusIdList));
            }
        }
        boolean withMaster = requestMap.isTrue(MOTCLE_SATELLITE_PREFIX + suffix);
        return SelectionUtils.toMotcleConditionEntry(motcleQueryBuilder.toMotcleQuery(), null, withMaster);
    }

    private void parseFicheCondition() {
        String ficheList = requestMap.getTrimedParameter(FICHE_LIST_PARAMNAME);
        if (ficheList.isEmpty()) {
            return;
        }
        queryBuilder.setFicheLogicalOperator(getLogicalOperator(FICHE_LOGICALOPERATOR_PARAMNAME));
        String[] tokens = StringUtils.getTokens(ficheList, ';', StringUtils.EMPTY_EXCLUDE);
        for (String token : tokens) {
            try {
                FicheCondition.Entry entry = parseFicheEntry(token);
                queryBuilder.addFicheConditionQuery(entry);
            } catch (ParseException pe) {

            }
        }
    }

    private String getLogicalOperator(String paramName) {
        String logicalOperator = requestMap.getTrimedParameter(paramName);
        logicalOperator = ConditionsConstants.checkLogicalOperator(logicalOperator, ConditionsConstants.LOGICALOPERATOR_AND);
        return logicalOperator;
    }

    public static Set<FieldKey> parseFieldKeySet(String s) {
        if (s == null) {
            return EMPTY_FIELDKEYSET;
        }
        String[] tokens = StringUtils.getTechnicalTokens(s, true);
        int length = tokens.length;
        if (length == 0) {
            return EMPTY_FIELDKEYSET;
        }
        Set<FieldKey> fieldKeySet = new LinkedHashSet<FieldKey>();
        for (int i = 0; i < length; i++) {
            try {
                FieldKey fieldKey = FieldKey.parse(tokens[i]);
                fieldKeySet.add(fieldKey);
            } catch (ParseException pe) {
            }
        }
        return fieldKeySet;
    }

    private FicheCondition.Entry parseFicheEntry(String entryString) throws ParseException {
        int idx = entryString.indexOf('/');
        if (idx < 1) {
            throw new ParseException("no /", 0);
        }
        SubsetKey corpusKey = SubsetKey.parse(SubsetKey.CATEGORY_CORPUS, entryString.substring(0, idx));
        entryString = entryString.substring(idx + 1);
        String[] tokens = StringUtils.getTokens(entryString, '|', StringUtils.EMPTY_INCLUDE);
        int length = tokens.length;
        if (length == 0) {
            throw new ParseException("no id range", 0);
        }
        RangeCondition idRangeCondition = parseRangeCondition(tokens[0]);
        if (idRangeCondition == null) {
            throw new ParseException("false id range = " + tokens[0], idx + 1);
        }
        FicheQuery ficheQuery = FicheQueryBuilder.init()
                .addCorpus(corpusKey)
                .setIdRangeCondition(idRangeCondition)
                .toFicheQuery();
        CroisementCondition croisementCondition = null;
        if (length > 1) {
            CroisementConditionBuilder builder = CroisementConditionBuilder.init();
            for (String modeToken : StringUtils.getTechnicalTokens(tokens[1], true)) {
                builder.addLienMode(modeToken);
            }
            if (length > 2) {
                RangeCondition weightRangeCondition = parseRangeCondition(tokens[2]);
                if (weightRangeCondition != null) {
                    builder.setWeightRangeCondition(weightRangeCondition);
                }
            }
            if (!builder.isEmpty()) {
                croisementCondition = builder.toCroisementCondition();
            }
        }
        return SelectionUtils.toFicheConditionEntry(ficheQuery, croisementCondition, false);
    }

    private RangeCondition parseRangeCondition(String token) {
        int length = token.length();
        if (length == 0) {
            return null;
        }
        boolean exclude = false;
        if (token.charAt(0) == '!') {
            exclude = true;
            if (length == 1) {
                return null;
            }
            token = token.substring(1);
        }
        Ranges ranges = RangeUtils.positiveRangeParse(token);
        if (ranges == null) {
            return null;
        }
        return RangeConditionBuilder.init(ranges).setExclude(exclude).toRangeCondition();
    }

}
