package eu.dnetlib.dlms.jdbc;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Required;

import eu.dnetlib.dlms.jdbc.server.AbstractDOLEngine;
import eu.dnetlib.dlms.jdbc.server.AbstractDOLExecuter;

/**
 * Statement implementation.
 * 
 * @author lexis
 */
public class DOLStatement implements Statement {

	/**
	 * default fetch size for the result set generated by the execution of this statement.
	 */
	private static final int DEFAULT_FETCH_SIZE = 10;
	/** DOL String to be executed. */
	private String dolString;
	/** The result set. */
	private DOLResultSet resultSet;
	/** The connection. */
	private DorotyConnection connection;
	/** Executer that will execute this statement. */
	private AbstractDOLExecuter dolExecuter;
	/** Factory for DOLREsultSet instances . */
	private AbstractResultSetFactory resultSetFactory;
	/**
	 * Fetch direction for result sets generated by this Statement object. Each result set has its own methods for
	 * getting and setting its own fetch direction. Possible Values: ResultSet.FETCH_FORWARD, ResultSet.FETCH_REVERSE,
	 * or ResultSet.FETCH_UNKNOWN.
	 */
	private int fetchDirection;
	/** default fetch size for result sets generated by this Statement object. */
	private int fetchSize;
	/** Time to wait response before giving up. 0 means wait eternally. */
	private int queryTimeout;
	/** True if this statement has been closed. */
	private boolean closed;

	/**
	 * either ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE.
	 */
	private int resultSetConcurrency;
	/**
	 * either ResultSet.HOLD_CURSORS_OVER_COMMIT or ResultSet.CLOSE_CURSORS_AT_COMMIT.
	 */
	private int resultSetHoldability;
	/**
	 * one of ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, or ResultSet.TYPE_SCROLL_SENSITIVE.
	 */
	private int resultSetType;

	/**
	 * By default, a Statement is not poolable when created.
	 */
	private boolean poolable;

	void setResultSet(final DOLResultSet resultSet) {
		this.resultSet = resultSet;
	}

	AbstractResultSetFactory getResultSetFactory() {
		return this.resultSetFactory;
	}

	@Required
	void setResultSetFactory(final AbstractResultSetFactory resultSetFactory) {
		this.resultSetFactory = resultSetFactory;
	}

	public AbstractDOLExecuter getDolExecuter() {
		return this.dolExecuter;
	}

	void setDolExecuter(final AbstractDOLExecuter dolExecuter) {
		this.dolExecuter = dolExecuter;
	}

	@Required
	public void setConnection(final DorotyConnection connection) {
		this.connection = connection;
	}

	void setClosed(final boolean closed) {
		this.closed = closed;
	}

	void setResultSetConcurrency(final int resultSetConcurrency) {
		this.resultSetConcurrency = resultSetConcurrency;
	}

	void setResultSetHoldability(final int resultSetHoldability) {
		this.resultSetHoldability = resultSetHoldability;
	}

	void setResultSetType(final int resultSetType) {
		this.resultSetType = resultSetType;
	}

	public void setDolString(final String dolCommand) {
		this.dolString = dolCommand;
	}

	public String getDolString() {
		return this.dolString;
	}

	/**
	 * Does nothing.
	 * 
	 * @param batchCommands
	 *            commands that will be ignored since this method does nothing
	 */
	public void setBatchCommands(final List<String> batchCommands) {
		//do nothing
	}

	public List<String> getBatchCommands() {
		return null;
	}

	/**
	 * Init properties with their default values.
	 * 
	 * @throws SQLException
	 *             exception setting Statement properties.
	 */
	private void initProperties() throws SQLException {
		this.setBatchCommands(new ArrayList<String>());
		this.fetchDirection = ResultSet.FETCH_FORWARD;
		this.resultSetHoldability = ResultSet.CLOSE_CURSORS_AT_COMMIT;
		this.fetchSize = DOLStatement.DEFAULT_FETCH_SIZE;
		this.queryTimeout = 0;
		this.poolable = false;
	}

	/**
	 * No args constructor.
	 * 
	 * @throws SQLException
	 *             exception generated at property initialization time.
	 */
	public DOLStatement() throws SQLException {
		this.initProperties();
	}

