/**
 *  Copyright 2010 Wallace Wadge
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.jolbox.bonecp;


import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;
import com.jolbox.bonecp.hooks.CoverageHook;
import com.jolbox.bonecp.hooks.CustomHook;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;

import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
/**
 * @author wwadge
 *
 */
public class TestStatementHandle {
	/** Class under test. */
	private StatementHandle testClass;
	/** Mock class under test. */
	private PreparedStatementHandle mockClass;
	/** Mock handles. */
	private IStatementCache mockCallableStatementCache;
	/** Mock handles. */
	private ConnectionHandle mockConnection;
	/** Mock handle. */
	private BoneCPConfig mockConfig;
	/** Mock handle. */
	private BoneCP mockPool;

	/**
	 * @throws java.lang.Exception
	 */
	@Before
	public void setUp() throws Exception {
		mockClass = createNiceMock(PreparedStatementHandle.class);
		mockCallableStatementCache = createNiceMock(IStatementCache.class);
		mockConnection = createNiceMock(ConnectionHandle.class);
		mockConfig = createNiceMock(BoneCPConfig.class);
		
		mockPool = createNiceMock(BoneCP.class);
		expect(mockConnection.getPool()).andReturn(mockPool).anyTimes();
		expect(mockPool.getConfig()).andReturn(mockConfig).anyTimes();
		expect(mockPool.getStatistics()).andReturn(new Statistics(mockPool)).anyTimes();
		expect(mockConfig.isStatisticsEnabled()).andReturn(true).anyTimes();
		expect(mockConfig.isCloseOpenStatements()).andReturn(true).anyTimes();
		expect(mockConfig.getQueryExecuteTimeLimitInMs()).andReturn(1L).anyTimes();
		expect(mockConfig.getConnectionHook()).andReturn(new CoverageHook()).anyTimes();
		replay(mockConnection, mockPool, mockConfig);
		testClass = new StatementHandle(mockClass, "", mockCallableStatementCache, mockConnection, "testSQL", true);
		reset(mockClass, mockCallableStatementCache, mockConnection, mockPool);
		
	    Logger shMockLogger = TestUtils.mockLogger(testClass.getClass());
	    expect(shMockLogger.isDebugEnabled()).andReturn(true).anyTimes();
	    Logger pshMockLogger = TestUtils.mockLogger(PreparedStatementHandle.class);
		expect(pshMockLogger.isDebugEnabled()).andReturn(true).anyTimes();

		reset(mockConnection, mockPool, mockConfig);
		replay(shMockLogger, pshMockLogger);
	}

	/** Test that each method will result in an equivalent bounce on the inner statement (+ test exceptions)
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	@Test
	public void testStandardBounceMethods() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		Set<String> skipTests = new HashSet<String>();
		skipTests.add("close"); 
		skipTests.add("getConnection"); 
		skipTests.add("isClosed");
		skipTests.add("internalClose");
		skipTests.add("trackResultSet");
		skipTests.add("checkClosed");
		skipTests.add("closeStatement"); 
		skipTests.add("closeAndClearResultSetHandles"); 
		skipTests.add("$VRi"); // this only comes into play when code coverage is started. Eclemma bug?

		CommonTestUtils.testStatementBounceMethod(mockConnection, testClass, skipTests, mockClass);
		
	}
	

	/** Test other methods not covered by the standard methods above.
	 * @throws SQLException
	 * @throws IllegalArgumentException
	 * @throws SecurityException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws NoSuchFieldException
	 */
	@SuppressWarnings("resource")
	@Test
	public void testRemainingForCoverage() throws SQLException, IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException{
		Statement mockStatement = createNiceMock(Statement.class);
		IStatementCache mockCache = createNiceMock(IStatementCache.class);
		expect(mockConnection.getPool()).andReturn(mockPool).anyTimes();
		expect(mockPool.getConfig()).andReturn(mockConfig).anyTimes();
		expect(mockConfig.isStatisticsEnabled()).andReturn(true).anyTimes();
		expect(mockConfig.getQueryExecuteTimeLimitInMs()).andReturn(1L).anyTimes();
		
		replay(mockConnection, mockConfig, mockPool);
		// alternate constructor 
		StatementHandle handle = new StatementHandle(mockStatement, mockConnection, true);

		handle = new StatementHandle(mockStatement, null, mockCache, mockConnection, "testSQL", true);
		
		handle.setLogicallyOpen();
		handle.getConnection();
		
		handle.logicallyClosed.set(true);
		Method method = handle.getClass().getDeclaredMethod("checkClosed");
		method.setAccessible(true);
		try{
			method.invoke(handle);
			Assert.fail("Exception should have been thrown");
		} catch (Throwable e){
		 // do nothing	
		}
		
		mockStatement.close();
		expectLastCall();
		
		
		
		replay(mockStatement, mockCache); 

		handle.close();
		verify(mockStatement);
		reset(mockStatement, mockCache);


		handle.setLogicallyOpen();
		Assert.assertFalse(handle.isClosed());
		
		reset(mockCache);
		mockCache.clear();
		expectLastCall().once();
		replay(mockCache);
		handle.clearCache();
		verify(mockCache);
		
		handle.setOpenStackTrace("foo");
		assertEquals("foo", handle.getOpenStackTrace());
		handle.sql = "foo";
		assertNotNull(handle.toString());
		
		
		testClass.logStatementsEnabled = false;
		testClass.addBatch("");
		testClass.clearBatch();
		
	}
	
