/*
 * This file is part of ***  M y C o R e  ***
 * See http://www.mycore.de/ for details.
 *
 * MyCoRe is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MyCoRe is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.mycore.solr.search;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.StringTokenizer;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.filter.ElementFilter;
import org.mycore.common.MCRUsageException;
import org.mycore.parsers.bool.MCRAndCondition;
import org.mycore.parsers.bool.MCRCondition;
import org.mycore.parsers.bool.MCROrCondition;
import org.mycore.parsers.bool.MCRSetCondition;
import org.mycore.services.fieldquery.MCRQuery;
import org.mycore.services.fieldquery.MCRQueryCondition;
import org.mycore.services.fieldquery.MCRQueryParser;
import org.mycore.services.fieldquery.MCRSortBy;

import jakarta.servlet.http.HttpServletRequest;

public class MCRQLSearchUtils {

    private static final Logger LOGGER = LogManager.getLogger(MCRQLSearchUtils.class);

    private static HashSet<String> SEARCH_PARAMETER = new HashSet<>(Arrays.asList("search", "query", "maxResults",
        "numPerPage", "page", "mask", "mode", "redirect", "qt"));

    /**
     * Build MCRQuery from editor XML input
     */
    public static MCRQuery buildFormQuery(Element root) {
        Element conditions = root.getChild("conditions");

        if (conditions.getAttributeValue("format", "xml").equals("xml")) {
            Element condition = conditions.getChildren().get(0);
            renameElements(condition);

            // Remove conditions without values
            List<Element> empty = new ArrayList<>();
            for (Iterator<Element> it = conditions.getDescendants(new ElementFilter("condition")); it.hasNext();) {
                Element cond = it.next();
                if (cond.getAttribute("value") == null) {
                    empty.add(cond);
                }
            }

            // Remove empty sort conditions
            Element sortBy = root.getChild("sortBy");
            if (sortBy != null) {
                for (Element field : sortBy.getChildren("field")) {
                    if (field.getAttributeValue("name", "").length() == 0) {
                        empty.add(field);
                    }
                }
            }

            for (int i = empty.size() - 1; i >= 0; i--) {
                empty.get(i).detach();
            }

            if (sortBy != null && sortBy.getChildren().size() == 0) {
                sortBy.detach();
            }

            // Remove empty returnFields
            Element returnFields = root.getChild("returnFields");
            if (returnFields != null && returnFields.getText().length() == 0) {
                returnFields.detach();
            }
        }

        return MCRQuery.parseXML(root.getDocument());
    }

    /**
     * Rename elements conditionN to condition. Transform condition with multiple child values to OR-condition.
     */
    protected static void renameElements(Element element) {
        if (element.getName().startsWith("condition")) {
            element.setName("condition");

            String field = new StringTokenizer(element.getAttributeValue("field"), " -,").nextToken();
            String operator = element.getAttributeValue("operator");
            if (operator == null) {
                LOGGER.warn("No operator defined for field: {}", field);
                operator = "=";
            }
            element.setAttribute("operator", operator);

            List<Element> values = element.getChildren("value");
            if (values != null && values.size() > 0) {
                element.removeAttribute("field");
                element.setAttribute("operator", "or");
                element.setName("boolean");
                for (Element value : values) {
                    value.setName("condition");
                    value.setAttribute("field", field);
                    value.setAttribute("operator", operator);
                    value.setAttribute("value", value.getText());
                    value.removeContent();
                }
            }
        } else if (element.getName().startsWith("boolean")) {
            element.setName("boolean");
            element.getChildren().forEach(MCRQLSearchUtils::renameElements);
        }
    }

    /**
     * Search using complex query expression given as text string
     */
    public static MCRQuery buildComplexQuery(String query) {
        return new MCRQuery(new MCRQueryParser().parse(query));
    }

    /**
     * Search in default search field specified by MCR.SearchServlet.DefaultSearchField
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static MCRQuery buildDefaultQuery(String search, String defaultSearchField) {
        String[] fields = defaultSearchField.split(" *, *");
        MCROrCondition queryCondition = new MCROrCondition();

        for (String fDef : fields) {
            MCRCondition condition = new MCRQueryCondition(fDef, "=", search);
            queryCondition.addChild(condition);
        }

        return new MCRQuery(MCRQueryParser.normalizeCondition(queryCondition));
    }

    /**
     * Search using name=value pairs from HTTP request
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static MCRQuery buildNameValueQuery(HttpServletRequest req) {
        MCRAndCondition condition = new MCRAndCondition();

        for (Enumeration<String> names = req.getParameterNames(); names.hasMoreElements();) {
            String name = names.nextElement();
            if (name.endsWith(".operator")) {
                continue;
            }
            if (name.contains(".sortField")) {
                continue;
            }
            if (SEARCH_PARAMETER.contains(name)) {
                continue;
            }
            if (name.startsWith("XSL.")) {
                continue;
            }

            String[] values = req.getParameterValues(name);
            MCRSetCondition parent = condition;

            if ((values.length > 1) || name.contains(",")) {
                // Multiple fields with same name, combine with OR
                parent = new MCROrCondition();
                condition.addChild(parent);
            }

            for (String fieldName : name.split(",")) {
                String operator = getReqParameter(req, fieldName + ".operator", "=");
                for (String value : values) {
                    parent.addChild(new MCRQueryCondition(fieldName, operator, value));
                }
            }
        }

        if (condition.getChildren().isEmpty()) {
            throw new MCRUsageException("Missing query condition");
        }

        return new MCRQuery(MCRQueryParser.normalizeCondition(condition));
    }

    protected static Document setQueryOptions(MCRQuery query, HttpServletRequest req) {
        String maxResults = getReqParameter(req, "maxResults", "0");
        query.setMaxResults(Integer.parseInt(maxResults));

        List<String> sortFields = new ArrayList<>();
        for (Enumeration<String> names = req.getParameterNames(); names.hasMoreElements();) {
            String name = names.nextElement();
            if (name.contains(".sortField")) {
                sortFields.add(name);
            }
        }

        if (sortFields.size() > 0) {
            sortFields.sort((arg0, arg1) -> {
                String s0 = arg0.substring(arg0.indexOf(".sortField"));
                String s1 = arg1.substring(arg1.indexOf(".sortField"));
                return s0.compareTo(s1);
            });
            List<MCRSortBy> sortBy = new ArrayList<>();
            for (String name : sortFields) {
                String sOrder = getReqParameter(req, name, "ascending");
                boolean order = Objects.equals(sOrder, "ascending") ? MCRSortBy.ASCENDING : MCRSortBy.DESCENDING;
                String fieldName = name.substring(0, name.indexOf(".sortField"));
                sortBy.add(new MCRSortBy(fieldName, order));
            }
            query.setSortBy(sortBy);
        }

        Document xml = query.buildXML();
        xml.getRootElement().setAttribute("numPerPage", getReqParameter(req, "numPerPage", "0"));
        xml.getRootElement().setAttribute("mask", getReqParameter(req, "mask", "-"));
        return xml;
    }

    protected static String getReqParameter(HttpServletRequest req, String name, String defaultValue) {
        String value = req.getParameter(name);
        if (value == null || value.trim().length() == 0) {
            return defaultValue;
        } else {
            return value.trim();
        }
    }
}
