package org.jboss.cache.pojo.collection;

import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.aop.proxy.ClassProxy;
import org.jboss.cache.pojo.PojoCache;
import org.jboss.cache.pojo.PojoCacheException;
import org.jboss.cache.pojo.PojoCacheFactory;
import org.jboss.cache.pojo.test.Address;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * Map interface testing.
 *
 * @author Ben Wang
 */

@Test(groups = {"functional"})
public class CachedMapTest
{
   Log log = LogFactory.getLog(CachedMapTest.class);
   PojoCache cache_;
   Map<Object, Object> hobbies;


   @BeforeMethod(alwaysRun = true)
   protected void setUp() throws Exception
   {
      log.info("setUp() ....");
      String configFile = "META-INF/local-service.xml";
      boolean toStart = false;
      cache_ = PojoCacheFactory.createCache(configFile, toStart);
      cache_.start();

      stage();
   }

   @AfterMethod(alwaysRun = true)
   protected void tearDown() throws Exception
   {
      cache_.stop();
   }

   @SuppressWarnings("unchecked")
   protected void stage() throws Exception
   {
      hobbies = new HashMap<Object, Object>();
      hobbies.put("1", "golf");
      hobbies.put("2", "tennis");
      hobbies.put("3", "polo");

      cache_.attach("/person/test7", hobbies);

      hobbies = (Map<Object, Object>) cache_.find("/person/test7");
      assertEquals("Map size", 3, hobbies.size());

      if (!(hobbies instanceof ClassProxy || hobbies instanceof Map))
      {
         fail("testPut(): hobbies is not instance of ClassProxy nor Map");
      }
   }

   /**
    * Test simple put
    *
    * @throws Throwable
    */
   public void testPut() throws Throwable
   {
      int size = hobbies.size();
      assertEquals("Size is ", 3, size);

      hobbies.put("6", "baseball");
      size = hobbies.size();
      assertEquals("Size is ", 4, size);

   }

   public void testAddAndRemoveIndex() throws Throwable
   {
      hobbies.put("4", "baseball");
      int size = hobbies.size();
      assertEquals("Size is ", 4, size);

      assertTrue("Skill contain Golf ", hobbies.containsKey("3"));

      hobbies.remove("3");
      size = hobbies.size();
      assertEquals("Size is ", 3, size);
      assertFalse("Skill does not contain 3 anymore ", hobbies.containsKey("3"));

      hobbies.clear();
      size = hobbies.size();
      assertEquals("Size is ", 0, size);

      assertTrue("Should be empty", hobbies.isEmpty());
   }

   public void testPutAllEtc() throws Throwable
   {
      Map<Object, Object> map = new HashMap<Object, Object>();
      map.put("4", "pingpong");
      map.put("5", "handball");

      hobbies.putAll(map);
      int size = hobbies.size();
      assertEquals("Size is ", 5, size);

      assertTrue("Key is ", hobbies.containsKey("4"));

      Set<Object> keys = hobbies.keySet();
      assertEquals("Key size ", 5, keys.size());

      Set<Map.Entry<Object, Object>> entries = hobbies.entrySet();
      assertEquals("Entry size ", 5, entries.size());

   }

   public void testLongValue() throws Throwable
   {
      Long val = new Long("8225676592564383");
      Long val2 = 8225676592564383L;
      assertTrue(0 == val.compareTo(val2));   // sanity check
      hobbies.put(val, "prateek");
      assertTrue(hobbies.containsKey(val));
      assertTrue(hobbies.get(val).equals("prateek"));
   }

   public void testEntrySet() throws Throwable
   {
      System.out.println("Map " + hobbies.toString());
      for (Iterator<Map.Entry<Object, Object>> i = hobbies.entrySet().iterator(); i.hasNext();)
      {
         Map.Entry<Object, Object> entry = i.next();
         System.out.println("Entry key and value " + entry.getKey() + " " + entry.getValue());
      }
   }

