package eu.dnetlib.contract.runner;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import eu.dnetlib.contract.ctx.GlobalContractContext;
import eu.dnetlib.contract.runner.ContractTestRunner;
import eu.dnetlib.contract.runner.ContractTestRunnerConfiguration;
import eu.dnetlib.contract.runner.ContractTestRunnerException;
import eu.dnetlib.contract.runner.IRunnable;
import eu.dnetlib.contract.runner.InvokerData;


/**
 * Contract test runner test class.
 * @author mhorst
 *
 */
public class ContractTestRunnerTest {
	
	@Test
	public void testValidateContractTestRunnerConfiguration() throws Exception {
		
		try {
			ContractTestRunnerConfiguration conf = null;
			ContractTestRunner.validateContractTestRunnerConfiguration(conf);
			fail("Exception should be thrown!");
		} catch (ContractTestRunnerException e) {
//			ok
		}
		
		try {
			ContractTestRunnerConfiguration conf = null;
			conf = new ContractTestRunnerConfiguration();
			ContractTestRunner.validateContractTestRunnerConfiguration(conf);
			fail("Exception should be thrown!");
		} catch (ContractTestRunnerException e) {
//			ok
		}
		
		try {
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			ContractTestRunner.validateContractTestRunnerConfiguration(conf);
			fail("Exception should be thrown!");
		} catch (ContractTestRunnerException e) {
//			ok
		}

		try {
//			bad parameter types size vs args size
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("concat");
			invokerData.setParameterTypes(new Class<?>[] {String.class, String.class});
			ContractTestRunner.validateContractTestRunnerConfiguration(conf);
			fail("Exception should be thrown!");
		} catch (ContractTestRunnerException e) {
//			ok
		}

		try {
//			bad parameter types size vs args size
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("concat");
			invokerData.setParameterTypes(new Class<?>[] {String.class});
			invokerData.setArgs(new Object[] {"aaa", "bbb"});
			ContractTestRunner.validateContractTestRunnerConfiguration(conf);
			fail("Exception should be thrown!");
		} catch (ContractTestRunnerException e) {
//			ok
		}
		
		{
//			parameter type specified
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("concat");
			invokerData.setParameterTypes(new Class<?>[] {String.class});
			invokerData.setArgs(new Object[] {new String("blah")});
			ContractTestRunner.validateContractTestRunnerConfiguration(conf);
		}
		
		{
//			no parameter type specified
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("concat");
			invokerData.setParameterTypes(null);
			invokerData.setArgs(new Object[] {new String("blah")});
			ContractTestRunner.validateContractTestRunnerConfiguration(conf);
		}
	}
	
	@Test
	public void testRunValid() throws Exception {
		{
			ContractTestRunner runner = new ContractTestRunner(null);
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("concat");
//			no parameter types specified
			invokerData.setParameterTypes(null);
			Object arg = new String("someOtherValue");
			invokerData.setArgs(new Object[] {arg});
			runner.setContractTestRunnerConfiguration(conf);
			Object[] result = runner.run();
			assertNotNull(result);
			assertNotNull(result[0]);
			assertEquals((""+target+arg), result[0]);
		}
		
		{
			ContractTestRunner runner = new ContractTestRunner(null);
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("concat");
//			parameter type specified
			invokerData.setParameterTypes(new Class<?>[] {String.class});
			Object arg = new String("someOtherValue");
			invokerData.setArgs(new Object[] {arg});
			runner.setContractTestRunnerConfiguration(conf);
			Object[] result = runner.run();
			assertNotNull(result);
			assertNotNull(result[0]);
			assertEquals((""+target+arg), result[0]);
		}
	}
	
	@Test
	public void testRunInvalid() {
		try {
			ContractTestRunner runner = new ContractTestRunner(null);
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("nonExistingMethod");
			runner.setContractTestRunnerConfiguration(conf);
			runner.run();
			fail("Exception should be thrown!");
		} catch (ContractTestRunnerException e) {
//			ok
		}
		
		try {
//			method with non 0 parameters, no params provided
			ContractTestRunner runner = new ContractTestRunner(null);
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("concat");
			runner.setContractTestRunnerConfiguration(conf);
			runner.run();
			fail("Exception should be thrown!");
		} catch (ContractTestRunnerException e) {
//			ok
		}
		
		try {
//			bad parameter type
			ContractTestRunner runner = new ContractTestRunner(null);
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("concat");
			invokerData.setParameterTypes(new Class<?>[] {Integer.class});
			runner.setContractTestRunnerConfiguration(conf);
			runner.run();
			fail("Exception should be thrown!");
		} catch (ContractTestRunnerException e) {
//			ok
		}
		
		try {
//			bad args type
			ContractTestRunner runner = new ContractTestRunner(null);
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("concat");
			invokerData.setParameterTypes(new Class<?>[] {String.class});
			invokerData.setArgs(new Object[] {new Integer(10)});
			runner.setContractTestRunnerConfiguration(conf);
			runner.run();
			fail("Exception should be thrown!");
		} catch (ContractTestRunnerException e) {
//			ok
		}
	}
	
	@Test
	public void testRunOnPrimitives() throws Exception {
		try {
//			null arg for primitive
			ContractTestRunner runner = new ContractTestRunner(null);
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("replace");
			invokerData.setParameterTypes(new Class<?>[] {char.class, char.class});
			invokerData.setArgs(new Object[] {null, null});
			runner.setContractTestRunnerConfiguration(conf);
			runner.run();
			fail("Exception should be thrown!");
		} catch (ContractTestRunnerException e) {
			System.currentTimeMillis();
//			ok
		}
		
		{
//			proper primitive arg
			ContractTestRunner runner = new ContractTestRunner(null);
			InvokerData invokerData = new InvokerData();
			ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
			Object target = new String("someValue");
			invokerData.setTarget(target);
			invokerData.setMethodName("replace");
			invokerData.setParameterTypes(new Class<?>[] {char.class, char.class});
			invokerData.setArgs(new Object[] {'o', 'a'});
			runner.setContractTestRunnerConfiguration(conf);
			Object[] result = runner.run();
			assertNotNull(result);
			assertNotNull(result[0]);
			assertEquals("sameValue",result[0]);
		}
	}