	/**
	 * Constructor.
	 * 
	 * @param connection
	 *            DorotyConnection instance
	 * @throws SQLException
	 *             exception generated at property initialization time.
	 */
	public DOLStatement(final DorotyConnection connection) throws SQLException {
		this();
		this.connection = connection;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#addBatch(java.lang.String)
	 */
	public void addBatch(final String sql) throws SQLException {
		throw new SQLFeatureNotSupportedException();

	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#cancel()
	 */
	public void cancel() throws SQLException {
		throw new SQLFeatureNotSupportedException();

	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#clearBatch()
	 */
	public void clearBatch() throws SQLException {
		throw new SQLException();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#clearWarnings()
	 */
	public void clearWarnings() throws SQLException {
		if (this.closed)
			throw new SQLException();
		throw new SQLFeatureNotSupportedException();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#close()
	 */
	public void close() throws SQLException {
		if (this.resultSet != null)
			this.resultSet.close();
		this.closed = true;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#execute(java.lang.String)
	 */
	public boolean execute(final String dol) throws SQLException {
		if (this.isClosed())
			throw new SQLException("Cannot call execute on a closed Statement");
		// closes current result set if it was previously open
		if (this.resultSet != null)
			this.resultSet.close();
		AbstractDOLEngine eng = this.connection.getDolEngine();
		this.dolExecuter = eng.execute(dol, null);
		if (this.dolExecuter.isResultSetExpected()) {
			this.resultSet = this.resultSetFactory.createDOLResultSet(this, this.dolExecuter.getColumns());
		} else
			this.resultSet = null;
		return this.dolExecuter.isResultSetExpected();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#execute(java.lang.String, int)
	 */
	public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException {
		throw new SQLFeatureNotSupportedException("Not implemented DOLStatement.execute(final String sql, final int autoGeneratedKeys)");
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#execute(java.lang.String, int[])
	 */
	public boolean execute(final String sql, final int[] columnIndexes) throws SQLException {
		throw new SQLFeatureNotSupportedException("Not implemented DOLStatement.execute(final String sql, final int[] columnIndexes)");
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#execute(java.lang.String, java.lang.String[])
	 */
	public boolean execute(final String sql, final String[] columnNames) throws SQLException {
		throw new SQLFeatureNotSupportedException("Not implemented DOLStatement.execute(final String sql,  final String[] columnNames)");
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#executeBatch()
	 */

	public int[] executeBatch() throws SQLException {
		throw new SQLException();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#executeQuery(java.lang.String)
	 */
	public ResultSet executeQuery(final String dol) throws SQLException {
		if (this.resultSet != null)
			this.resultSet.close();
		if (this.isClosed()) {
			throw new SQLException();
		}
		AbstractDOLEngine eng = this.connection.getDolEngine();
		this.dolExecuter = eng.execute(dol, null);
		if (this.dolExecuter.isResultSetExpected()) {
			this.resultSet = this.resultSetFactory.createDOLResultSet(this, this.dolExecuter.getColumns());
		} else {
			this.resultSet = null;
			throw new SQLException("Called executeQuery on a dol statement that does not return a ResultSet");
		}
		return this.resultSet;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#executeUpdate(java.lang.String)
	 */
	public int executeUpdate(final String sql) throws SQLException {
		if (this.isClosed()) {
			throw new SQLException();
		}
		throw new SQLFeatureNotSupportedException();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#executeUpdate(java.lang.String, int)
	 */

	public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
		throw new SQLFeatureNotSupportedException();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#executeUpdate(java.lang.String, int[])
	 */

	public int executeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
		throw new SQLFeatureNotSupportedException();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#executeUpdate(java.lang.String, java.lang.String[])
	 */

	public int executeUpdate(final String sql, final String[] columnNames) throws SQLException {
		throw new SQLFeatureNotSupportedException();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getConnection()
	 */
	public DorotyConnection getConnection() throws SQLException {
		return this.connection;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getFetchDirection()
	 */

	public int getFetchDirection() throws SQLException {
		return this.fetchDirection;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getFetchSize()
	 */

	public int getFetchSize() throws SQLException {
		return this.fetchSize;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getGeneratedKeys()
	 */

	public ResultSet getGeneratedKeys() throws SQLException {
		throw new SQLFeatureNotSupportedException();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getMaxFieldSize()
	 */

	public int getMaxFieldSize() throws SQLException {
		//0 == no limit
		return 0;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getMaxRows()
	 */

	public int getMaxRows() throws SQLException {
		//0 == no limit
		return 0;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getMoreResults()
	 */

	public boolean getMoreResults() throws SQLException {
		throw new SQLException("Do I need to ask for more results?");
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getMoreResults(int)
	 */

	public boolean getMoreResults(final int current) throws SQLException {
		throw new SQLException("Do I need to ask for more results with parameter current = " + current + " ?");
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getQueryTimeout()
	 */

	public int getQueryTimeout() throws SQLException {
		return this.queryTimeout;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getResultSet()
	 */
	public DOLResultSet getResultSet() throws SQLException {
		if (this.isClosed()) {
			throw new SQLException("Cannot ask a resultSet to a closed DOLStatement");
		}
		return this.resultSet;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getResultSetConcurrency()
	 */
	public int getResultSetConcurrency() throws SQLException {
		if (this.isClosed()) {
			throw new SQLException("Cannot ask a resultSet to a closed DOLStatement");
		}
		return this.resultSetConcurrency;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getResultSetHoldability()
	 */
	public int getResultSetHoldability() throws SQLException {
		if (this.isClosed()) {
			throw new SQLException("Cannot ask a resultSet to a closed DOLStatement");
		}
		return this.resultSetHoldability;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getResultSetType()
	 */

	public int getResultSetType() throws SQLException {
		if (this.isClosed()) {
			throw new SQLException("Cannot ask a resultSet to a closed DOLStatement");
		}
		return this.resultSetType;
	}

	/**
	 * {@inheritDoc}.
	 * 
	 * @see java.sql.Statement#getUpdateCount()
	 */
	public int getUpdateCount() throws SQLException {
		//TODO: handle updateCount property
		return 0;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#getWarnings()
	 */

	public SQLWarning getWarnings() throws SQLException {
		if (this.isClosed()) {
			throw new SQLException();
		}
		throw new SQLFeatureNotSupportedException();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#isClosed()
	 */

	public boolean isClosed() throws SQLException {
		return this.closed;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#isPoolable()
	 */

	public boolean isPoolable() throws SQLException {
		if (this.isClosed()) {
			throw new SQLException();
		}
		return this.poolable;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#setCursorName(java.lang.String)
	 */

	public void setCursorName(final String name) throws SQLException {
		throw new SQLFeatureNotSupportedException();

	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#setEscapeProcessing(boolean)
	 */

	public void setEscapeProcessing(final boolean enable) throws SQLException {
		if (this.isClosed()) {
			throw new SQLException();
		}
		throw new SQLFeatureNotSupportedException();

	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#setFetchDirection(int)
	 */

	public void setFetchDirection(final int direction) throws SQLException {
		if (this.isClosed()) {
			throw new SQLException();
		}
		if (direction != ResultSet.FETCH_FORWARD && direction != ResultSet.FETCH_REVERSE && direction != ResultSet.FETCH_UNKNOWN)
			throw new SQLException("Fetch direction parameter = " + direction);
		this.fetchDirection = direction;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#setFetchSize(int)
	 */

	public void setFetchSize(final int rows) throws SQLException {
		if (this.isClosed() || rows < 0) {
			throw new SQLException();
		}
		this.fetchSize = rows;

	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#setMaxFieldSize(int)
	 */

	public void setMaxFieldSize(final int max) throws SQLException {
		if (this.isClosed() || max < 0) {
			throw new SQLException();
		}
		throw new SQLFeatureNotSupportedException();

	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#setMaxRows(int)
	 */

	public void setMaxRows(final int max) throws SQLException {
		if (this.isClosed() || max < 0) {
			throw new SQLException();
		}
		throw new SQLFeatureNotSupportedException();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#setPoolable(boolean)
	 */

	public void setPoolable(final boolean poolable) throws SQLException {
		if (this.isClosed()) {
			throw new SQLException();
		}
		this.poolable = poolable;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Statement#setQueryTimeout(int)
	 */

	public void setQueryTimeout(final int seconds) throws SQLException {
		if (this.isClosed() || seconds < 0) {
			throw new SQLException();
		}
		this.queryTimeout = seconds;

	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Wrapper#isWrapperFor(java.lang.Class)
	 */
	public boolean isWrapperFor(final Class<?> iface) throws SQLException {
		throw new SQLException("Do I need to be a wrapper of class " + iface + " ?");
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Wrapper#unwrap(java.lang.Class)
	 */
	public <T> T unwrap(final Class<T> iface) throws SQLException {
		throw new SQLException("Do I need to call unwrap with parameter class " + iface + " ?");
	}

}
