package org.jboss.cache.aop;

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.PropertyConfigurator;
import org.jboss.cache.aop.test.Address;
import org.jboss.cache.aop.test.Link;
import org.jboss.cache.aop.test.NodeManager;
import org.jboss.cache.aop.test.Person;
import org.jboss.cache.misc.TestingUtil;

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

/**
 * Test object graph handling in aop, e.g., circular reference, multiple reference, link, etc.
 * @author Ben Wang  
 */

public class ReplicatedObjectGraphAopTest extends TestCase
{
   Log log=LogFactory.getLog(ReplicatedObjectGraphAopTest.class);
   PojoCache cache1;
   PojoCache cache2;

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

   protected void setUp() throws Exception
   {
      super.setUp();
      log.info("setUp() ....");
      cache1 = createCache("CacheGroup");
      cache2 = createCache("CacheGroup");
   }

   protected void tearDown() throws Exception
   {
      super.tearDown();
      cache1.remove("/");
      cache1.stop();
      cache2.stop();
   }

   private PojoCache createCache(String name) throws Exception {
      PojoCache tree=new PojoCache();
      PropertyConfigurator config=new PropertyConfigurator();
      config.configure(tree, "META-INF/replSync-service.xml"); // read in generic replAsync xml
      tree.setClusterName(name);
      tree.createService();
      tree.startService();
      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.putObject("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache1.getObject("/person/joe");
      cache1.putObject("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache1.getObject("/person/ben");

      Address addr = new Address();
      addr.setStreet("123 Albert Ave.");
      addr.setCity("Sunnyvale");
      addr.setZip(94087);
      cache1.putObject("/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.putObject("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache1.getObject("/person/joe");
      cache1.putObject("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache1.getObject("/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.removeObject("/person/joe");
      Person ben = (Person) cache.getObject("/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 XtestCheckReplInstance() throws Exception
   {
      log.info("testCheckReplInstance() ...");
      stage0();
      TestingUtil.sleepThread(100);
      Person joe = (Person)cache1.getObject("/person/joe");
      Person ben = (Person)cache1.getObject("/person/ben");
      assertEquals("Ben and Joe's address should be the same ", joe.getAddress().getCity(),
            ben.getAddress().getCity());

      Address joe1 = (Address)cache2.getObject("/address");
      assertEquals("Ben's address should not be changed ", joe.getAddress().getCity(), joe1.getCity());
      cache1.removeObject("/person/ben");
      Address joe2 = (Address)cache2.getObject("/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.getObject("/person/joe");
      Person ben = (Person)cache1.getObject("/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.removeObject("/person/ben");
   }

   public void testCircularReference1() throws Exception
   {
//        try {Thread.sleep(10000); } catch (Exception e) {};
      log.info("testCircularReference1() ...");
      Link parent = new Link("parent");
      Link child = new Link("child");
      parent.setLink(child);
      child.setLink(parent);
      cache1.putObject("/link/parent", parent);
      TestingUtil.sleepThread(100);
      assertEquals("parent", ((Link) cache1.getObject("/link/parent")).getName());
      assertEquals("child", ((Link) cache1.getObject("/link/parent")).getLink().getName());
      assertEquals("parent", ((Link) cache2.getObject("/link/parent")).getName());
      assertEquals("child", ((Link) cache2.getObject("/link/parent")).getLink().getName());
      ((Link) cache2.getObject("/link/parent")).setLink(null);
      assertNull("Child should be null", ((Link) cache2.getObject("/link/parent")).getLink());
      Link link = (Link)cache1.removeObject("/link/parent");
      assertNotNull("Link should not be null ", link);
      System.out.println("Link: " +link);
   }

   public void testCircularReference2() throws Exception
   {
//        try {Thread.sleep(10000); } catch (Exception e) {};
      log.info("testCircularReference2() ...");
      Link parent = new Link("parent");
      Link child = new Link("child");
      cache1.putObject("/link/parent", parent);
      parent.setLink(child);
      child.setLink(parent);
      assertEquals("parent", ((Link) cache1.getObject("/link/parent")).getName());
      assertEquals("child", ((Link) cache1.getObject("/link/parent")).getLink().getName());
      assertEquals("parent", ((Link) cache2.getObject("/link/parent")).getName());
      assertEquals("child", ((Link) cache2.getObject("/link/parent")).getLink().getName());
      ((Link) cache2.getObject("/link/parent")).setLink(null);
      assertNull("Child should be null", ((Link) cache2.getObject("/link/parent")).getLink());
      Link link = (Link)cache1.removeObject("/link/parent");
      assertNotNull("Link should not be null ", link);
   }

   public void testCircularReference3() throws Exception
   {
//        try {Thread.sleep(10000); } catch (Exception e) {};
      log.info("testCircularReference3() ...");
      Link parent = new Link("parent");
      Link child = new Link("child");
      cache1.putObject("/link/parent", parent);
      cache1.putObject("/link/child", child);
      TestingUtil.sleepThread(100);
      parent.setLink(child);
      child.setLink(parent);

      Link p1 = (Link) cache1.getObject("/link/parent");
      Link c1 = (Link) cache1.getObject("/link/child");
      assertEquals("parent", p1.getName());
      assertEquals("child", p1.getLink().getName());
      assertEquals("child", c1.getName());
      assertEquals("parent", c1.getLink().getName());

      Link p2 = (Link) cache1.getObject("/link/parent");
      Link c2 = (Link) cache1.getObject("/link/child");

      assertEquals("parent", p2.getName());
      assertEquals("child", p2.getLink().getName());
      assertEquals("child", c2.getName());
      assertEquals("parent", c2.getLink().getName());

      p2.setLink(null);
      assertNull("Child should be null", p2.getLink());
      Link link = (Link)cache1.removeObject("/link/parent");
      assertNotNull("Link should not be null ", link);
   }

   /**
    * Setting the circular relationship and also as a shared object.
    * @throws Exception
    */
   public void testCircularReference4() throws Exception
   {
//        try {Thread.sleep(10000); } catch (Exception e) {};
      log.info("testCircularReference3() ...");
      Link parent = new Link("parent");
      Link child = new Link("child");
      parent.setLink(child);
      child.setLink(parent);

      List list = new ArrayList();
      list.add(parent);

      cache1.putObject("/list", list);
      cache1.putObject("/alias", list);

      TestingUtil.sleepThread(100);
      List list1 = (List)cache2.getObject("/list");
      List list2 = (List)cache2.getObject("/alias");

      assertEquals("parent", ((Link) list1.get(0)).getName());
      assertEquals("child", ((Link) list2.get(0)).getLink().getName());
   }

   public void testCircularAndSharedReferences() throws Exception
   {
      log.info("testCircularAndSharedReferences() ...");
      NodeManager pm_ = new NodeManager();

      pm_.setRootNode("root");
      pm_.addNode("root", "kanto");
      pm_.addNode("root.kanto", "tokyo");
      pm_.addNode("root.kanto", "kanagawa");

      cache1.putObject("/propagation", pm_);
      assertEquals("kanagawa", pm_.findNode("root.kanto.kanagawa").getNodeRDN());
      pm_.addNode("root.kanto.tokyo", "hadanshita");
      assertEquals("hadanshita", pm_.findNode("root.kanto.tokyo.hadanshita").getNodeRDN());

      NodeManager pm2_ = (NodeManager)cache2.getObject("/propagation");
      assertEquals("kanagawa", pm2_.findNode("root.kanto.kanagawa").getNodeRDN());
      assertEquals("hadanshita", pm2_.findNode("root.kanto.tokyo.hadanshita").getNodeRDN());

/*
      System.out.println("\n\n");
      System.out.println("---------------------------------------------");
      System.out.println("Initial pm state");
      System.out.println("---------------------------------------------");
      pm_.printNodes();

      System.out.println("\n\n");
      System.out.println("---------------------------------------------");
      System.out.println("Initial cache content");
      System.out.println(cache_.printDetails());
      System.out.println("---------------------------------------------");
*/
   }

   public void testRemoveObject1() throws Exception
   {
      log.info("testRemoveObject1() ...");
      cache1.putObject("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache1.getObject("/person/joe");
      cache1.putObject("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache1.getObject("/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 = (Address) ((Person)cache2.getObject("/person/joe")).getAddress();
      Address add2 = (Address) ((Person)cache2.getObject("/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.removeObject("/person/joe");
      add2 = (Address) ((Person)cache2.getObject("/person/ben")).getAddress();
      System.out.println("*** Cache content *** " +cache2.printDetails());

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

   public void testRemoveObject2() throws Exception
   {
      log.info("testRemoveObject2() ...");
      cache1.putObject("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache1.getObject("/person/joe");
      cache1.putObject("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache1.getObject("/person/ben");
      cache1.putObject("/person/john", createPerson("John Daly", 41));
      Person john = (Person) cache1.getObject("/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 = (Address) ((Person)cache2.getObject("/person/joe")).getAddress();
      Address add2 = (Address) ((Person)cache2.getObject("/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 = (Address) ((Person)cache2.getObject("/person/joe")).getAddress();
      assertEquals("City ", "San Jose", add2.getCity());
      add2 = (Address) ((Person)cache2.getObject("/person/ben")).getAddress();
      assertEquals("City ", "Santa Clara", add2.getCity());
      add2 = (Address) ((Person)cache2.getObject("/person/john")).getAddress();
      assertEquals("City ", "Santa Clara", add2.getCity());
   }

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

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

}

