/*
 *
 */
package com.postgres.replic.util;

import com.postgres.replic.server.props.ServerProps;
import com.postgres.replic.util.struct.TableMap;
import com.postgres.util.jdbc.ConnectionPool;

import java.sql.Connection;
import java.sql.Statement;


/**
 */
public class DBProcessor extends AbstractInitializable {
	protected ServerProps serverProps;

	protected TableColumnCache tableColumnCache;
	//private final static int SUCCESS = 1;
	private ConnectionPool cPool;
	private Connection conn;

	private String errorPrefix;
	private boolean isConnectionSame = false;
	private int pstmtUseCount = 0;
	private boolean resetPstmtCache = false;
	// flag to indicate that the table column cache should be reset
	private boolean resetTableColumnCache = false;
	private final static String[] sqls = {
		"set transaction isolation level serializable", // 0
		"set enable_seqscan to off", // 1
		"set enable_indexscan to on", // 2
	};

	public void resetTableColumnCache() {
		resetTableColumnCache = true;
	}

	/**
	 *
	 * @exception  RservException  Description of the Exception
	 */
	public void closeConnection() throws RservException {
		setErrorPrefix("closeConnection: ");
		validate();
		if (cPool != null && conn != null) {
			cPool.close(conn);
		}
	}

	/**
	 *
	 * @exception  Exception  Description of the Exception
	 */
	public void commit() throws Exception {
		getConnection().commit();
	}

	/**
	 * @param  conn                Parameter
	 * @param  serverProps         Parameter
	 * @exception  RservException  Description of the Exception
	 * @exception  Exception       Description of the Exception
	 */
	public void init(Connection conn, ServerProps serverProps)
			throws RservException, Exception {
		this.serverProps = serverProps;

		// This postion is reused only
		pstmtUseCount++;
		if (this.conn != null && conn != null && conn.equals(this.conn)) {
			isConnectionSame = true;
		} else {
			isConnectionSame = false;
		}

		if (pstmtUseCount >= serverProps.getPstmtReuseCount() || !isConnectionSame) {
			resetPstmtCache = true;
			pstmtUseCount = 0;// <== reset pstmtUseCount!
		} else {
			resetPstmtCache = false;
		}
		getLogger().debug("*** DBProcessor::init: pstmtUseCount=" + pstmtUseCount + "; isConnectionSame=" + isConnectionSame + "; resetPstmtCache=" + resetPstmtCache);

		this.conn = conn;
		validate();

		//setInitialized(true);
	}


	/**
	 *
	 * @param  cPool               Parameter
	 * @exception  RservException  Description of the Exception
	 */
	public void init(ConnectionPool cPool) throws RservException {
		this.cPool = cPool;
		validate();
		//setInitialized(true);
	}

	/**
	 *
	 * @exception  Exception  Description of the Exception
	 */
	public void rollback() throws Exception {
		getConnection().rollback();
		/*
         Statement stmt = null;
         String sql = "ROLLBACK";
        try {
            stmt = getConnection().createStatement();
            stmt.executeUpdate(sql);
            getLogger().debug("{TRANSACTION ROLLBACK}");
        } catch (Exception e) {
            throw new Exception("DBProcessor::rollback: " + e.toString());
        } finally {
            generalClose(stmt, null);
        }
        */
	}

	/**
	 *  Sets the debug attribute
	 *
	 * @param  debug  The new debug value
	 */
	public void setDebug(boolean debug) {
		super.setDebug(debug);
		if (getTableColumnCache() != null) {
			getTableColumnCache().setDebug(debug);
		}
	}

	/**
	 *  Sets the optimizerHints attribute
	 *
	 * @exception  Exception  Description of the Exception
	 */
	public void setOptimizerHints() throws Exception {
		Statement stmt = null;

		try {
			stmt = getConnection().createStatement();
			stmt.executeUpdate(sqls[1]);
			getLogger().debug("DBProcessor::setOptimizerHints: sql=" + sqls[1]);
			stmt.executeUpdate(sqls[2]);
			getLogger().debug("DBProcessor::setOptimizerHints: sql=" + sqls[2]);
		} catch (Exception e) {
			throw new Exception("DBProcessor::setDatabaseHints: " + e.toString());
		} finally {
			try {
				if (stmt != null) {
					stmt.close();
				}
			} catch (Exception ex) {
			}
		}
	}

	/**
	 *  Sets the transactionSerializable attribute
	 *
	 * @exception  Exception  Description of the Exception
	 */
	public void setTransactionSerializable() throws Exception {
		Statement stmt = null;

		try {
			stmt = getConnection().createStatement();
			stmt.executeUpdate(sqls[0]);
			getLogger().debug("DBProcessor::setTransactionSerializable: sql=" + sqls[0]);
		} catch (Exception e) {
			throw new Exception("DBProcessor::setTransactionSerializable: " + e.toString());
		} finally {
			try {
				if (stmt != null) {
					stmt.close();
				}
			} catch (Exception ex) {
			}
		}
	}

