package eu.dnetlib.functionality.lightui.web;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.annotation.Resource;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;

import eu.dnetlib.data.index.IIndexService;
import eu.dnetlib.enabling.tools.ServiceLocator;
import eu.dnetlib.functionality.lightui.utils.EPRUtil;
import eu.dnetlib.functionality.lightui.utils.ResultSet;
import eu.dnetlib.functionality.lightui.utils.ResultSetFactory;

public class AsynchronousSearchEngine implements SearchEngine {

	protected Log log = LogFactory.getLog(AsynchronousSearchEngine.class);

	protected ServiceLocator<IIndexService> indexLocator;

	protected ExecutorService executor;

	protected Cache resultSetByQueryCache;
	protected Cache resultSetByEprCache;
	
	private boolean withHighlight;

	private String defaultQuery;
	
	@Resource(name="lightuiResultSetFactory")
	protected ResultSetFactory resultSetFactory;

	public AsynchronousSearchEngine() {
		executor = Executors.newFixedThreadPool(8);
	}

	public ResultSet resultSetForQueryOrEpr(String query, String metadataFormat, W3CEndpointReference epr) {
		if (epr == null) {
			try {
				String q = "";
				if (withHighlight && !query.trim().equalsIgnoreCase(defaultQuery.trim()))
					q = ">solr=NAMESPACE solr.hl=true and ";
				q += query;
				
				ResultSet rs = getFutureResultSetByQuery(q, metadataFormat).get();
				setResultSetByEpr(rs.getEpr(), rs);

				log.debug("executing query:" + q);
				
				return rs;
			} catch (InterruptedException e) {
				log.fatal(e);
				return null;
			} catch (ExecutionException e) {
				log.fatal(e);
				return null;
			}
		} else {
			return getResultSetByEpr(epr);
		}
	}

	public Future<ResultSet> prepareQuery(final String cqlQuery, final String metadataFormat) {
		log.info("SUBMITTING ASYNC");
		Future<ResultSet> rs = executor.submit(new Callable<ResultSet>() {
			public ResultSet call() {
				ResultSet rs = null;
				try {
					rs = createResultSet(indexLocator.getService().indexLookup("all", cqlQuery, metadataFormat, "index"));
					rs.getNumberOfElements();
				} catch (Exception e) {
					log.error("Error prefetching page", e);
				}
				// prefetch
				log.debug("ASYNC FINISHED");
				return rs;
			}
		});

		setFutureResultSetByQuery(cqlQuery, rs);
		
		return rs;
	}

	/**
	 * Creates a new resultset instance for a given resultset. This method can be hooked for injecting mocks during
	 * testing // TODO: use a factory?
	 *
	 * @param epr
	 * @return
	 */
	protected ResultSet createResultSet(W3CEndpointReference epr) {
		return resultSetFactory.newInstance(epr);
	}

	@SuppressWarnings("unchecked")
	protected Future<ResultSet> getFutureResultSetByQuery(final String cqlQuery, final String metadataFormat) {
		Element element = resultSetByQueryCache.get(cqlQuery);
		if (element != null) {
			return (Future<ResultSet>) element.getObjectValue();
		}
		log.info("no future resultset for query: " + cqlQuery);
		return prepareQuery(cqlQuery, metadataFormat);
	}

	protected void setFutureResultSetByQuery(String query, Future<ResultSet> future) {
		resultSetByQueryCache.put(new Element(query, future));
	}

	protected ResultSet getResultSetByEpr(W3CEndpointReference epr) {
		return (ResultSet) resultSetByEprCache.get(getEprKey(epr)).getObjectValue();
	}

	protected void setResultSetByEpr(W3CEndpointReference epr, ResultSet rs) {
		resultSetByEprCache.put(new Element(getEprKey(epr), rs));
	}

	protected String getEprKey(W3CEndpointReference epr) {
		String rsId = EPRUtil.getResourceIdentifier(epr);
		String address = EPRUtil.getAddress(epr);
		return address + "." + rsId;
	}

	public ServiceLocator<IIndexService> getIndexLocator() {
		return indexLocator;
	}

	@Required
	public void setIndexLocator(ServiceLocator<IIndexService> indexLocator) {
		this.indexLocator = indexLocator;
	}

	public ExecutorService getExecutor() {
		return executor;
	}

	public void setExecutor(ExecutorService executor) {
		this.executor = executor;
	}

	protected Cache getResultSetByQueryCache() {
		return resultSetByQueryCache;
	}

	public void setResultSetByQueryCache(Cache resultSetByQueryCache) {
		this.resultSetByQueryCache = resultSetByQueryCache;
	}

	protected Cache getResultSetByEprCache() {
		return resultSetByEprCache;
	}

	public void setResultSetByEprCache(Cache resultSetByEprCache) {
		this.resultSetByEprCache = resultSetByEprCache;
	}

	@Required
	public void setWithHighlight(boolean withHighlight) {
		this.withHighlight = withHighlight;
	}

	public boolean isWithHighlight() {
		return withHighlight;
	}

	public String getDefaultQuery() {
		return defaultQuery;
	}

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

}