	/** temp */
	boolean firstTime = true;
	
	/** Tests query time limit.
	 * @throws SQLException
	 */
	@Test
	public void testQueryTimeLimit() throws SQLException{
		Statement mockStatement = createNiceMock(Statement.class);
		// alternate constructor 
		expect(mockConnection.getPool()).andReturn(mockPool).anyTimes();
		expect(mockPool.getConfig()).andReturn(mockConfig).anyTimes();
		reset(mockConfig);
		ConnectionPartition mockPartition = createNiceMock(ConnectionPartition.class);
		expect(mockPartition.getQueryExecuteTimeLimitinNanoSeconds()).andReturn(1L).anyTimes();
		expect(mockConnection.getOriginatingPartition()).andReturn(mockPartition).anyTimes();
//		expect(mockConfig.getQueryExecuteTimeLimit()).andReturn(1).anyTimes();
		CustomHook hook = new CustomHook();
		expect(mockConfig.getConnectionHook()).andReturn(hook).anyTimes();
		mockStatement.execute((String)EasyMock.anyObject());
		expectLastCall().andAnswer(new IAnswer<Object>() {
			
			@SuppressWarnings("unqualified-field-access")
			public Object answer() throws Throwable {
				if (firstTime){
					Thread.sleep(300);
					firstTime=false;
				}
				return true;
			}
		}).anyTimes();
		replay(mockConnection, mockConfig, mockPool, mockStatement, mockPartition);

		StatementHandle handle = new StatementHandle(mockStatement, mockConnection, true);
		handle.execute("test");
		handle.close();
		assertEquals(1, hook.queryTimeout);
	}

	/**
	 * Coverage.
	 */
	@Test
	public void testGetterSetter(){
		Statement mockStatement = createNiceMock(Statement.class); 
		testClass.setInternalStatement(mockStatement);
		Object obj = new Object();
		testClass.setDebugHandle(obj);
		assertEquals(mockStatement, testClass.getInternalStatement());
		
		assertEquals(obj, testClass.getDebugHandle());
	}
	
	
	/**
	 * @throws SQLException
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	@Test
	public void testAssortedCoverage() throws SQLException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		// using a null batchSQL statement...
		Statement mockStatement = createNiceMock(Statement.class);
		testClass.logStatementsEnabled = false;
		Field field = testClass.getClass().getDeclaredField("internalStatement");
		field.setAccessible(true);
		field.set(testClass, mockStatement);
		expect(mockStatement.executeBatch()).andReturn(null).once();
		replay(mockStatement);
		testClass.executeBatch();
	}
	
	/**
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws SQLException
	 */

}