	/**
	 *  Sets the verbose attribute
	 *
	 * @param  verbose  The new verbose value
	 */
	public void setVerbose(boolean verbose) {
		super.setDebug(verbose);
		if (getTableColumnCache() != null) {
			getTableColumnCache().setDebug(verbose);
		}
	}

	/**
	 *  Description of the Method
	 *
	 * @param  stmt  Parameter
	 * @param  rs    Parameter
	 */
	/*protected void generalClose(Statement stmt, ResultSet rs) {
		try {
			if (stmt != null) {
				stmt.close();
			}
		} catch (Exception e) {}

		try {
			if (rs != null) {
				rs.close();
			}
		} catch (Exception e) {}
	}*/

	/**
	 *  Description of the Method
	 *
	 * @param  stmt  Parameter
	 * @param  rs    Parameter
	 */
	/*protected void generalClose(PreparedStatement stmt, ResultSet rs) {
		try {
			if (stmt != null) {
				stmt.close();
			}
		} catch (Exception e) {}

		try {
			if (rs != null) {
				rs.close();
			}
		} catch (Exception e) {}
	}*/

	/**
	 *  Gets the connection attribute
	 *
	 * @return                     The connection value
	 * @exception  RservException  Description of the Exception
	 */
	protected Connection getConnection() throws RservException {
		setErrorPrefix("getConnection: ");
		validate();
		if (cPool != null && conn == null) {
			conn = cPool.getConnection();
		}
		return conn;
	}

	/**
	 *  Gets the errorPrefix attribute
	 *
	 * @return    The errorPrefix value
	 */
	protected String getErrorPrefix() {
		return errorPrefix == null ? "" : errorPrefix;
	}

	/**
	 *  Gets the pstmtUseCount attribute
	 *
	 * @return    The pstmtUseCount value
	 */
	protected int getPstmtUseCount() {
		return pstmtUseCount;
	}

	/**
	 *  Gets the tableColumnCache attribute
	 *
	 * @return    The tableColumnCache value
	 */
	protected TableColumnCache getTableColumnCache() {
		return tableColumnCache;
	}

	/**
	 *  Gets the tableMap attribute
	 *
	 * @return                     The tableMap value
	 * @exception  RservException  Description of the Exception
	 */
	protected TableMap getTableMap() throws RservException {
		return null;
	}

	/**
	 *  Gets the connectionSame attribute
	 *
	 * @return    The connectionSame value
	 */
	protected boolean isConnectionSame() {
		return isConnectionSame;
	}

	/**
	 *  Description of the Method
	 *
	 * @exception  Exception  Description of the Exception
	 */
	protected void resetPstmtCache() throws Exception {
		// If this is not the same connection  - update Pstm cache:
		if (resetPstmtCache) {
			tableColumnCache.resetPstmtCache();
		}
	}

	/**
	 *  Sets the errorPrefix attribute
	 *
	 * @param  errorPrefix  The new errorPrefix value
	 */
	protected void setErrorPrefix(String errorPrefix) {
		this.errorPrefix = errorPrefix;
	}


	/**
	 *  Sets the tableColumnCache attribute
	 *
	 * @exception  Exception  Description of the Exception
	 */
	protected void setTableColumnCache() throws Exception {
		// if resetTableColumnCache is set, regenerate the table column cache
		if (tableColumnCache == null || resetTableColumnCache) {
			tableColumnCache = new TableColumnCache(conn, getTableMap(), serverProps);
			resetTableColumnCache = false;
		}
	}

	/**
	 *
	 * @exception  RservException  Description of the Exception
	 */
	protected void validate() throws RservException {
		if (conn == null && cPool == null) {
			throw new RservException(errorPrefix + "Either connection or connection pool must be set");
		}
	}

	/**
	 *
	 * @exception  RservException  Description of the Exception
	 */
	protected void validateInit() throws RservException {
		if (!initialized()) {
			throw new RservException(errorPrefix + " object is not initialized");
		}
	}


	protected boolean transactionIdPrecedes(long id1, long id2) {

		if (!serverProps.useXIDType()) {
			return (id1 < id2);
		}

		/*
		 * If either ID is a permanent XID then we can just do unsigned
		 * comparison.  If both are normal, do a modulo-2^31 comparison.
		 */
		int diff;

		if (!transactionIdIsNormal(id1) || !transactionIdIsNormal(id2)) {
			return (id1 < id2);
		}

		diff = (int) (id1 - id2);
		return (diff < 0);
	}

	protected boolean transactionIdIsNormal(long id) {
		if (!serverProps.useXIDType()) {
			return id > 0;
		}
		return id > 2;
	}

}

