package org.jboss.cache.aop.collection;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheException;
import org.jboss.cache.aop.PojoCache;
import org.jboss.cache.aop.TreeCacheAopTester;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Iterator;
import java.util.Collection;
import java.util.HashSet;
import java.util.HashMap;


/**
 * Generic Collection class support testing.
 */

public class CollectionAopTest extends TestCase
{
   Log log=LogFactory.getLog(CollectionAopTest.class);
   TreeCacheAopTester tester;
   PojoCache cache;

   public CollectionAopTest(String name)
   {
      super(name);
   }

   protected void setUp() throws Exception
   {
      super.setUp();
      log.info("setUp() ....");
      String configFile = "META-INF/local-service.xml";
      tester = new TreeCacheAopTester(configFile);

      cache = tester.getCache();
   }

   protected void tearDown() throws Exception
   {
      super.tearDown();
      tester.stop();
      tester = null;
   }

   /**
    * Testing using LinkedList proxy.
    * @throws Exception
    */
   public void testLinkedList() throws Exception {
      LinkedList list = new LinkedList();
      LinkedList list1;
      list.add("English");
      try {
         cache.putObject("/aop/list", list);
         list = (LinkedList)cache.getObject("/aop/list");
         list.add("French");
         list1 = (LinkedList)cache.getObject("/aop/list");
         assertEquals("Size of list ", 2, list1.size());
      } catch (CacheException e) {
         fail("pubtObject fails");
         throw e;
      }
   }

   /**
    * Testing using LinkedMap proxy.
    * @throws Exception
    */
   public void testLinkedMap() throws Exception {
      LinkedHashMap map = new LinkedHashMap();
      LinkedHashMap map1;
      map.put("1", "English");
      try {
         cache.putObject("/aop/map", map);
         map = (LinkedHashMap)cache.getObject("/aop/map");
         map.put("2", "French");
         map1 = (LinkedHashMap)cache.getObject("/aop/map");
         assertEquals("Size of map ", 2, map1.size());
      } catch (CacheException e) {
         fail("pubtObject fails");
         throw e;
      }
   }

   /**
    * Testing using LinkedSet proxy.
    * @throws Exception
    */
   public void testLinkedSet() throws Exception {
      LinkedHashSet set = new LinkedHashSet();
      LinkedHashSet set1;
      set.add("English");
      Map map;
      try {
         cache.putObject("/aop/set", set);
         set = (LinkedHashSet)cache.getObject("/aop/set");
         set.add("French");
         set1 = (LinkedHashSet)cache.getObject("/aop/set");
         assertEquals("Size of set ", 2, set1.size());
      } catch (CacheException e) {
         fail("pubtObject fails");
         throw e;
      }
   }

   public static Test suite() throws Exception
   {
      return new TestSuite(CollectionAopTest.class);
   }

   public static void main(String[] args) throws Exception
   {
      junit.textui.TestRunner.run(suite());
   }