	@Test
	public void testRunValidWithBeforeRunnable() throws Exception {
		ContractTestRunner runner = new ContractTestRunner(null);
		InvokerData invokerData = new InvokerData();
		ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
//			
		RunnableExample runnable = new RunnableExample();
		conf.setBeforeRunnable(runnable);
		assertFalse(runnable.getFlag());
//			
		Object target = new String("someValue");
		invokerData.setTarget(target);
		invokerData.setMethodName("concat");
//		no parameter types specified
		invokerData.setParameterTypes(null);
		Object arg = new String("someOtherValue");
		invokerData.setArgs(new Object[] {arg});
		runner.setContractTestRunnerConfiguration(conf);
		
		Object[] result = runner.run();
		assertNotNull(result);
		assertNotNull(result[0]);
		assertEquals((""+target+arg), result[0]);
//		flag verification
		assertTrue(runnable.getFlag());
	}
	
	@Test
	public void testRunValidWithAfterRunnable() throws Exception {
		ContractTestRunner runner = new ContractTestRunner(null);
		InvokerData invokerData = new InvokerData();
		ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
//			
		RunnableExample runnable = new RunnableExample();
		conf.setAfterRunnable(runnable);
		assertFalse(runnable.getFlag());
//			
		Object target = new String("someValue");
		invokerData.setTarget(target);
		invokerData.setMethodName("concat");
//		no parameter types specified
		invokerData.setParameterTypes(null);
		Object arg = new String("someOtherValue");
		invokerData.setArgs(new Object[] {arg});
		runner.setContractTestRunnerConfiguration(conf);
		
		Object[] result = runner.run();
		assertNotNull(result);
		assertNotNull(result[0]);
		assertEquals((""+target+arg), result[0]);
//		flag verification
		assertTrue(runnable.getFlag());
	}
	
	@Test
	public void testRunValidWithBeforeDataList() throws Exception {
		ContractTestRunner runner = new ContractTestRunner(null);
		InvokerData invokerData = new InvokerData();
		ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
//			
		InternalCounterHolder beforeTesterTarget = new InternalCounterHolder();
		InvokerData beforeInvoker = new InvokerData();
		beforeInvoker.setTarget(beforeTesterTarget);
		beforeInvoker.setMethodName("increase");
		beforeInvoker.setParameterTypes(null);
		beforeInvoker.setArgs(null);
		List<InvokerData> beforeDataList = new ArrayList<InvokerData>();
		beforeDataList.add(beforeInvoker);
		beforeDataList.add(beforeInvoker);
		beforeDataList.add(beforeInvoker);
		conf.setBeforeDataList(beforeDataList);
//			
		Object target = new String("someValue");
		invokerData.setTarget(target);
		invokerData.setMethodName("concat");
//		no parameter types specified
		invokerData.setParameterTypes(null);
		Object arg = new String("someOtherValue");
		invokerData.setArgs(new Object[] {arg});
		runner.setContractTestRunnerConfiguration(conf);
		
		Object[] result = runner.run();
		assertNotNull(result);
		assertNotNull(result[0]);
		assertEquals((""+target+arg), result[0]);
//		validating counter
		assertEquals(beforeDataList.size(), beforeTesterTarget.getCounter());
	}
	
	@Test
	public void testRunValidWithAfterDataList() throws Exception {
		ContractTestRunner runner = new ContractTestRunner(null);
		InvokerData invokerData = new InvokerData();
		ContractTestRunnerConfiguration conf = new ContractTestRunnerConfiguration(invokerData);
//			
		InternalCounterHolder afterTesterTarget = new InternalCounterHolder();
		InvokerData afterInvoker = new InvokerData();
		afterInvoker.setTarget(afterTesterTarget);
		afterInvoker.setMethodName("increase");
		afterInvoker.setParameterTypes(null);
		afterInvoker.setArgs(null);
		List<InvokerData> afterDataList = new ArrayList<InvokerData>();
		afterDataList.add(afterInvoker);
		afterDataList.add(afterInvoker);
		afterDataList.add(afterInvoker);
		conf.setBeforeDataList(afterDataList);
//			
		Object target = new String("someValue");
		invokerData.setTarget(target);
		invokerData.setMethodName("concat");
//		no parameter types specified
		invokerData.setParameterTypes(null);
		Object arg = new String("someOtherValue");
		invokerData.setArgs(new Object[] {arg});
		runner.setContractTestRunnerConfiguration(conf);
		
		Object[] result = runner.run();
		assertNotNull(result);
		assertNotNull(result[0]);
		assertEquals((""+target+arg), result[0]);
//		validating counter
		assertEquals(afterDataList.size(), afterTesterTarget.getCounter());
	}
	
	class RunnableExample implements IRunnable {
		
		private Boolean flag = false;

		public Boolean getFlag() {
			return flag;
		}

		/* (non-Javadoc)
		 * @see eu.dnetlib.contract.runner.IRunnable#run(eu.dnetlib.contract.ctx.GlobalContractContext)
		 */
		public void run(GlobalContractContext context) {
			flag = true;
		}
	}
	
	class InternalCounterHolder {
		int counter = 0;
		
		public void increase() {
			this.counter++;
		}
		
		public int getCounter() {
			return counter;
		}
	}
}
