package eu.dnetlib.dlms.jdbc.server;

import java.sql.SQLException;
import java.util.concurrent.LinkedBlockingQueue;

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

import eu.dnetlib.dlms.jdbc.ColumnInfo;
import eu.dnetlib.dlms.jdbc.DataResult;
import eu.dnetlib.dlms.jdbc.SentinelDataResult;

/**
 * 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);
    /**
     * Default length of the queue that contains dataResults generated by the
     * execution of a statement.
     */
    public static final int DEFAULT_QUEUE_LENGTH = 10;
    /**
     * Queue of DataResult containing results of the execution of the dol
     * statement.
     */
    private LinkedBlockingQueue<DataResult> dataRows;
    /** Size of the queue that holds data rows. */
    private int queueLength;
    /** Info about the columns returned by the execution of a dol statement. */
    private ColumnInfo[] columns;
    /**
     * True if the execution of the current statement is completed and all the
     * results were enqueued in dataRows.
     */
    private boolean executionCompleted;
    /**
     * True if this executer enqueues results to be read in dataRows, false if
     * no results are generated by this executer.
     */
    private boolean resultSetExpected;

    /**
     * No args constructor.
     */
    public AbstractDOLExecuter() {
	this.executionCompleted = true;
	this.queueLength = DEFAULT_QUEUE_LENGTH;
	this.columns = new ColumnInfo[0];
	this.dataRows = new LinkedBlockingQueue<DataResult>(this.queueLength);
    }

    void setResultSetExpected(final boolean resultSetExpected) {
	this.resultSetExpected = resultSetExpected;
    }

    public boolean isResultSetExpected() {
	return this.resultSetExpected;
    }

    void setDataRows(final LinkedBlockingQueue<DataResult> dataRows) {
	this.dataRows = dataRows;
    }

    public LinkedBlockingQueue<DataResult> getDataRows() {
	return this.dataRows;
    }

    public int getQueueLength() {
	return this.queueLength;
    }

    public void setQueueLength(final int queueLength) {
	this.queueLength = queueLength;
    }

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

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

    public boolean isExecutionCompleted() {
	return this.executionCompleted;
    }

    /**
     * Sets the field executionCompleted to the given value. If the value is
     * true, then it puts a SentinelDataResult object into the queue, so that
     * consumer can know that they do not have to wait for any other DataResult.
     * This method has to be called by AbstractDOLExecuter implementation after
     * they end the execution of the statement.
     * 
     * @param executionCompleted
     *            boolean
     * @throws InterruptedException
     *             when executionCompleted is true, trying to put a
     *             SentinelDataResult in this.dataRows
     */
    public void setExecutionCompleted(final boolean executionCompleted)
	    throws InterruptedException {
	this.executionCompleted = executionCompleted;
	if (this.executionCompleted) {
	    // need to enqueue the sentinel element in the queue:
	    this.dataRows.put(new SentinelDataResult());
	}
    }

    /**
     * Consumes (returns and deletes) the next element in the queue
     * this.dataRows or null if the execution is completed and there are no more
     * elements to consume in the dataRows queue. If the queue is empty but
     * execution is still ongoing, then we will wait until a new object is
     * inserted in the queue
     * 
     * @return an Object[] instance which is the head of this.dataRows.
     * @throws InterruptedException
     *             if interrupted while waitingfor the queue to be populated
     */
    public DataResult getNextRow() throws InterruptedException {
	log.debug("They ask me for a row...");
	// if the execution ended and data rows have already been consumed (==
	// dataRow is empty) then return null.
	if (this.executionCompleted && this.dataRows.isEmpty()) {
	    log.debug("No more rows to return");
	    return new SentinelDataResult();
	} else {
	    log.debug("ready to get another row:");
	    // take is blocking: execution is not complete and I have to wait
	    // for dataRows to be fed with results
	    DataResult res = this.dataRows.take();
	    log.debug("Took it!");
	    return res;
	}
    }

    /**
     * Executes the command, calling the method executeImpl(String) that has to
     * be overridden by this class' subclasses.
     * 
     * @param stm
     *            the statement to execute
     * @throws SQLException
     *             while executing the request
     * @see AbstractDOLExecuter.executeImpl(ASTNode)
     */
    public void execute(final eu.dnetlib.dlms.jdbc.ast.ASTNode stm)
	    throws SQLException {
	try {
	    log.debug(this + ".execute(" + stm + ")");
	    if (this.dataRows == null)
		this.dataRows = new LinkedBlockingQueue<DataResult>(
			this.queueLength);
	    this.setExecutionCompleted(false);
	    this.executeImpl(stm);
	} catch (InterruptedException ie) {
	    throw new SQLException(ie);
	}
    }

    /**
     * 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
     *            ASTNode instance to execute
     * @throws SQLException
     *             while executing the request
     */
    protected abstract void executeImpl(eu.dnetlib.dlms.jdbc.ast.ASTNode stm)
	    throws SQLException;

}
