package com.hammurapi.common.extract.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

import com.hammurapi.extract.AbstractExtractor;
import com.hammurapi.extract.ComparisonResult;
import com.hammurapi.extract.ComparisonResult.Type;
import com.hammurapi.extract.Constant;
import com.hammurapi.extract.Equal;
import com.hammurapi.extract.Extractor;
import com.hammurapi.extract.ExtractorFactory;
import com.hammurapi.extract.IndexedExtractor;
import com.hammurapi.extract.InstanceOfPredicate;
import com.hammurapi.extract.MappedPredicate;
import com.hammurapi.extract.Predicate;
import com.hammurapi.store.local.LocalStore;

public class ExtractorTests {

	@Test
	public void testExtract() {	
		Account account = new Account();
		account.setNumber(123);
		AccountNumberExtractor accountNumberExtractor = new AccountNumberExtractor();
		Map<LocalStore<Account, Integer>, Map<Extractor<Account, ? super Integer, LocalStore<Account, Integer>>, ? super Integer>> cache = new HashMap<LocalStore<Account,Integer>, Map<Extractor<Account,? super Integer,LocalStore<Account,Integer>>,? super Integer>>(); 
		Integer accountNumber = accountNumberExtractor.extract(null, cache, account);
		assertEquals(accountNumber.intValue(), 123);
	}

	public static class Calculator {
		
		int addCounter;
		int multiplyCounter;
	
		int add(int a, int b) {
			++addCounter;
			return a+b;
		}
		
		int multiply(int a, int b) {
			++multiplyCounter;
			return a*b;
		}
	}
	
	public static class AddExtractor extends AbstractExtractor<Calculator, Integer, Object> {

		private int a;
		private int b;

		public AddExtractor(int a, int b) {
			super(0, null, false, 0);
			this.a = a;
			this.b = b;
		}

		@Override
		protected Integer extractInternal(
				Object context,
				Map<Object, Map<Extractor<Calculator, ? super Integer, Object>, ? super Integer>> cache,
				Calculator... obj) {
			return obj[0].add(a,b);
		}
		
	}
	
	public static class MultiplyExtractor extends AbstractExtractor<Calculator, Integer, Object> {

		private int a;
		private int b;

		public MultiplyExtractor(int a, int b) {
			super(0, null, false, 0);
			this.a = a;
			this.b = b;
		}

		@Override
		protected Integer extractInternal(
				Object context,
				Map<Object, Map<Extractor<Calculator, ? super Integer, Object>, ? super Integer>> cache,
				Calculator... obj) {
			return obj[0].multiply(a,b);
		}
		
	}
	
	@Test
	public void testCaching() {
		Calculator calculator = new Calculator();
		Map<Object, Map<Extractor<Calculator, ? super Integer, Object>, ? super Integer>> cache = new HashMap<Object, Map<Extractor<Calculator,? super Integer,Object>,? super Integer>>();
		
		// Extracting 5+6
		AddExtractor addExtractor = new AddExtractor(5, 6);
		Integer addResult = addExtractor.extract(null, cache, calculator);
		assertEquals(5+6, addResult.intValue());
		assertEquals(1, calculator.addCounter);
		
		// Extracting 33+88
		AddExtractor addExtractor2 = new AddExtractor(33, 88);
		Integer addResult2 = addExtractor2.extract(null, cache, calculator);
		assertEquals(33+88, addResult2.intValue());
		assertEquals(2, calculator.addCounter);
		
		// Extracting 5+6, should come from cache.
		addResult = addExtractor.extract(null, cache, calculator);
		assertEquals(5+6, addResult.intValue());
		assertEquals(2, calculator.addCounter);
		
		// Extracting 33+88
		MultiplyExtractor multiplyExtractor = new MultiplyExtractor(33, 88);
		Integer multiplyResult = multiplyExtractor.extract(null, cache, calculator);
		assertEquals(33*88, multiplyResult.intValue());
		assertEquals(1, calculator.multiplyCounter);
		
	}
	