   public void testValues() throws Throwable
   {
      System.out.println("Map " + hobbies.toString());

      Set<String> correct = new HashSet<String>();
      correct.add("golf");
      correct.add("tennis");
      correct.add("polo");

      Collection<Object> values = hobbies.values();
      assertEquals("Correct number of elements in value collection",
                   correct.size(), values.size());

      for (String s : correct)
         assertTrue(values.contains(s));

      Iterator<Object> iter = values.iterator();
      while (iter.hasNext())
      {
         String value = (String) iter.next();
         assertTrue(value + " expected", correct.remove(value));
      }
      assertTrue("No missing elements from iterator", correct.size() == 0);

      iter.remove();
      assertTrue("2 elements left after remove via iter", values.size() == 2);
      assertTrue("Iter removal reflected in map", hobbies.size() == 2);

      Object[] data = values.toArray();
      assertTrue("2 elements in values array", data.length == 2);

      values.remove(data[0]);
      assertTrue("1 element left after remove", values.size() == 1);
      assertTrue("Removal reflected in map", hobbies.size() == 1);

      values.clear();
      assertTrue("0 elements left after clear", values.size() == 0);
      assertTrue("Clear reflected in map", hobbies.size() == 0);
   }

   public void testContainsValue() throws Throwable
   {
      System.out.println("Map " + hobbies.toString());
      assertTrue("contains golf", hobbies.containsValue("golf"));
      assertTrue("contains tennis", hobbies.containsValue("tennis"));
      assertTrue("contains polo", hobbies.containsValue("polo"));
      assertFalse("does not contain squash", hobbies.containsValue("squash"));
   }

   public void testEquals1() throws Throwable
   {
      Map<String, String> map = new HashMap<String, String>();
      map.put("1", "test");
      map.put("4", "test");
      map.put("2", "tennis");
      assertFalse("Map should not be the same ", map.equals(hobbies));
   }

   @SuppressWarnings("unchecked")
   public void testEquals2() throws Throwable
   {
      Map<String, String> map1 = new HashMap<String, String>();
      cache_.attach("map1", map1);
      map1 = (Map<String, String>) cache_.find("map1");
      map1.put("1", "test");

      Map<String, String> map2 = new HashMap<String, String>();
      cache_.attach("map2", map2);
      map2 = (Map<String, String>) cache_.find("map2");
      map2.put("1", "me");

      assertFalse("Map should not be the same ", map1.equals(map2));
   }

   @SuppressWarnings("unchecked")
   public void testEquals3() throws Throwable
   {
      Map<String, String> map1 = new HashMap<String, String>();
      cache_.attach("map1", map1);
      map1 = (Map<String, String>) cache_.find("map1");
      map1.put("1", "test");
      map1.put("2", "test");

      Map<String, String> map2 = new HashMap<String, String>();
      cache_.attach("map2", map2);
      map2 = (Map<String, String>) cache_.find("map2");
      map2.put("1", "me");
      map2.put("2", "me");

      assertFalse("Map should not be the same ", map1.equals(map2));
   }

   @SuppressWarnings("unchecked")
   public void testAttachAndDetach() throws Exception
   {
      Map<Object, String> map = new HashMap<Object, String>();
      map.put("1", "English");
      map.put("2", "French");
      map.put("3", "Taiwanese");

      cache_.attach("/test", map); // attach
      map = (Map<Object, String>) cache_.find("/test");
      assertEquals("Size ", 3, map.size());

      map = (Map<Object, String>) cache_.detach("/test");  // detach
      assertEquals("Size ", 3, map.size());

      System.out.println("**** End of cache content **** ");
      map.remove("2");
      map.put(2, "Hoklo");
      assertEquals("Size ", 3, map.size());
      assertEquals("Content ", "Hoklo", map.get(2));

      // Try to re-attach
      cache_.attach("/test", map);
      map.remove("3");
      assertEquals("Size ", 2, map.size());
   }

   @SuppressWarnings("unchecked")
   public void testPojoAttachAndDetach() throws Exception
   {
      Address add1 = new Address();
      add1.setCity("San Jose");
      add1.setZip(95123);

      Address add2 = new Address();
      add2.setCity("Sunnyvale");
      add2.setZip(94086);

      Address add3 = new Address();
      add3.setCity("Santa Clara");
      add3.setZip(951131);

      Map<String, Address> map = new HashMap<String, Address>();
      map.put("1", add1);
      map.put("2", add2);
      map.put("3", add3);

      cache_.attach("/test", map); // attach
      map = (Map<String, Address>) cache_.find("/test");
      assertEquals("Size ", 3, map.size());

      map = (Map<String, Address>) cache_.detach("/test");
      assertEquals("Size ", 3, map.size());

      System.out.println("**** End of cache content **** ");
      map.remove("2");
      map.put("2", add2);
      assertEquals("Size ", 3, map.size());
      assertEquals("Content ", add2, map.get("2"));

      // Try to re-attach
      cache_.attach("/test", map);
      map.remove("2");
      assertEquals("Size ", 2, map.size());
   }