   // tests for each of the methods in Collection interface
   public void testSize()  throws Exception {

      LinkedList list = new LinkedList();
      list.add("java");
      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         assertEquals("size of collection", 1, list.size());
         list.add("c");
         list.add("lisp");
         assertEquals("size of collection", 3, list.size());
         list.remove("lisp");
         assertEquals("size of collection", 2, list.size());
         list.remove("c");
         assertEquals("size of collection", 1, list.size());
         list.clear();
         assertEquals("size of collection", 0, list.size());
      }
      catch (CacheException e) {
         fail("testSize "+ e.getMessage());
         throw e;
      }

   }

   public void testIsEmpty() throws Exception
   {
      LinkedList list = new LinkedList();

      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         assertTrue("collection is empty", list.isEmpty());
         list.add("c");
         list.add("lisp");
         assertFalse("collection is not empty", list.isEmpty());
         list.remove("lisp");
         assertFalse("collection is not empty", list.isEmpty());
         list.remove("c");
         assertTrue("collection is empty", list.isEmpty());
      }
      catch (CacheException e) {
         fail("testIsEmpty "+ e.getMessage());
         throw e;
      }
   }

   public void testContains() throws Exception
   {
      LinkedList list = new LinkedList();
      list.add("java");
      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         assertFalse("collection doesn't contains", list.contains("lisp"));
         list.add("c");
         list.add("lisp");
         assertTrue("collection contains", list.contains("lisp"));
         list.remove("lisp");
         assertFalse("collection doesn't contain", list.contains("lisp"));
         list.remove("c");
         list.clear();
         assertFalse("collection doesn't contains", list.contains("c"));
      }
      catch (CacheException e) {
         fail("testContains "+ e.getMessage());
         throw e;
      }

   }

   public void testIterator() throws Exception
   {
      LinkedList list = new LinkedList();
      LinkedList list2 = new LinkedList();
      list2.add("java");
      list2.add("c");
      list2.add("lisp");
      list2.add("c++");
      list2.add("forth");
      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         list.addAll(list2);
         Iterator iter = list.iterator();
         while(iter.hasNext()) {
            Object o = iter.next();
            assertTrue("Iteration test",list2.contains(o));
         }
      }
      catch (CacheException e) {
         fail("testIterator "+ e.getMessage());
         throw e;
      }
   }

   public void testToArray() throws Exception
   {
      LinkedList list = new LinkedList();
      LinkedList list2 = new LinkedList();
      list2.add("java");
      list2.add("c");
      list2.add("lisp");
      list2.add("c++");
      list2.add("forth");
      list.addAll(list2);
      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         Object[] values = list.toArray();

         for(int looper = 0; looper < values.length; looper ++) {
            assertTrue("toArray test",list2.contains(values[looper]));
         }

         values = new Object[1];
         values = list.toArray(values);  // test with too small of an array
         for(int looper = 0; looper < values.length; looper ++) {
            assertTrue("toArray test",list2.contains(values[looper]));
         }

         values = new Object[10];
         values = list.toArray(values);  // test with too large of an array, result should be null padded
         for(int looper = 0; looper < values.length; looper ++) {
            assertTrue("toArray test",(values[looper] == null || list2.contains(values[looper])));
         }

      }
      catch (CacheException e) {
         fail("testToArray "+ e.getMessage());
         throw e;
      }
   }

   public void testAdd() throws Exception
   {
      LinkedList list = new LinkedList();
      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         list.add("java");
         assertTrue("add test", list.contains("java"));
         assertFalse("add test", list.contains("c#"));
      }
      catch (CacheException e) {
         fail("testAdd "+ e.getMessage());
         throw e;
      }

   }

   public void testRemove() throws Exception
   {
      LinkedList list = new LinkedList();
      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         list.add("java");
         assertTrue(list.remove("java"));
         assertFalse("remove test", list.contains("java"));
      }
      catch (CacheException e) {
         fail("testRemove "+ e.getMessage());
         throw e;
      }

   }

   public void testAddAll() throws Exception
   {
      LinkedList list = new LinkedList();
      LinkedList list2 = new LinkedList();
      list2.add("java");
      list2.add("c");
      list2.add("lisp");
      list2.add("c++");
      list2.add("forth");

      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         assertTrue(list.addAll(list2));
         Object[] values = list.toArray();

         for(int looper = 0; looper < values.length; looper ++) {
            assertTrue("testAddAll",list2.contains(values[looper]));
         }
      }
      catch (CacheException e) {
         fail("testAddAll "+ e.getMessage());
         throw e;
      }

   }

   public void testClear() throws Exception
   {
      LinkedList list = new LinkedList();
      LinkedList list2 = new LinkedList();
      list2.add("java");
      list2.add("c");
      list2.add("lisp");
      list2.add("c++");
      list2.add("forth");

      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         assertTrue(list.addAll(list2));
         assertTrue("testClear",list.size() > 0);
         list.clear();
         assertTrue("testClear",list.size() == 0);
      }
      catch (CacheException e) {
         fail("testClear "+ e.getMessage());
         throw e;
      }

   }

   public void testRetainAll() throws Exception
   {
      LinkedList list = new LinkedList();
      LinkedList list2 = new LinkedList();
      list2.add("java");
      list2.add("c");
      list2.add("lisp");
      list2.add("c++");
      list2.add("forth");

      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         assertTrue(list.addAll(list2));
         list2.remove("c");
         list2.remove("lisp");
         list2.remove("c++");
         list2.remove("forth");
         assertTrue("testRetainAll", list.retainAll(list2) );
         // should only have java left
         assertTrue("testRetainAll, list size should be 1 but is " + list.size(), list.size() == 1);
         assertTrue("testRetainAll", list.contains("java"));
      }
      catch (CacheException e) {
         fail("testRetainAll "+ e.getMessage());
         throw e;
      }


   }

   public void testRemoveAll() throws Exception
   {
      LinkedList list = new LinkedList();
      LinkedList list2 = new LinkedList();
      list2.add("java");
      list2.add("c");
      list2.add("lisp");
      list2.add("c++");
      list2.add("forth");

      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         assertTrue(list.addAll(list2));
         list2.remove("java");
         assertTrue("testRemoveAll", list.removeAll(list2) );
         // should only have java left
         assertTrue("testRemoveAll", list.size() == 1);
         assertTrue("testRemoveAll", list.contains("java"));
      }

      catch (CacheException e) {
         fail("testRemoveAll "+ e.getMessage());
         throw e;
      }


   }

   public void testContainsAll() throws Exception
   {
      LinkedList list = new LinkedList();
      LinkedList list2 = new LinkedList();
      list2.add("java");
      list2.add("c");
      list2.add("lisp");
      list2.add("c++");
      list2.add("forth");

      try {
         cache.putObject("/language/list", list);
         list = (LinkedList)cache.getObject("/language/list");
         assertTrue(list.addAll(list2));
         list2.remove("java");
         assertTrue("testContainsAll", list.containsAll(list2) );
         list.remove("c");
         assertFalse("testContainsAll", list.containsAll(list2) );
      }
      catch (CacheException e) {
         fail("testContainsAll "+ e.getMessage());
         throw e;
      }
   }

   /**
    * Commented it out now. We should move this to perf directory.
    * @throws Exception
    */
   public void XtestListAddPerformance() throws Exception
   {
      Collection cacheList = new LinkedList();
      Collection jdkList = new LinkedList();
      addPerformance(cacheList, jdkList, "List");
   }

   public void XtestSetAddPerformance() throws Exception
   {
      Collection cacheList = new HashSet();
      Collection jdkList = new HashSet();
      addPerformance(cacheList, jdkList, "Set");
   }

   public void XtestMapAddPerformance() throws Exception
   {
      Map cacheList = new HashMap();
      Map jdkList = new HashMap();
      addPerformance(cacheList, jdkList, "Map");
   }

   public void XtestListIterPerformance() throws Exception
   {
      Collection cacheList = new LinkedList();
      Collection jdkList = new LinkedList();
      iterPerformance(cacheList, jdkList, "List");
   }

   public void XtestSetIterPerformance() throws Exception
   {
      Collection cacheList = new HashSet();
      Collection jdkList = new HashSet();
      iterPerformance(cacheList, jdkList, "Set");
   }

   public void XtestMapIterPerformance() throws Exception
   {
      Map cacheList = new HashMap();
      Map jdkList = new HashMap();
      iterPerformance(cacheList, jdkList, "Map");
   }

   public void addPerformance(Collection cacheList, Collection jdkList, String label) throws Exception {
      try {
         cache.putObject("/language/list", cacheList);
         cacheList = (Collection)cache.getObject("/language/list");

         long start = System.currentTimeMillis();
         collectionAdd(1000, jdkList);
         long jdkTime = System.currentTimeMillis() - start;

         start = System.currentTimeMillis();
         collectionAdd(1000, cacheList);
         long cacheTime = System.currentTimeMillis() - start;
         //assertTrue(label + " 1000 add performance, jdk time=" + jdkTime +", cache time=" + cacheTime, goodEnough(cacheTime,jdkTime));
         log.info( label + " 1000 add performance, jdk time=" + jdkTime +", cache time=" + cacheTime);
      }
      catch (CacheException e) {
         fail("testAddAll "+ e.getMessage());
         throw e;
      }
   }

   public void addPerformance(Map cacheList, Map jdkList, String label) throws Exception {
      try {
         cache.putObject("/language/list", cacheList);
         cacheList = (Map)cache.getObject("/language/list");

         long start = System.currentTimeMillis();
         collectionAdd(1000, jdkList);
         long jdkTime = System.currentTimeMillis() - start;

         start = System.currentTimeMillis();
         collectionAdd(1000, cacheList);
         long cacheTime = System.currentTimeMillis() - start;
         //assertTrue(label + " 1000 add performance, jdk time=" + jdkTime +", cache time=" + cacheTime, goodEnough(cacheTime,jdkTime));
         log.info( label + " 1000 add performance, jdk time=" + jdkTime +", cache time=" + cacheTime);
      }
      catch (CacheException e) {
         fail("testAddAll "+ e.getMessage());
         throw e;
      }
   }

   public void iterPerformance(Collection cacheList, Collection jdkList, String label) throws Exception {
         cache.putObject("/language/list", cacheList);
      cacheList = (Collection)cache.getObject("/language/list");

      collectionAdd(1000, jdkList);
      collectionAdd(1000, cacheList);

      long start = System.currentTimeMillis();
      Iterator iter = jdkList.iterator();
      while(iter.hasNext())
         iter.next();
      long jdkTime = System.currentTimeMillis() - start;

      start = System.currentTimeMillis();
      iter = cacheList.iterator();
      while(iter.hasNext())
         iter.next();
      long cacheTime = System.currentTimeMillis() - start;
      //assertTrue(label + " 1000 iter performance, jdk time=" + jdkTime +", cache time=" + cacheTime, goodEnough(cacheTime,jdkTime));
      log.info( label + " 1000 iter performance, jdk time=" + jdkTime +", cache time=" + cacheTime);
   }

   public void iterPerformance(Map cacheList, Map jdkList, String label) throws Exception {
      cache.putObject("/language/list", cacheList);
      cacheList = (Map)cache.getObject("/language/list");

      collectionAdd(1000, jdkList);
      collectionAdd(1000, cacheList);

      long start = System.currentTimeMillis();
      Iterator iter = jdkList.keySet().iterator();
      while(iter.hasNext())
         iter.next();
      long jdkTime = System.currentTimeMillis() - start;

      start = System.currentTimeMillis();
      iter = cacheList.keySet().iterator();
      while(iter.hasNext())
         iter.next();
      long cacheTime = System.currentTimeMillis() - start;

      //assertTrue(label + " 1000 iter performance, jdk time=" + jdkTime +", cache time=" + cacheTime, goodEnough(cacheTime,jdkTime));
      log.info( label + " 1000 iter performance, jdk time=" + jdkTime +", cache time=" + cacheTime);
      System.out.println( label + " 1000 iter performance, jdk time=" + jdkTime +", cache time=" + cacheTime);
   }

   public void collectionAdd(int count, Collection c)  throws Exception {
      for( int looper = 0; looper < count; looper ++) {
         String value = Integer.toString(looper);
         c.add(value);
      }
   }

   public void collectionAdd(int count, Map c)  throws Exception {
      for( int looper = 0; looper < count; looper ++) {
         String value = Integer.toString(looper);
         c.put(value,value);
      }
   }

   public boolean goodEnough( double cacheTime, double jdkTime) {
      if(jdkTime ==0)
         jdkTime =1;

      double percent = ((cacheTime - jdkTime) / jdkTime) * 100.0;

      log.info("percent =" + percent + " cache=" + cacheTime +" jdk =" + jdkTime);
      return percent < 100.0;
   }


}