	@Test
	public void testEquality() {
		Predicate<Account, LocalStore<Account,Integer>> predicate = ExtractorFactory.INSTANCE.createPredicate(
				"javascript",
				"account.getNumber()!=123", 
				new String[] {"account"}, 
				new Class[] {Account.class},
				Object.class, 
				this.getClass().getClassLoader());
		
		Predicate<Account, LocalStore<Account,Integer>> predicate2 = ExtractorFactory.INSTANCE.createPredicate(
				"javascript",
				"account.getNumber()!=123", 
				new String[] {"account"}, 
				new Class[] {Account.class},
				Object.class, 
				this.getClass().getClassLoader());
		
		assertEquals(predicate, predicate2);
		
		Predicate<Account, LocalStore<Account,Integer>> predicate3 = ExtractorFactory.INSTANCE.createPredicate(
				"javascript",
				"account.getNumber() != 123", 
				new String[] {"account"}, 
				new Class[] {Account.class},
				Object.class, 
				this.getClass().getClassLoader());
		
		assertFalse(predicate3.equals(predicate));
		
		Predicate<Account, LocalStore<Account,Integer>> predicate4 = ExtractorFactory.INSTANCE.createPredicate(
				"javascript",
				"account.getNumber()!=123", 
				new String[] {"balance"}, 
				new Class[] {Account.class},
				Object.class, 
				this.getClass().getClassLoader());
		
		assertFalse(predicate4.equals(predicate));
	}
	
	@Test
	public void testCost() {	
		Account account = new Account();
		account.setNumber(123);
		account.setBalance(new BigDecimal(3.14159));
		Extractor<Account, BigDecimal, LocalStore<Account, Integer>> balanceExtractor = new AbstractExtractor<Account, BigDecimal, LocalStore<Account, Integer>>(0, TimeUnit.MICROSECONDS, false, 0) {
		
			@Override
			protected BigDecimal extractInternal(
					LocalStore<Account, Integer> context,
					Map<LocalStore<Account, Integer>, Map<Extractor<Account, ? super BigDecimal, LocalStore<Account, Integer>>, ? super BigDecimal>> cache,
					Account... obj) {
				return obj[0].getBalance();
			}
			
		};
		BigDecimal balance = balanceExtractor.extract(null, null, account);
		// Verify that extractor cost is 100000 (100 milliseconds) +- 10 millisecond.
		assertEquals(100000, balanceExtractor.getCost(), 20000);
		assertEquals(new BigDecimal(3.14159), balance);
	}
	
	@Test
	public void testMappedPredicate() {
		Predicate<Object, Object> p1 = MappedPredicate.mapPredicate(new InstanceOfPredicate<Object, Object>(new IndexedExtractor<Object, Object>(3), String.class), new int[] {1, 10, 3});
		Predicate<Object, Object> p2 = MappedPredicate.mapPredicate(new InstanceOfPredicate<Object, Object>(new IndexedExtractor<Object, Object>(3), String.class), new int[] {1, 10, 3});
		ComparisonResult cr = p1.compareTo(p2);
		
		assertNotNull(cr);
		assertEquals(Type.EQUAL, cr.getType());
		assertTrue(cr.isOneToOneMapping());
	}
	
	enum TestEnum {
		ONE, TWO
	}
	
	@Test
	public void testEnumEquals() {
		Constant c1 = new Constant(TestEnum.ONE);
		Constant c2 = new Constant(TestEnum.TWO);
		Constant c3 = new Constant(null);
		
		Equal equal = new Equal(0, null, c1, c2, true);
		equal.extract(null, null, null);
		
		equal = new Equal(0, null, c1, c2, false);
		equal.extract(null, null, null);
		
		equal = new Equal(0, null, c1, c1, true);
		equal.extract(null, null, null);
		
		equal = new Equal(0, null, c1, c2, false);
		equal.extract(null, null, null);
		
		equal = new Equal(0, null, c3, c2, true);
		equal.extract(null, null, null);
		
		equal = new Equal(0, null, c3, c2, false);
		equal.extract(null, null, null);
		
		equal = new Equal(0, null, c3, c3, true);
		equal.extract(null, null, null);
		
		equal = new Equal(0, null, c3, c3, false);
		equal.extract(null, null, null);
		
	}
	
}
