/**********************************************************************
* Copyright (C) Contributors                                          *
*                                                                     *
* This program is free software; you can redistribute it and/or       *
* modify it under the terms of the GNU General Public License         *
* as published by the Free Software Foundation; either version 2      *
* of the License, or (at your option) any later version.              *
*                                                                     *
* This program is distributed in the hope that it will be useful,     *
* but WITHOUT ANY WARRANTY; without even the implied warranty of      *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the        *
* GNU General Public License for more details.                        *
*                                                                     *
* You should have received a copy of the GNU General Public License   *
* along with this program; if not, write to the Free Software         *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,          *
* MA 02110-1301, USA.                                                 *
*                                                                     *
* Contributors:                                                       *
* - Trek Global                                                       *
* - Carlos Ruiz - globalqss                                           *
**********************************************************************/
package org.compiere.db;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;

import javax.sql.RowSet;

import org.adempiere.exceptions.DBException;
import org.compiere.util.CCachedRowSet;
import org.compiere.util.CLogger;
import org.compiere.util.CStatementVO;
import org.compiere.util.DB;
import org.compiere.util.DBReadReplica;

/**
 * Read replica proxy for prepared statement 
 * @author Low Heng Sin
 */
public class ReadReplicaPreparedStatementProxy implements InvocationHandler {

	private boolean close = false;
	
	/**	Logger							*/
	protected transient CLogger			log = CLogger.getCLogger (getClass());
	/** Wrap prepared statement 		*/
	protected transient PreparedStatement p_stmt = null;
	/**	Value Object					*/
	protected CStatementVO				p_vo = null;
	
	/**
	 * @param resultSetType
	 * @param resultSetConcurrency
	 * @param sql
	 */
	public ReadReplicaPreparedStatementProxy(int resultSetType, int resultSetConcurrency, String sql) {
		if (sql == null || sql.length() == 0)
			throw new IllegalArgumentException("sql required");

		p_vo = new CStatementVO(resultSetType, resultSetConcurrency, DB.getDatabase().convertStatement(sql));

		init();
	}
	
	/**
	 * Initialise the prepared statement wrapper object
	 */
	private void init() {
		try {
			Connection conn = DBReadReplica.getConnectionRO();
			if (conn != null) {
				p_stmt = conn.prepareStatement(p_vo.getSql(), p_vo.getResultSetType(), p_vo.getResultSetConcurrency());
			}
		} catch (Exception e) {
			log.log(Level.SEVERE, p_vo.getSql(), e);
			throw new DBException(e);
		}
	}

	/**
	 * Execute the wrapped prepare statement and return row set
	 * @return row set
	 */
	private RowSet getRowSet() 
	{
		if (log.isLoggable(Level.FINEST))
			log.finest("local_getRowSet");
		
		RowSet rowSet = null;
		ResultSet rs = null;
		try
		{
			rs = p_stmt.executeQuery();
			rowSet = CCachedRowSet.getRowSet(rs);						
		}
		catch (Exception ex)
		{
			log.log(Level.SEVERE, p_vo.toString(), ex);
			throw new RuntimeException (ex);
		}		
		finally
		{
			DB.close(rs);
		}
		return rowSet;
	}

	@Override
	public Object invoke(Object obj, Method method, Object[] args)
			throws Throwable {
		String name = method.getName();		
		//handle special case	
		if (name.equals("executeQuery") || name.equals("executeUpdate") 
			|| name.equals("execute") || name.equals("addBatch")) {
			if (args != null && args.length > 0 && args[0] != null && args[0] instanceof String) {
				String sql = (String)args[0];
				p_vo.setSql(DB.getDatabase().convertStatement(sql));
				args[0] = p_vo.getSql();
			}
		} else if (name.equals("close") && (args == null || args.length == 0)) {
			close();
			return null;
		} else if (name.equals("getRowSet") && (args == null || args.length == 0)) {
			return getRowSet();
		} else if (name.equals("isClosed") && (args == null || args.length == 0)) {
			return close;
		} else if (name.equals("finalize") && (args == null || args.length == 0)) {
			if (p_stmt != null && !close) 
			{
				this.close();
			}
			return null;
		} else if (name.equals("getSql") && (args == null || args.length == 0)) {
			return getSql();
		} else if (name.equals("equals") && (args != null && args.length == 1)) {
			return equals(args[0]);
		}
		
		Method m = PreparedStatement.class.getMethod(name, method.getParameterTypes());
		try
		{
			return m.invoke(p_stmt, args);
		}
		catch (InvocationTargetException e)
		{
			throw DB.getSQLException(e);
		}
	}
	
	/**
	 * Close
	 * @throws SQLException
	 * @see java.sql.Statement#close()
	 */
	private void close () throws SQLException
	{
		if (close) return;
		
		if (p_stmt!=null)
		{
			try 
			{
	            p_stmt.close();
	        } catch (SQLException e) {
	            ;
	        }
			finally 
			{
				try 
				{
					DBReadReplica.closeReadReplicaStatement(p_stmt);
				} 
				catch (Exception e) 
				{
					;
				} 
				finally 
				{
					p_stmt = null;
					close = true;
				}
			}
		}
		else
		{
			close = true;
		}
	}	//	close
	
	/**
	 * 	Get Sql
	 *	@return sql
	 */
	public String getSql()
	{
		if (p_vo != null)
			return p_vo.getSql();
		return null;
	}
	
	/**
	 * 
	 * @return true if the wrapped prepared statement is valid
	 */
	public boolean isValid()
	{
		return p_stmt != null;
	}
}
