package gov.va.med.cds.util;

import static org.junit.Assert.assertTrue;
import gov.va.med.cds.junit.runners.SuiteAwareRunner;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith( SuiteAwareRunner.class )
public class QueryCacheUnitTest 
{
	
	@Test
	public void testQueryCacheManager()
	{
		QueryCacheManager m = QueryCacheManager.getInstance();
		assertTrue("The QueryCacheManager should not be null",m!=null);
		m.setEnabled(false);
		assertTrue("The QueryCacheManager should be disabled", m.isEnabled()==false);
		assertTrue("The cacheHitCount should be -1", m.getCacheHitCount()==-1);
		assertTrue("The cacheMissCount should be -1", m.getCacheMissCount()==-1);
		assertTrue("The maxDataAgeInSeconds should be set to default value", m.getMaxEntryAgeSeconds()== QueryCacheManager.DEFAULT_MAX_ENTRY_AGE_IN_SECONDS);
		m.setMaxEntryAgeSeconds(54);
		assertTrue("The maxDataAgeInSeconds should be set to 54", m.getMaxEntryAgeSeconds()== 54);
		m.setMaxEntriesPerDataSource(12);
		assertTrue("The MaxEntriesPerDataSource per datasource should be 12", m.getMaxEntriesPerDataSource()==12);
		assertTrue("The QueryCacheManager XML Element representation should not be null", m.toElement()!=null);
		assertTrue("The QueryCacheManager XML String representation should not be null", m.toString()!=null);
		assertTrue("The QueryCacheManager Dump should not be null", m.getQueryCacheManagerDump()!=null);
		
		m.setEnabled(true);
		assertTrue("The QueryCacheManager should be enabled", m.isEnabled());
		assertTrue("The cacheHitCount should be 0", m.getCacheHitCount()==0);
		assertTrue("The cacheMissCount should be 0", m.getCacheMissCount()==0);
		assertTrue("The QueryCacheManager XML Element representation should not be null", m.toElement()!=null);
		assertTrue("The QueryCacheManager XML String representation should not be null", m.toString()!=null);
		assertTrue("The QueryCacheManager Dump should not be null", m.getQueryCacheManagerDump()!=null);
		
		m.setMaxEntryAgeSeconds(55);
		assertTrue("The maxDataAgeInSeconds should be set to 55", m.getMaxEntryAgeSeconds()== 55);
		m.setMaxEntriesPerDataSource(15);
		assertTrue("The MaxEntriesPerDataSource per datasource should be 15", m.getMaxEntriesPerDataSource()==15);
		
		
	}

	@Test
	public void testQueryCacheManagerAcceptsNullValuesWhenDisabled()
	{
		QueryCacheManager m = QueryCacheManager.getInstance();
		
		try
		{
		m.setEnabled(false);
		m.put(null, null, null, null);
		}
		catch ( Exception e)
		{
			assertTrue("The QueryCacheManager should accept null params when disabled", false);
		}
		
		m.put(null, "", "", "",new ArrayList<Object>());
		ArrayList<Object> list1 = new ArrayList<Object>();
		list1.add("an item");
		m.put(null,"","","",list1);
		
	}

	
	
	@Test( expected=IllegalArgumentException.class)
	public void testQueryCacheManagerBadMaxDataAgeInSeconds()
	{
		QueryCacheManager.getInstance().setMaxEntryAgeSeconds(-5);
	}
	@Test( expected=IllegalArgumentException.class)
	public void testQueryCacheManagerBadMaxEntries()
	{
		QueryCacheManager.getInstance().setMaxEntriesPerDataSource(-10);
	}
	
	@Test( expected = IllegalArgumentException.class)
	public void testQueryCacheUtilQueryIdentifierCannotBeNull()
	{
		new QueryCacheUtil().getNamedQueryList(null, null, null);
	}
	
	@Test( expected = IllegalArgumentException.class)
	public void testQueryCacheUtilQueryIdentifierCannotBeEmptyString()
	{
		new QueryCacheUtil().getNamedQueryList(null, "", null);
	}
	
	@Test( expected = IllegalArgumentException.class)
	public void testQueryCacheUtilSessionCannotBeNull()
	{
		new QueryCacheUtil().getNamedQueryList(null, "someQueryName", null);
	}
	
