package org.jboss.cache.pojo;

import static org.testng.AssertJUnit.assertEquals;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import org.jboss.cache.misc.TestingUtil;
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 ReplicatedObjectGraphTest 
{
   Log log = LogFactory.getLog(ReplicatedObjectGraphTest.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 tree = PojoCacheFactory.createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC), toStart);
      tree.start();
      return tree;
   }

   //   public void testDummy() {}

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

   private void stage0() throws Exception
   {
      cache1.attach("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache1.find("/person/joe");
      cache1.attach("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache1.find("/person/ben");

      Address addr = new Address();
      addr.setStreet("123 Albert Ave.");
      addr.setCity("Sunnyvale");
      addr.setZip(94087);
      cache1.attach("/address", addr);

      // They share the sub-object: address
      joe.setAddress(addr);
      ben.setAddress(addr);
      assertEquals("Joe's address should still be valid ", "Sunnyvale", joe.getAddress().getCity());
      assertEquals("Ben's address should still be valid ", "Sunnyvale", ben.getAddress().getCity());
   }

   private void stage1() throws Exception
   {
      cache1.attach("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache1.find("/person/joe");
      cache1.attach("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache1.find("/person/ben");

      Address addr = new Address();
      addr.setStreet("123 Albert Ave.");
      addr.setCity("Sunnyvale");
      addr.setZip(94087);

      // They share the sub-object: address
      joe.setAddress(addr);
      ben.setAddress(addr);
      assertEquals("Joe's address should still be valid ", "Sunnyvale", joe.getAddress().getCity());
      assertEquals("Ben's address should still be valid ", "Sunnyvale", ben.getAddress().getCity());
   }

   private void stage2(PojoCache cache) throws Exception
   {
      //
      cache.detach("/person/joe");
      Person ben = (Person) cache.find("/person/ben");
      assertEquals("Ben's address should still be valid ", "Sunnyvale", ben.getAddress().getCity());
      Address addr = ben.getAddress();
      addr.setCity("Santa Clara");
      assertEquals("Ben's address should be changed ", "Santa Clara", ben.getAddress().getCity());
   }

   /**
    * Test whether repeated update on the ref count will change the replicated aop instances
    *
    * @throws Exception
    */
   public void testCheckReplInstance() throws Exception
   {
      log.info("testCheckReplInstance() ...");
      stage0();
      TestingUtil.sleepThread(100);
      Person joe = (Person) cache1.find("/person/joe");
      Person ben = (Person) cache1.find("/person/ben");
      assertEquals("Ben and Joe's address should be the same ", joe.getAddress().getCity(),
              ben.getAddress().getCity());

      Address joe1 = (Address) cache2.find("/address");
      assertEquals("Ben's address should not be changed ", joe.getAddress().getCity(), joe1.getCity());
      ben = (Person) cache2.find("/person/ben");
      cache2.detach("/person/ben");
      Address joe2 = (Address) cache2.find("/address");
      assertEquals("Joe's reference should be the same.", joe1, joe2);
   }

   public void testRefCountCheckRepl() throws Exception
   {
      log.info("testRefCountCheckRepl() ...");
      stage1();
      TestingUtil.sleepThread(100);
      Person joe = (Person) cache1.find("/person/joe");
      Person ben = (Person) cache1.find("/person/ben");
      assertEquals("Ben and Joe's address should be the same ", joe.getAddress().getCity(),
              ben.getAddress().getCity());
      TestingUtil.sleepThread(100);
      stage2(cache2);
      assertEquals("Ben's address should be changed on cache1 as well ", "Santa Clara", ben.getAddress().getCity());
      cache2.detach("/person/ben");
   }


   public void testdetach1() throws Exception
   {
      log.info("testdetach1() ...");
      cache1.attach("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache1.find("/person/joe");
      cache1.attach("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache1.find("/person/ben");

      Address addr = new Address();
      addr.setStreet("123 Albert Ave.");
      addr.setCity("Sunnyvale");
      addr.setZip(94087);

      // They share the sub-object: address
      log.info("testMultipleReference(): set Joe address");
      joe.setAddress(addr);
      log.info("testMultipleReference(): set Ben address");
      ben.setAddress(addr);

      Address add1 = ((Person) cache2.find("/person/joe")).getAddress();
      Address add2 = ((Person) cache2.find("/person/ben")).getAddress();
      assertEquals(add1.getCity(), add2.getCity());
      addr.setCity("Santa Clara");
      assertEquals(add1.getCity(), add2.getCity());

      // Remove pojo joe will relocate the address field to ben's
      cache2.detach("/person/joe");
      add2 = ((Person) cache2.find("/person/ben")).getAddress();

      assertEquals("City ", "Santa Clara", add2.getCity());
   }

   public void testdetach2() throws Exception
   {
      log.info("testdetach2() ...");
      cache1.attach("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache1.find("/person/joe");
      cache1.attach("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache1.find("/person/ben");
      cache1.attach("/person/john", createPerson("John Daly", 41));
      Person john = (Person) cache1.find("/person/john");

      Address addr = new Address();
      addr.setStreet("123 Albert Ave.");
      addr.setCity("Sunnyvale");
      addr.setZip(94087);

      Address addr1 = new Address();
      addr1.setStreet("123 Albert Ave.");
      addr1.setCity("San Jose");
      addr1.setZip(94087);

      // They share the sub-object: address
      log.info("testMultipleReference(): set Joe address");
      joe.setAddress(addr);
      log.info("testMultipleReference(): set Ben address");
      ben.setAddress(addr);
      john.setAddress(addr);

      Address add1 = ((Person) cache2.find("/person/joe")).getAddress();
      Address add2 = ((Person) cache2.find("/person/ben")).getAddress();
      assertEquals(add1.getCity(), add2.getCity());
      addr.setCity("Santa Clara");
      assertEquals(add1.getCity(), add2.getCity());

      // Remove pojo joe will relocate the address field to ben's
      joe.setAddress(addr1);
      add2 = ((Person) cache2.find("/person/joe")).getAddress();
      assertEquals("City ", "San Jose", add2.getCity());
      add2 = ((Person) cache2.find("/person/ben")).getAddress();
      assertEquals("City ", "Santa Clara", add2.getCity());
      add2 = ((Person) cache2.find("/person/john")).getAddress();
      assertEquals("City ", "Santa Clara", add2.getCity());
   }



}

