package org.jboss.cache.pojo.collection;

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

import java.util.Collection;
import java.util.HashMap;
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.Fqn;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
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.jboss.cache.pojo.test.Person;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * Test replicated Map
 *
 * @author Ben Wang
 * @author Scott Marlow
 */

@Test(groups = {"functional"})
public class ReplicatedSyncMapTest 
{
   Log log = LogFactory.getLog(ReplicatedSyncMapTest.class);
   PojoCache cache1;
   PojoCache cache2;


   @BeforeMethod(alwaysRun = true)
   protected void setUp() throws Exception
   {
      log.info("setUp() ....");
      cache1 = createCache("CacheGroup");
      cache2 = createCache("CacheGroup");
   }

   @AfterMethod(alwaysRun = true)
   protected void tearDown() throws Exception
   {
      cache1.getCache().removeNode(Fqn.fromString("/"));
      cache1.stop();
      cache2.stop();
   }

   private PojoCache createCache(String name) throws Exception
   {
      boolean toStart = false;
      PojoCache cache = PojoCacheFactory.createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC), toStart);
      cache.start();
      return cache;
   }

//   public void testDummy() {}


   protected Person createPerson(String name, int age)
   {
      Person p = new Person();
      p.setName(name);
      p.setAge(age);
      return p;
   }


   /**
    * Test attachment and then detachment and attachment.
    *
    * @throws Exception
    */
   @SuppressWarnings("unchecked")
   public void testAttachDetach() throws Exception
   {
      log.info("testAttachDetach() ....");
      Map<String, Address> map1 = new HashMap<String, Address>();
      Address addr = new Address();
      addr.setCity("San Jose");
      addr.setZip(95123);
      map1.put("key1", addr);

      Address addr2 = new Address();
      addr2.setCity("Santa Clara");
      addr2.setZip(95131);

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

      // Pure list
      cache1.attach("/map", map1);
      map1 = (Map<String, Address>) cache1.find("/map");
      map1.put("key2", addr2);
      map1 = (Map<String, Address>) cache1.detach("/map");
      assertEquals("Detached map should still be", 2, map1.size());
      map1.put("key3", addr3);
      cache1.attach("/map", map1);

      Map map2 = (Map) cache2.find("/map");
      assertEquals("Map size should be ", 3, map2.size());
   }

   @SuppressWarnings("unchecked")
   public void testRelationshipWithSharedMap1() throws Exception
   {
      log.info("testRelationshipWithMap() ....");
      Map<String, Address> map1 = new HashMap<String, Address>();
      Address addr = new Address();
      addr.setCity("San Jose");
      addr.setZip(95123);
      map1.put("key1", addr);

      // Pure set
      cache1.attach("/map", map1);
      // We specifically need to use Proxy otherwise it won't work with multiple references
      map1 = (Map<String, Address>) cache1.find("/map");
      cache1.attach("/alias", map1);

      Map map2 = (Map) cache1.find("/alias");
      Address add1 = (Address) ((Map.Entry) map2.entrySet().iterator().next()).getValue();
      assertNotNull("Address should not be null", add1);
      assertEquals("Zip ", 95123, add1.getZip());

      map1 = (Map<String, Address>) cache2.find("/map");
      map2 = (Map) cache2.find("/alias");
      assertTrue("Map size should not be 0 ", (map2.size() != 0));
      assertEquals("Both maps should be equal ", map1, map2);
      add1 = (Address) ((Map.Entry) map2.entrySet().iterator().next()).getValue();
      assertNotNull("Address should not be null", add1);
      assertEquals("Zip ", 95123, add1.getZip());
   }

   @SuppressWarnings("unchecked")
   public void testNullWithSharedMap1() throws Exception
   {
      log.info("testNullWithSharedMap1() ....");
      Map<String, String> map1 = new HashMap<String, String>();
      map1.put("null value test", null);
      map1.put(null, "null key test");

      // Pure set
      cache1.attach("/map", map1);
      // We specifically need to use Proxy otherwise it won't work with multiple references
      map1 = (Map<String, String>) cache1.find("/map");
      cache1.attach("/alias", map1);

      Map map2 = (Map) cache1.find("/alias");

      map1 = (Map<String, String>) cache2.find("/map");
      map2 = (Map) cache2.find("/alias");
      assertTrue("Map size should not be 0 ", (map2.size() != 0));
      assertEquals("Both maps should be equal ", map1, map2);

      assertTrue("Get null key returns non-null value", map2.get(null) != null);
      assertTrue("Get null key returns correct value", map2.get(null).equals("null key test"));

      assertTrue("Get null value returns null value", map2.get("null value test") == null);
      assertTrue("containsKey test for null value", map2.containsKey("null value test"));
      assertTrue("containsKey test for null key", map2.containsKey(null));
      assertTrue("containsValue test for null value", map2.containsValue(null));

      assertTrue("values (positive) null test", map2.values().contains(null));
      Collection<String> walk = map1.values();

      assertTrue(walk.remove(null));
      assertFalse("values (negative) null test", map2.values().contains(null));

      assertTrue("values (positive) null test", map2.values().contains("null key test"));
      assertTrue(walk.remove("null key test"));
      assertFalse("values (negative) null test", map2.values().contains("null key test"));

      map1.put("null value test", null);
      map1.put(null, "null key test");


      assertTrue("remove null item test", map1.remove(null).equals("null key test"));
      assertFalse("null item removed", map2.containsKey(null));


   }

   @SuppressWarnings("unchecked")
   public void testRelationshipWithSharedMap2() throws Exception
   {
      log.info("testRelationshipWithMap2() ....");
      Map<String, Address> map1 = new HashMap<String, Address>();
      Address addr = new Address();
      addr.setCity("San Jose");
      addr.setZip(95123);
      map1.put("key1", addr);

      Map<String, Address> map2 = new HashMap<String, Address>();
      map2.put("key2", addr);

      cache2.attach("/map1", map1);
      cache2.attach("/map2", map2);

      map1 = (Map<String, Address>) cache2.find("/map1");
      map2 = (Map<String, Address>) cache2.find("/map2");
      assertTrue("Map size should not be 0 ", (map2.size() != 0));
      assertEquals("Both maps should be equal ", map1.get("key1"), map2.get("key2"));
      Address add1 = (Address) ((Map.Entry) map2.entrySet().iterator().next()).getValue();
      assertNotNull("Address should not be null", add1);
      assertEquals("Zip ", 95123, add1.getZip());
   }

   @SuppressWarnings("unchecked")
   public void testKeySetRemoveWithSharedMap1() throws Exception
   {
      log.info("testKeySetRemoveWithSharedMap1() ....");
      Map<String, Object> map1 = new HashMap<String, Object>();
      Address addr = new Address();
      addr.setCity("San Jose");
      addr.setZip(95123);
      map1.put("key1_map1", addr);
      map1.put("key2_map1", null);
      map1.put(null, "null value test");
      Map<String, Object> map2 = new HashMap<String, Object>();
      map2.put("key1_map2", addr);
      map2.put("key2_map2", "round");

      assertTrue("key1 exists", map1.containsKey("key1_map1"));


      cache2.attach("/map1", map1);
      cache2.attach("/map2", map2);

      map1 = (Map<String, Object>) cache1.find("/map1");
      map2 = (Map<String, Object>) cache1.find("/map2");

      assertTrue("key1 exists", map1.containsKey("key1_map1"));
      assertTrue("null key exists", map1.containsKey(null));
      assertTrue("key2 exists", map1.containsKey("key2_map1"));

      Set<String> set = map1.keySet();
      Iterator<String> iter = set.iterator();
      while (iter.hasNext())
      {
         Object o = iter.next();
         System.out.println("testKeySetRemoveWithSharedMap1 iter.next returned: " + o);

         if (o == null || "key1_map1".equals(o))
            iter.remove();
      }
      assertTrue("key2 exists", map1.containsKey("key2_map1"));
      assertFalse("key1 doesn't exist", map1.containsKey("key1_map1"));
      assertFalse("null key doesn't exist", map1.containsKey(null));

      map1 = (Map<String, Object>) cache2.find("/map1");
      map2 = (Map<String, Object>) cache2.find("/map2");

      assertTrue("key2 exists", map1.containsKey("key2_map1"));
      assertFalse("key1 doesn't exist", map1.containsKey("key1_map1"));
      assertFalse("null key doesn't exist", map1.containsKey(null));

   }

   @SuppressWarnings("unchecked")
   public void testRecursion1() throws Exception
   {
      Map<String, Object> map = new HashMap<String, Object>();
      map.put("1", "1");
      map.put("2", "2");
      cache1.attach("map", map);

      map = (Map<String, Object>) cache1.find("map");
      map.put("map", map);

      assertEquals("size ", 3, map.size());
      Map m2 = (Map) map.get("map");
      assertTrue("Instance of AopProxy", m2 instanceof ClassProxy);
//      assertEquals("ClassProxy instance", list, l2);
   }

   @Test(enabled = false)
   public void testRecursion2() throws Exception
   {
      Map<String, Object> map = new HashMap<String, Object>();
      map.put("1", "1");
      map.put("2", "2");
      map.put("map", map);

      try
      {
         cache1.attach("map", map);
      }
      catch (PojoCacheException pex)
      {
         // Expected, we can't do recursive map.
         pex.printStackTrace();
      }

      fail("Test should fail since we can't handle recursive map");
   }



}

