package eu.dnetlib.functionality.index.query;

import java.util.List;
import java.util.Map;

import com.google.common.collect.BiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import eu.dnetlib.data.provision.index.rmi.IndexServiceException;
import eu.dnetlib.functionality.cql.CqlTranslator;
import eu.dnetlib.functionality.cql.lucene.TranslatedQuery;
import eu.dnetlib.functionality.index.client.IndexClient;
import eu.dnetlib.functionality.index.client.IndexClientException;
import eu.dnetlib.functionality.index.query.Pruner.Result;
import eu.dnetlib.functionality.index.utils.MetadataReference;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * A factory for creating IndexQuery objects.
 */
public abstract class IndexQueryFactory {

	/**
	 * Query tree pruner.
	 */
	private Pruner pruner;

	/**
	 * Query tree pruner. Collects parameters which affect the semantic of the cql parser.
	 */
	private Pruner cqlPruner;

	/** The default query params. */
	private Map<String, List<String>> defaultQueryParams;

	/** CqlTranslator. */
	@Autowired
	private CqlTranslator translator;

	/** The browse aliases. */
	@Autowired
	private BrowseAliases browseAliases;

	/** The weights. */
	@Autowired
	private Weights weights;

	/**
	 * New instance.
	 * 
	 * @param cql
	 *            the cql
	 * @param res
	 *            the res
	 * @param queryLanguage
	 *            the query language
	 * @return the index query
	 */
	protected abstract IndexQuery newInstance(final TranslatedQuery cql, final Result res, final QueryLanguage queryLanguage);

	/**
	 * Sets the query options.
	 * 
	 * @param indexQuery
	 *            the index query
	 * @return the index query
	 */
	protected abstract IndexQuery setQueryOptions(final IndexQuery indexQuery, final IndexClient client);

	/**
	 * Gets the index query.
	 * 
	 * @param lang
	 *            the lang
	 * @param query
	 *            the query
	 * @param mdRef
	 *            the md ref
	 * @return the index query
	 * @throws IndexServiceException
	 *             the index service exception
	 */
	public IndexQuery getIndexQuery(final QueryLanguage lang, final String query, final IndexClient client, final MetadataReference mdRef)
			throws IndexClientException {

		String myquery = query;

		if ((myquery == null) || myquery.isEmpty()) throw new IndexClientException("query cannot be empty or null");

		try {
			final Result cqlRes = getCqlPruner().prune(getCqlPruner().parse(myquery));
			final Result res = getPruner().prune(cqlRes.getNode());

			final TranslatedQuery tQuery = translator.getTranslatedQuery(res.getNode(), client.getCqlValueTransformerMap(mdRef),
					overrideCqlParams(cqlRes.getOptionMap()), browseAliases.get(mdRef), weights.get(mdRef));

			return setQueryOptions(newInstance(tQuery, res, lang), client);
		} catch (Exception e) {
			throw new IndexClientException(e);
		}
	}

	/**
	 * Method overrides the default values in the defaultQueryParams with the given override map.
	 * 
	 * @param override
	 *            the map containing the override values.
	 * @return the overridden parameter map
	 * 
	 */
	private Map<String, List<String>> overrideCqlParams(final Map<String, List<String>> override) {
		Map<String, List<String>> cqlParams = Maps.newHashMap();
		cqlParams.putAll(getDefaultQueryParams());
		cqlParams.putAll(override);
		return cqlParams;
	}

	public List<String> getBrowsableFields(final List<String> fields, final MetadataReference mdRef) throws IndexClientException {
		return getBrowsableFields(fields, browseAliases.get(mdRef));
	}

	/**
	 * Gets the list of aliases available for browse
	 * 
	 * @param fields
	 *            list of input fields
	 * @param aliases
	 *            key= non-browasbale-field-name, value=browsable-field-name
	 * @return the list of browasable field names
	 */
	public List<String> getBrowsableFields(final List<String> fields, final BiMap<String, String> aliases) {
		List<String> browsables = Lists.newArrayListWithExpectedSize(fields.size());
		for (String f : fields) {
			if (aliases.containsKey(f)) {
				browsables.add(aliases.get(f));
			} else {
				browsables.add(f);
			}
		}
		return browsables;
	}

	/**
	 * Gets the pruner.
	 * 
	 * @return the pruner
	 */
	public Pruner getPruner() {
		return pruner;
	}

	/**
	 * Sets the pruner.
	 * 
	 * @param pruner
	 *            the new pruner
	 */
	public void setPruner(final Pruner pruner) {
		this.pruner = pruner;
	}

	/**
	 * Gets the cql pruner.
	 * 
	 * @return the cql pruner
	 */
	public Pruner getCqlPruner() {
		return cqlPruner;
	}

	/**
	 * Sets the cql pruner.
	 * 
	 * @param cqlPruner
	 *            the new cql pruner
	 */
	public void setCqlPruner(final Pruner cqlPruner) {
		this.cqlPruner = cqlPruner;
	}

	/**
	 * Gets the default query params.
	 * 
	 * @return the default query params
	 */
	public Map<String, List<String>> getDefaultQueryParams() {
		return defaultQueryParams;
	}

	/**
	 * Sets the default query params.
	 * 
	 * @param defaultQueryParams
	 *            the default query params
	 */
	public void setDefaultQueryParams(final Map<String, List<String>> defaultQueryParams) {
		this.defaultQueryParams = defaultQueryParams;
	}

}