   public void testKeyAsObject() throws Exception
   {
      SerializableKeyValue key = new SerializableKeyValue(42, "Novell");
      Address add1 = new Address();
      add1.setCity("Waltham");
      add1.setStreet("404 Wyman Street");
      add1.setZip(02451);

      Map<SerializableKeyValue, Address> map = new HashMap<SerializableKeyValue, Address>();
      map.put(key, add1);
      cache_.attach("/keytest", map);
      @SuppressWarnings("unchecked")
      Map<SerializableKeyValue, Address> ref = (Map<SerializableKeyValue, Address>) cache_.find("/keytest");

      Iterator<SerializableKeyValue> iter = ref.keySet().iterator();
      assertTrue(iter.hasNext());
      Object keyFromMap = iter.next();
      assertEquals("original key is same as key in pojocache map, key class=" +
                   key.getClass().getName() + ", keyFromMap class=" + keyFromMap.getClass().getName(),
                   key, keyFromMap);
      assertEquals("local map is equal to pojocache map", map, ref);
   }

   public void testNonSerializableKeyAsObject() throws Exception
   {
      // negative test as we should get a java.io.NotSerializableException
      NotSerializableKeyValue key = new NotSerializableKeyValue(42, "Novell");
      Address add1 = new Address();
      add1.setCity("Waltham");
      add1.setStreet("404 Wyman Street");
      add1.setZip(02451);

      Map<NotSerializableKeyValue, Address> map = new HashMap<NotSerializableKeyValue, Address>();
      map.put(key, add1);
      try
      {
         cache_.attach("/keytest", map);  // this should throw RuntimeException with cause of java.io.NotSerializableException
         fail("failed to get expected runtimeException when adding nonserializable key to pojocache map");
      }
      catch (PojoCacheException e)
      {
         Throwable t = e;

         assertTrue("we got expected PojoCacheException "
                    + t.getCause().getClass().getName(), t.getCause() instanceof PojoCacheException);
      }
      map.clear();
      cache_.attach("/keytest", map);  // this should succeed
      @SuppressWarnings("unchecked")
      Map<NotSerializableKeyValue, Address> ref = (Map<NotSerializableKeyValue, Address>) cache_.find("/keytest");

      // try adding nonserializable key to pojocache based map
      try
      {
         ref.put(key, add1);  // this should throw RuntimeException with cause of java.io.NotSerializableException
         fail("failed to get expected runtimeException when putting nonserializable key to pojocache map");
      }
      catch (RuntimeException e)
      {
         Throwable t = e;

         assertTrue("we got expected PojoCacheException "
                    + t.getClass().getName(), t instanceof PojoCacheException);
      }
   }



   static class SerializableKeyValue implements Serializable
   {
      private static final long serialVersionUID = 1L;
      Integer ivalue;
      String svalue;

      SerializableKeyValue(Integer ivalue, String svalue)
      {
         this.ivalue = ivalue;
         this.svalue = svalue;
      }

      public int hashCode()
      {
         return ivalue.hashCode() + svalue.hashCode();
      }

      public boolean equals(Object o)
      {
         if (o == this)
         {
            return true;
         }
         if (!(o instanceof SerializableKeyValue))
         {
            System.out.println("equals: not instanceof SerializableKeyValue , it is a " + o.getClass().getName());
            return false;
         }
         SerializableKeyValue val = (SerializableKeyValue) o;
         return val.ivalue == this.ivalue && val.svalue == this.svalue;
      }
   }

   static class NotSerializableKeyValue
   {
      Integer ivalue;
      String svalue;

      NotSerializableKeyValue(Integer ivalue, String svalue)
      {
         this.ivalue = ivalue;
         this.svalue = svalue;
      }

      public int hashCode()
      {
         return ivalue.hashCode() + svalue.hashCode();
      }

      public boolean equals(Object o)
      {
         if (o == this)
         {
            return true;
         }
         if (!(o instanceof SerializableKeyValue))
         {
            System.out.println("equals: not instanceof SerializableKeyValue , it is a " + o.getClass().getName());
            return false;
         }
         SerializableKeyValue val = (SerializableKeyValue) o;
         return val.ivalue == this.ivalue && val.svalue == this.svalue;
      }
   }

}

