package eu.dnetlib.functionality.lightui.utils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import org.z3950.zing.cql.CQLAndNode;
import org.z3950.zing.cql.CQLBBQNode;
import org.z3950.zing.cql.CQLNode;
import org.z3950.zing.cql.CQLParseException;
import org.z3950.zing.cql.CQLTermNode;
import org.z3950.zing.cql.ModifierSet;

public abstract class AbstractQueryBuilder implements QueryBuilder {

	/**
	 * True if bbqs are potentially enabled.
	 */
	protected boolean useBbqs = false;

	/**
	 * matches all documents.
	 */
	private String defaultQuery = "textual";

	/**
	 * Handles each single field and generates a piece of the final CQL tree for it.
	 *
	 * @param nodes
	 *            collection of nodes which will be ANDed together
	 * @param key
	 *            field key
	 * @param value
	 *            field value
	 * @throws CQLParseException
	 *             could happen
	 * @throws IOException
	 *             could happen
	 */
	protected abstract void handleField(final Collection<CQLNode> nodes, final String key, final String value) throws CQLParseException, IOException;

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.lightui.utils.QueryBuilder#getQuery(javax.servlet.http.HttpServletRequest)
	 */
	public CQLNode getQuery(final Map<String, String[]> params) throws CQLParseException, IOException {

		final Collection<CQLNode> nodes = new ArrayList<CQLNode>();

		for (final Map.Entry<String, String[]> entry : params.entrySet()) {
			String value = entry.getValue()[0];
			String key = entry.getKey().toLowerCase();
			
			if (key.startsWith("_")) continue;
			
			if (value.contains("||")) {
				value = value.substring(0, value.indexOf("||"));
			}
			
			if (value.equals("")) continue;

			handleField(nodes, key, value);
		}

		return buildConjunction(nodes);
	}

	/**
	 * Given a list of nodes return a node chaining all these nodes in AND clauses.
	 *
	 * <p>
	 * Particular care is devoted to the handling of optional BBQs when there is only one term (e.g. an term-less search
	 * inside a collection, where the first term is the collection itself), in this case the query is augmented with a
	 * useless " .. and (textual)" which is an artifact used to only pass the driver.bbq CQL qualifier to the index.
	 * </p>
	 *
	 * @param nodes
	 *            list of nodes
	 * @return AND node
	 * @throws IOException
	 * @throws CQLParseException
	 */
	protected CQLNode buildConjunction(final Collection<CQLNode> nodes) throws CQLParseException, IOException {
		CQLNode query = null;
		
		CQLTermNode wildcardNode = new CQLTermNode(null, null, defaultQuery);
		
		for (final CQLNode node : nodes)
			query = chainNode(query, node);

		if (isUseBbqs() && nodes.size() == 1 && query instanceof CQLBBQNode)
			query = chainNode(wildcardNode, query);

		if(query == null)
			return wildcardNode;

		return query;
	}

	/**
	 * Create an AND chain by chaining query <b>after</b> (i.e putting 'node' in front). Otherwise unnecessary
	 * parentheses will be generated in the zing CQL serializer.
	 *
	 * <p>CQLBBQNodes are recognized and ANDed with the special driver driver.bbq CQL qualifier.
	 * </p>
	 *
	 * @param query
	 *            rest of the query
	 * @param node
	 *            node
	 * @return and node.
	 */
	private CQLNode chainNode(final CQLNode query, final CQLNode node) {
		if (query == null)
			return node;

		final ModifierSet modifier = new ModifierSet("and");
		if (isUseBbqs() && node instanceof CQLBBQNode)
			modifier.addModifier("driver.bbq", "=", ((CQLBBQNode) node).getBbqName());

		return new CQLAndNode(node, query, modifier);
	}

	public boolean isUseBbqs() {
		return useBbqs;
	}

	public void setUseBbqs(boolean useBbqs) {
		this.useBbqs = useBbqs;
	}

	public String getDefaultQuery() {
		return defaultQuery;
	}

	public void setDefaultQuery(String defaultQuery) {
		this.defaultQuery = defaultQuery;
	}

}
