package it.cnr.isti.driver.web;

import it.cnr.isti.driver.utils.ODL_EPR;
import it.cnr.isti.driver.utils.ResultSet;
import it.cnr.isti.driver.utils.SearchServiceLocator;

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 net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class AsynchronousSearchEngine implements SearchEngine {

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

	protected SearchServiceLocator searchLocator;

	protected ExecutorService executor;

	protected Cache resultSetByQueryCache;
	protected Cache resultSetByEprCache;

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

	public ResultSet resultSetForQueryOrEpr(String query, ODL_EPR epr) {
		if (epr == null) {
			try {
				ResultSet rs = getFutureResultSetByQuery(query).get();
				setResultSetByEpr(rs.getEpr(), rs);
				return rs;
			} catch (InterruptedException e) {
				log.fatal(e);
				return null;
			} catch (ExecutionException e) {
				log.fatal(e);
				return null;
			}
		} else {
			return getResultSetByEpr(epr);
		}
	}

	public void prepareQuery(final String cqlQuery) {
		Future<ResultSet> rs = executor.submit(new Callable<ResultSet>() {
			public ResultSet call() {
				ResultSet rs = createResultSet(searchLocator.getService()
						.search(cqlQuery));
				// prefetch
				rs.getNumberOfElements();
				return rs;
			}
		});

		setFutureResultSetByQuery(cqlQuery, 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(ODL_EPR epr) {
		return new ResultSet(epr);
	}

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

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

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

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

	protected String getEprKey(ODL_EPR epr) {
		return epr.getAddress() + "." + epr.getResourceIdentifier();
	}

	public SearchServiceLocator getSearchLocator() {
		return searchLocator;
	}

	public void setSearchLocator(SearchServiceLocator searchLocator) {
		this.searchLocator = searchLocator;
	}

	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;
	}

}