	@Test
	public void testQueryCache()
	{
		/// test in and out functionality
		QueryCache cache = new QueryCache();
		List<Object> list = new ArrayList<Object>();
		BigDecimal in = new BigDecimal(5);
		list.add(in);		
		cache.put("testQueryName", "paramName", "paramValue", list);		
		List<Object> listOut = cache.get("testQueryName", "paramName", "paramValue");
		BigDecimal out = (BigDecimal) listOut.get(0);
		assertTrue("A Big Decimal with a value of 5 should be returned from the Cache but it not", in.equals(out));
		
		
		HashMap<String,Object> queryParams = new HashMap<String,Object>();
		queryParams.put("paramName", "paramValue");
		List<Object> listOut2 = cache.get("testQueryName", queryParams);
		assertTrue("The listOut should not be null", listOut2!=null);
		
		
		cache.setMaxEntryAgeInSeconds(1000);
		assertTrue("The Max Entry Age in Seconds should be 1000", cache.getMaxEntryAgeInSeconds()==1000);
		
		assertTrue("The Cache should have a hit count of 2", cache.getCacheHitCount()==2);
		
		cache.get("whoknows","param1","value1");		
		assertTrue("The Cache should have a miss count of 1", cache.getCacheMissCount()==1);
		
		cache.setMaxEntries(444);
		assertTrue("The Cache should have max entries of 444", cache.getMaxEntries()==444);
		
		cache.put("testQueryName", "paramName", "paramValue", list);
		assertTrue("The Cache should still have a size of 1", cache.size()==1);
		
		cache.put("testQueryName2", "paramName2", "paramValue2", list);
		
		cache.put("testQueryName3", "paramName3", "paramValue3", list);
		cache.setMaxEntries(5);
		
		assertTrue( "The XML QueryCache representation should contain the String 'paramName2'", cache.toString().indexOf("paramName2")!=-1);
		
		
		
		
		
		
	}

