package org.jboss.cache.pojo.collection;

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

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

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.PojoCacheAlreadyDetachedException;
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 object graph handling in aop, e.g., circular reference, multiple reference, link, etc.
 *
 * @author Ben Wang
 */

@Test(groups = {"functional"})
public class ReplicatedSyncListTest 
{
   Log log = LogFactory.getLog(ReplicatedSyncListTest.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() ....");
      List<Address> list1 = new ArrayList<Address>();
      Address addr = new Address();
      addr.setCity("San Jose");
      addr.setZip(95123);
      list1.add(addr);

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

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

      // Pure list
      cache1.attach("/list", list1);
      list1 = (List<Address>) cache1.find("/list");
      list1.add(addr2);
      // The return value is the original reference.
      list1 = (List<Address>) cache1.detach("/list");
      assertEquals("Detached list should still be", 2, list1.size());
      list1.add(addr3);
      cache1.attach("/list", list1);

      List list2 = (List) cache2.find("/list");
      assertTrue("List size should not be 0 ", (list2.size() != 0));
      assertEquals("Both list values should be equal ", list1.get(0).getZip(),
                   ((Address) list2.get(0)).getZip());
   }

   @SuppressWarnings("unchecked")
   public void testRemoteDetach() throws Exception
   {
      List<Object> list = new ArrayList<Object>();
      Address addr = new Address();
      addr.setCity("San Jose");
      addr.setZip(95123);
      list.add(addr);

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

      cache1.attach("/list", list);
      list = (List<Object>) cache2.find("/list");
      cache1.detach("/list");

      boolean pass = false;
      try
      {
         list.add(addr);
      }
      catch (PojoCacheAlreadyDetachedException e)
      {
         pass = true;
      }

      assertTrue("Expected PojoCacheAlreadyDetachedExcpetion!", pass);
   }

   /**
    * Two different keys share same list.
    *
    * @throws Exception
    */
   @SuppressWarnings("unchecked")
   public void testRelationshipWithSharedList1() throws Exception
   {
      log.info("testRelationshipWithList() ....");
      List<Address> list1 = new ArrayList<Address>();
      Address addr = new Address();
      addr.setCity("San Jose");
      addr.setZip(95123);
      list1.add(addr);

      // Pure list
      cache1.attach("/list", list1);
      // We specifically need to use Proxy otherwise it won't work with multiple references
      list1 = (List<Address>) cache1.find("/list");
      cache1.attach("/alias", list1);

      List list2 = (List) cache1.find("/alias");
      Address add1 = (Address) list2.get(0);
      assertNotNull("Address should not be null", add1);
      assertEquals("Zip ", 95123, add1.getZip());

      list1 = (List<Address>) cache2.find("/list");
      list2 = (List) cache2.find("/alias");
      assertTrue("List size should not be 0 ", (list2.size() != 0));
      assertEquals("Both lists should be equal ", list1, list2);
      assertEquals("Both list values should be equal ", list1.get(0), list2.get(0));
   }

   /**
    * Shared object between two list item.
    *
    * @throws Exception
    */
   public void testRelationshipWithSharedList2() throws Exception
   {
      log.info("testRelationshipWithList2() ....");
      // 2 lists with shared objects
      List<Address> list1 = new ArrayList<Address>();
      Address addr = new Address();
      addr.setCity("San Jose");
      addr.setZip(95123);
      list1.add(addr);

      List<Address> list2 = new ArrayList<Address>();
      list2.add(addr);

      cache1.attach("/list1", list1);
      cache1.attach("/list2", list2);
      @SuppressWarnings("unchecked")
      Address add2 = ((List<Address>) cache2.find("/list2")).get(0);
      @SuppressWarnings("unchecked")
      Address add1 = ((List<Address>) cache2.find("/list1")).get(0);
      assertEquals("Address should be the same", add1, add2);
      assertEquals("Both shared object should be equal ", add2.getZip(), add1.getZip());
   }

   /**
    * Shared object between regular POJO and List item.
    *
    * @throws Exception
    */
   public void testRelationshipWithSharedList3() throws Exception
   {
      log.info("testRelationshipWithList3() ....");
      // 2 lists with shared objects
      List<Address> list1 = new ArrayList<Address>();
      Address addr = new Address();
      addr.setCity("San Jose");
      addr.setZip(95123);
      list1.add(addr);

      List<Address> list2 = new ArrayList<Address>();
      list2.add(addr);

      cache1.attach("/address", addr);
      cache1.attach("/list1", list1);
      @SuppressWarnings("unchecked")
      Address add1 = ((List<Address>) cache2.find("/list1")).get(0);
      Address add2 = (Address) cache2.find("/address");
      assertEquals("Address should be the same", add1, add2);
      assertEquals("Both shared object should be equal ", 95123, add1.getZip());
   }

   @SuppressWarnings("unchecked")
   public void testNullWithSharedList1() throws Exception
   {
      log.info("testNullWithSharedList1() ....");
      List<String> list1 = new ArrayList<String>();
      list1.add("element 0");
      list1.add(null);  // element 1
      list1.add("element 2");
      list1.add(null);  // element 3

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

      List<String> list2 = (List) cache1.find("/alias");

      list1 = (List<String>) cache2.find("/list");
      list2 = (List<String>) cache2.find("/alias");
      assertTrue("List size should not be 0 ", (list2.size() != 0));
      assertEquals("Both listss should be equal ", list1, list2);

      Object a1[] = list1.toArray();
      Object a2[] = list2.toArray();
      assertTrue("element 1 is null", (a1[1] == null));
      assertTrue("element 1 is null", (a2[1] == null));
      assertTrue("element 3 is null", (a1[3] == null));
      assertTrue("element 3 is null", (a2[3] == null));

      assertTrue("contains test for null value", list1.contains(null));
      assertTrue("contains test for null value", list2.contains(null));

      assertTrue("index of null ", list2.indexOf(null) == 1);
      assertTrue("last index of null ", list2.lastIndexOf(null) == 3);

      list1.set(0, null);   // set first element to null
      assertTrue("set first item to null", list2.get(0) == null);
      list1.set(0, "element 0");
      assertTrue("set first item to 'element 0'", list2.get(0).equals("element 0"));


      ListIterator<String> listIter = list1.listIterator();
      assertTrue("listiter has next", listIter.hasNext());
      assertTrue("listiter 1st element is 'element 0'", listIter.next().equals("element 0"));
      assertTrue("listiter has next", listIter.hasNext());
      assertTrue("listiter 2nd element is null", listIter.next() == null);
      listIter.remove();

      assertTrue("2nd element should be 'element 2'", list2.get(1).equals("element 2"));

   }

   @SuppressWarnings("unchecked")
   public void testRecursion1() throws Exception
   {
      List<Object> list = new ArrayList<Object>();
      list.add("1");
      list.add("2");
      cache1.attach("list", list);

      list = (List<Object>) cache1.find("list");
      list.add(list);

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

   @SuppressWarnings("unchecked")
   public void testRecursion2() throws Exception
   {
      List<Object> list = new ArrayList<Object>();
      list.add("1");
      list.add("2");
      list.add(list);

      cache1.attach("list", list);

      list = (List<Object>) cache1.find("list");
      list.toString();
   }



}

