package eu.dnetlib.dlms.jdbc.server;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.common.collect.Lists;

import eu.dnetlib.dlms.jdbc.ColumnInfo;
import eu.dnetlib.dlms.jdbc.DataResult;
import eu.dnetlib.dlms.jdbc.DataResultsContainer;
import eu.dnetlib.dlms.jdbc.ExecutorContext;
import eu.dnetlib.dlms.jdbc.ast.Statement;

/**
 * Executer class of commands in DOroty Language. This class should use dlms-core DAO to work with the system.
 * 
 * @author lexis
 */
public abstract class AbstractDOLExecuter {

	/** Logger. */
	private static final Log log = LogFactory.getLog(AbstractDOLExecuter.class);

	/** Info about the columns returned by the execution of a dol statement. */
	private ColumnInfo[] columns;
	/**
	 * Result of the execution of a list of statements.
	 */
	private Future<DataResultsContainer> exeResults;

	/**
	 * No args constructor.
	 */
	public AbstractDOLExecuter() {
		this.columns = new ColumnInfo[0];
		this.exeResults = null;
	}

	/**
	 * Gets the next result of the execution.
	 * 
	 * @return a List of DataResult
	 * @throws InterruptedException
	 *             getting the result
	 * @throws ExecutionException
	 *             getting the result
	 */
	public Collection<DataResult> getNextResult() throws InterruptedException, ExecutionException {
		log.debug("They ask me for nextResult...");
		final DataResultsContainer drContainer = this.exeResults.get();
		if (drContainer == null)
			return Lists.newArrayList();
		final Collection<DataResult> res = drContainer.getDataResultList();
		log.debug("nextResult is..." + res);
		return res;
	}

	/**
	 * Executes the command, calling the method executeImpl(String) that has to be overridden by this class' subclasses.
	 * 
	 * @param stmColl
	 *            the statements to execute
	 * @param params
	 *            map of String, ParamInfo. It holds the parameters supplied by the user.
	 * @throws SQLException
	 *             problems during the statements execution
	 * @throws ExecutionException
	 *             problems during the statements execution
	 * @see AbstractDOLExecuter.executeImpl(ASTNode)
	 */
	public void execute(final Collection<eu.dnetlib.dlms.jdbc.ast.Statement> stmColl, final Map<String, ParamInfo> params) throws SQLException,
			ExecutionException {
		if (stmColl == null)
			throw new SQLException("Empty collection of Statement");
		final ExecutorService exeService = Executors.newCachedThreadPool();
		final Future<DataResultsContainer> futureList = exeService.submit(new Callable<DataResultsContainer>() {

			public DataResultsContainer call() throws Exception {
				return AbstractDOLExecuter.this.executeTask(stmColl, params);
			}

		});
		this.exeResults = futureList;
	}

	/**
	 * Contains the logic of the execution of the ASTNode owned by this executer, eventually enqueuing data in
	 * this.dataRows, so that data can be consumed afterward.
	 * 
	 * @param stm
	 *            Statement to execute
	 * @param context
	 *            current context for the Statement execution
	 * @return the result of the execution of the Statement
	 * @throws SQLException
	 *             problems during the statements execution
	 */
	protected abstract DataResultsContainer executeImpl(final eu.dnetlib.dlms.jdbc.ast.Statement stm, ExecutorContext context) throws SQLException;

	/**
	 * Executes each Statement in the given collection, returning the results generated by the last executed Statement.
	 * 
	 * @param stmColl
	 *            Collection of Statement to execute
	 * @param params
	 *            map of String, ParamInfo. It holds the parameters supplied by the user.
	 * @return a List of DataResult that is generated by the last executed Statement
	 * @throws SQLException
	 *             problems executing statements.
	 */
	private DataResultsContainer executeTask(final Collection<eu.dnetlib.dlms.jdbc.ast.Statement> stmColl, final Map<String, ParamInfo> params)
			throws SQLException {
		final List<Statement> stmList = new ArrayList<Statement>(stmColl);
		final ExecutorContext theContext = new ExecutorContext(new HashMap<String, Variable<?>>(), params, null);
		DataResultsContainer result = null;
		for (int i = 0; i < stmList.size(); i++) {
			final Statement stm = stmList.get(i);
			log.debug(this + ".execute(" + stm + ")");
			result = this.executeImpl(stm, theContext);
			//if it is the last statement prepare columnInfos.
			if (i == stmList.size() - 1 && result != null) {
				final ColumnInfo[] infos = result.getColumnInfo();
				this.setColumns(infos);
				log.debug("colInfo in executeTask == " + Arrays.toString(infos));
			}
		}
		return result;
	}

	public void setColumns(final ColumnInfo[] columns) {
		this.columns = columns;
	}

	public ColumnInfo[] getColumns() {
		return this.columns;
	}
}