	@Test
	public void testQueryCacheMaxEntriesFunctionality() 
	{
		try {
			QueryCache cache = new QueryCache();
			cache.setMaxEntries(2);
			List<Object> list1 = new ArrayList<Object>();
			list1.add("value1");			
			cache.put("testQueryName1", "paramName1", "paramValue1", list1);
			List<Object> list2 = new ArrayList<Object>();
			list2.add("value2");			
			cache.put("testQueryName2", "paramName2", "paramValue2", list2);
			List<Object> list3 = new ArrayList<Object>();
			list3.add("value3");			
			cache.put("testQueryName3", "paramName3", "paramValue3", list3);
			assertTrue("The QueryCache should have exactly 2 items but does not", cache.size()==2);
			
			List<Object> listOut2 = cache.get("testQueryName2", "paramName2", "paramValue2");
			List<Object> listOut3 = cache.get("testQueryName3", "paramName3", "paramValue3");
			assertTrue("The last two lists should be returned but are not", listOut2!=null && listOut3!=null);
			
		} 
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Test
	public void testQueryCacheLRUFunctionality() 
	{
			/// the QueryCache currently uses a LRU cache item eviction policy (least recently used)
			/// so the first value in will be removed when the size of the cache is reached and a new value is put in			
			QueryCache cache = new QueryCache();
			cache.setMaxEntries(2);

			List<Object> list1 = new ArrayList<Object>();
			list1.add("value1");			
			cache.put("testQueryName1", "paramName1", "paramValue1", list1);
			List<Object> list2 = new ArrayList<Object>();
			list2.add("value2");			
			cache.put("testQueryName2", "paramName2", "paramValue2", list2);
			
			cache.get("testQueryName1", "paramName1", "paramValue1");  /// makes 1 the most recently used cached item
			
			List<Object> list3 = new ArrayList<Object>();
			list3.add("value3");			
			cache.put("testQueryName3", "paramName3", "paramValue3", list3);
			assertTrue("The QueryCache should have exactly 2 items but does not", cache.size()==2);
			
			List<Object> list1Out = cache.get("testQueryName1", "paramName1", "paramValue1");
			assertTrue("The list1Out value should not be null", list1Out!=null);
			
			List<Object> list2Out = cache.get("testQueryName2", "paramName2", "paramValue2");
			assertTrue("The list2Out value should be null ", list2Out==null);
			
			List<Object> list3Out = cache.get("testQueryName3", "paramName3", "paramValue3");
			assertTrue("The list3Out value should not be null ", list3Out!=null);
			
			List<Object> list4 = new ArrayList<Object>();
			list4.add("value4");
			cache.put("testQueryName4", "paramName4", "paramValue4",list4);
			
			cache.setMaxEntries(1);
			assertTrue("The cache should only have 1 cached value",cache.size()==1);
			
			List<Object> list4Out = cache.get("testQueryName4", "paramName4", "paramValue4");
			assertTrue("The 4th cached item should be in the cache but is not", list4Out!=null );			

			
	}
	
	@Test
	public void testQueryCacheMaxAgeFunctionality() 
	{
		try {
			QueryCache cache = new QueryCache();
			cache.setMaxEntryAgeInSeconds(1);
			List<Object> list = new ArrayList<Object>();
			BigDecimal in = new BigDecimal(5);
			list.add(in);
			cache.put("testQueryName", "paramName", "paramValue", list);
			Thread.sleep(1100);			
			List<Object> listOut = cache.get("testQueryName", "paramName", "paramValue");
			assertTrue("The Cached Value should have expired but did not", listOut==null);
		} 
		catch (Exception e) {
		}
		
		try {
			QueryCache cache = new QueryCache();
			cache.setMaxEntryAgeInSeconds(1000);
			List<Object> list = new ArrayList<Object>();
			BigDecimal in = new BigDecimal(5);
			list.add(in);
			cache.put("testQueryName", "paramName", "paramValue", list);
			Thread.sleep(1100);			
			List<Object> listOut = cache.get("testQueryName", "paramName", "paramValue");
			assertTrue("The Cached Value should not have expired but did", listOut!=null);
		} 
		catch (Exception e) {
		}
		
		try {
			QueryCache cache = new QueryCache();
			cache.setMaxEntryAgeInSeconds(2);
			List<Object> list1 = new ArrayList<Object>();
			BigDecimal in1 = new BigDecimal(5);
			list1.add(in1);
			cache.put("testQueryName1", "paramName1", "paramValue1", list1);
			Thread.sleep(2100);	
			
			// call the cache for the first element to cause the expired data to be removed
			cache.get("testQueryName1", "paramName1", "paramValue1");
			
			List<Object> list2 = new ArrayList<Object>();
			BigDecimal in2 = new BigDecimal(2);
			list2.add(in2);
			cache.put("testQueryName2", "paramName2", "paramValue2", list2);
			assertTrue("The Cache size should be 1 but is not", cache.size()==1);
			List<Object> listOut2 = cache.get("testQueryName2", "paramName2", "paramValue2");
			assertTrue("The Cached Value 2 should not have expired but did", listOut2!=null);
			
			Thread.sleep(1100);
			cache.setMaxEntryAgeInSeconds(1);
			assertTrue("The Cache should be empty due to all entries exceeding max age", cache.size()==0);
			
		} 
		catch (Exception e) {
		}

		
	}
	
	@Test
	public void testQueryCacheValueMethods() {
		List<Object> list = new ArrayList<Object>();
		BigDecimal in = new BigDecimal(5);
		list.add(in);
		QueryCacheValue val = new QueryCacheValue(list);
		assertTrue("The result should be false", val.exceedsMaxAge(-1)==false);
		
		assertTrue("The value should not be equal to the QueryCacheValue", !val.equals("someString"));
		
		Element e = val.toElement();
		assertTrue("The QueryCacheValue Element representation should not be null", e!=null);
		assertTrue("The QueryCacheValue Element representation should contain the value 5", e.asXML().indexOf("5")!=-1);
		assertTrue("The QueryCacheValue toString representation should contain the value 5", val.toString().indexOf("5")!=-1);
		
		List<Object> elements1 = new ArrayList<Object>();
		
		DocumentFactory df1 = new DocumentFactory();
		Element a1 = df1.createElement("a");
		Element b1 = df1.createElement("b");
		elements1.add(a1);
		elements1.add(b1);
		QueryCacheValue elementVal1 = new QueryCacheValue(elements1);
		assertTrue("The QueryCacheValue Element representation should contain the element 'a'", elementVal1.toElement().asXML().indexOf("a")!=-1);
		
		
	}	
	
	@Test (expected = IllegalArgumentException.class )
	public void testQueryCacheValueWithNullArguments()
	{
		new QueryCacheValue(null);
	}
	
	@Test
	public void testQueryCacheValueImmutability()
	{
		/// this test ensures that CachedValue are immutable  
		String message = "The cached value should be immutable, but is not ";
		ArrayList<Object> listGoingIn = new ArrayList<Object>();
		QueryCacheValue val = new QueryCacheValue(listGoingIn);
		listGoingIn.add("Some Value");
		
		List<Object> listComingOut = val.getCachedValue();
		assertTrue(message, listComingOut.size()==0);
		
		listComingOut.add("Another Item");
		
		List<Object> listFromCache = val.getCachedValue();
		assertTrue(message, listFromCache.size()==0);
		
	}
	
	@Test
	public void testQueryCacheValueMaxAgeFunctionality()
	{
		long seconds = 2;
		
		ArrayList<Object> list = new ArrayList<Object>();
		QueryCacheValue val = new QueryCacheValue(list);
		list.add("Some Value");
		assertTrue( "A problem exists with the max age functionality.", !val.exceedsMaxAge(seconds));
		
		try 
		{
			Thread.sleep(seconds * 1000 + 100);
			assertTrue("Max age should have been exceeded but was not.",val.exceedsMaxAge(seconds));			
		} 
		catch (Exception e) 
		{
		}		
		
		QueryCacheValue val2 = new QueryCacheValue(list);
		assertTrue("QueryCacheValue should always have a positive last update time", val2.getLastUpdateTime()>0);
		long timeNow = System.currentTimeMillis();
		assertTrue("QueryCacheValue should have an update time less than or equal to the present time", val.getLastUpdateTime()<timeNow);
		
	}
	
	@Test
	public void testQueryCacheValueEqualsFunctionality()
	{
		
		try 
		{
			ArrayList<Object> list1 = new ArrayList<Object>();
			list1.add("Some Value");
			QueryCacheValue val1 = new QueryCacheValue(list1);
			
			Thread.sleep(500);
			
			ArrayList<Object> list2 = new ArrayList<Object>();
			list2.add("Some Value");
			QueryCacheValue val2 = new QueryCacheValue(list2);
			
			assertTrue("Query Cache Values should equal each other even though their underlying timestamps are different", val1.equals(val2));
			
			list2.add("A second Value");
			QueryCacheValue val3 = new QueryCacheValue(list2);
			assertTrue("Query Cache Values should not equal each other", !val2.equals(val3));

		} 
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
	}
	
	
	
	
	@Test (expected = IllegalArgumentException.class )
	public void testQueryCacheKeyWithNullArguments()
	{
		new QueryCacheKey(null,null,null);
	}

	@Test (expected = IllegalArgumentException.class )
	public void testQueryCacheKeyWithQueryIdentifierArguments()
	{
		new QueryCacheKey(null,"testParamName","testObject");
	}

	@Test 
	public void testQueryCacheKeyOkWithNullParamArguments()
	{
		new QueryCacheKey("testQueryName",null,null);		
	}
	
	
	
	@Test
	public void testQueryCacheKeyCoreMethods()
	{
		String message = "QueryCacheKey key1 should not equal QueryCacheKey key2 since queryIdentifier arguments differ";
		QueryCacheKey key1 = new QueryCacheKey("test1","paramName1","paramValue2");
		QueryCacheKey key2 = new QueryCacheKey("test2","paramName1","paramValue2");
		assertTrue(message, !key1.equals(key2));
		
		message="QueryCacheKey key1 should not equal QueryCacheKey key2 since paramName arguments differ";
		key1 = new QueryCacheKey("test1","paramName1","paramValue1");
		key2 = new QueryCacheKey("test1","paramName2","paramValue1");
		assertTrue(message, !key1.equals(key2));

		message="QueryCacheKey key1 should not equal QueryCacheKey key2 since paramValue arguments differ";
		key1 = new QueryCacheKey("test1","paramName1","paramValue2");
		key2 = new QueryCacheKey("test1","paramName1","paramValue3");
		assertTrue(message, !key1.equals(key2));

		message="QueryCacheKey key1 should equal QueryCacheKey key2 since all arguments are the same";
		key1 = new QueryCacheKey("test1","paramName1",new BigDecimal(1));
		key2 = new QueryCacheKey("test1","paramName1",new BigDecimal(1));
		assertTrue(message, key1.equals(key2));
		
		message="QueryCacheKey key1 should not equal QueryCacheKey key2 since key1 has no arguments and key2 has arguments";
		key1 = new QueryCacheKey("test1",null,null);
		key2 = new QueryCacheKey("test1","paramName1",new BigDecimal(1));
		assertTrue(message, !key1.equals(key2));
		
		message="QueryCacheKey key1 should equal QueryCacheKey key2 since both have no arguments and the queryIdentifier's are the same";
		key1 = new QueryCacheKey("test1",null,null);
		key2 = new QueryCacheKey("test1",null,null);
		assertTrue(message, key1.equals(key2));
		
		message="QueryCacheKey key1 hashCode() should equal QueryCacheKey key2 hashCode() since all values are the same";
		key1 = new QueryCacheKey("test1","paramName1","paramValue1");
		key2 = new QueryCacheKey("test1","paramName1","paramValue1");
		assertTrue(message, key1.hashCode()== key2.hashCode());
		
		message="QueryCacheKey key1 hashCode() should equal QueryCacheKey key2 hashCode() because param values are the same";
		HashMap<String,Object> params1 = new HashMap<String,Object>();
		params1.put("paramName1", new BigDecimal(1));
		params1.put("paramName2", new BigDecimal(2));
		HashMap<String,Object> params2 = new HashMap<String,Object>();
		params2.put("paramName1", new BigDecimal(1));
		params2.put("paramName2", new BigDecimal(2));
		key1 = new QueryCacheKey("test1",params1);
		key2 = new QueryCacheKey("test1",params2);
		assertTrue(message, key1.hashCode()== key2.hashCode());
		message="QueryCacheKey key1 should equal QueryCacheKey key2 because param values are the same";
		assertTrue(message, key2.equals(key1));
		
		message="QueryCacheKey key1 hashCode() should not equal QueryCacheKey key2 hashCode() because param values are different";
		params1 = new HashMap<String,Object>();
		params1.put("paramName1", new BigDecimal(1));
		params1.put("paramName2", new BigDecimal(2));
		params2 = new HashMap<String,Object>();
		params2.put("paramName1", new BigDecimal(3));
		params2.put("paramName2", new BigDecimal(4));
		key1 = new QueryCacheKey("test1",params1);
		key2 = new QueryCacheKey("test1",params2);
		assertTrue(message, key1.hashCode()!= key2.hashCode());
		message="QueryCacheKey key1 should not equal QueryCacheKey key2 because param values are different";
		assertTrue(message, !key2.equals(key1));
		
		
		String queryIdentifier = "defIdentifier";
		String paramName="abcParam";
		String paramValue = "xyzValue";
		key1 = new QueryCacheKey(queryIdentifier, paramName, paramValue);
		message ="QueryCacheKey toString() method should contain the values '" + queryIdentifier + "' and '"+ paramName + "' and '"+paramValue+"'"; 
		assertTrue( message, key1.toString().indexOf(queryIdentifier)!=-1 && key1.toString().indexOf(paramName)!=-1 && key1.toString().indexOf(paramValue)!=-1 );

		message = "QueryCacheKey should not be equal to a String Object";
		assertTrue(message, !key1.equals("random string"));
		
	}
	
	@Test
	public void testQueryKeytoElementMethod()
	{
		QueryCacheKey key = new QueryCacheKey("abc");
		Element e = key.toElement();
		assertTrue( "QueryCacheKey Element represenation should not be null ", e!=null);
		assertTrue( "QueryCacheKey xml represenation should contain the text 'abc'", e.asXML().indexOf("abc")!=-1);
		assertTrue("QueryCacheKey toString() represenation should contain the text 'abc'", key.toString().indexOf("abc")!=-1);
		
	}
	
	@Test
	public void testQueryCacheKeyEquals()
	{
		QueryCacheKey key = new QueryCacheKey("abc");
		assertTrue("QueryCacheKey objects should be the same", key.equals(key));
		
	}
	
	@Test( expected=java.lang.IllegalArgumentException.class)
	public void testNullValueInQueryIdentifierOnlyContructor()
	{
		new QueryCacheKey(null );		
	}	
	
	@Test( expected=java.lang.IllegalArgumentException.class)
	public void testNullQueryCacheKeyIdentifier()
	{
		new QueryCacheKey(null,"someParam","someValue" );
		
	}
	
	@Test( expected=java.lang.IllegalArgumentException.class)
	public void testNullQueryCacheKeyIdentifier3()
	{
		new QueryCacheKey(null,null );
		
	}

}


