/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jboss.cache.pojo.passivation;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.Fqn;
import org.jboss.cache.notifications.annotation.CacheListener;
import org.jboss.cache.notifications.annotation.NodeActivated;
import org.jboss.cache.notifications.annotation.NodePassivated;
import org.jboss.cache.notifications.event.NodeEvent;
import org.jboss.cache.pojo.PojoCache;
import org.jboss.cache.pojo.PojoCacheFactory;
import org.jboss.cache.pojo.test.Address;
import org.jboss.cache.pojo.test.Link;
import org.jboss.cache.pojo.test.Person;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * Basic PojoCache test case.
 *
 * @author Ben Wang
 */

@Test(groups = {"functional"})
public class LocalTest
{
   Log log = LogFactory.getLog(org.jboss.cache.pojo.passivation.LocalTest.class);
   PojoCache cache_;
   MyCacheListener listener_;


   @BeforeMethod(alwaysRun = true)
   protected void setUp() throws Exception
   {
      log.info("setUp() ....");
      String configFile = "META-INF/pojocache-passivation-service.xml";
      boolean toStart = false;
      cache_ = PojoCacheFactory.createCache(configFile, toStart);
      cache_.getCache().getConfiguration().setTransactionManagerLookupClass("org.jboss.cache.transaction.BatchModeTransactionManagerLookup");

      listener_ = new MyCacheListener();
      cache_.getCache().addCacheListener(listener_);

      cache_.start();
   }

   @AfterMethod(alwaysRun = true)
   protected void tearDown() throws Exception
   {
      cache_.getCache().removeNode(Fqn.ROOT);
      ((CacheSPI<Object, Object>) cache_.getCache()).getCacheLoaderManager().getCacheLoader().remove(Fqn.ROOT);
      cache_.getCache().removeCacheListener(listener_);
      cache_.stop();
      //Thread.sleep(1000);
   }

   //   public void testDummy() {}

   private Person createPerson(String name, int age)
   {
      Person p = new Person();
      p.setName(name);
      p.setAge(age);
      Address add = new Address();
      add.setZip(95123);
      add.setCity("San Jose");
      p.setAddress(add);
      return p;
   }

   private void sanityCheck()
   {
      if (listener_.getActivationCount() == 0 || listener_.getPassivationCount() == 0)
      {
         fail("Sanity checking for passivation failed. Counters: activation - " + listener_.getActivationCount()
                 + " passivation - " + listener_.getPassivationCount());
      }

      listener_.reset();
   }

   public void testSimple() throws Exception
   {
      log.info("testSimple() ....");
      String id = "person";
      Person joe = createPerson("Joe Black", 20);
      cache_.attach(id, joe);
      Thread.sleep(9100);// default is 3 seconds so joe should have been passivated.

      assertNull("Node should be evicted ", ((CacheSPI<Object, Object>) cache_.getCache()).peek(new Fqn<String>(id), false));

      assertEquals("age ", 20, joe.getAge());
      joe.setAge(30);
      assertEquals("age ", 30, joe.getAge());

      sanityCheck();
   }

   public void testSetAddress() throws Exception
   {
      log.info("testAddress() ....");
      String id = "person";
      Person joe = createPerson("Joe Black", 20);
      cache_.attach(id, joe);
      Thread.sleep(9100);// default is 3 seconds so joe should have been passivated.

      assertNull("Node should be evicted ", ((CacheSPI<Object, Object>) cache_.getCache()).peek(new Fqn<String>(id), false));

      Address addr = new Address();
      addr.setCity("Taipei");
      addr.setZip(106);

      joe.setAddress(addr);

      sanityCheck();
   }

   public void testFindAgain() throws Exception
   {
      log.info("testFindAgain() ....");
      String id = "person";
      Person joe = createPerson("Joe Black", 20);
      cache_.attach(id, joe);
      Thread.sleep(9100);// default is 3 seconds so joe should have been passivated.

      assertNull("Node should be evicted ", ((CacheSPI<Object, Object>) cache_.getCache()).peek(new Fqn<String>(id), false));

      Person p = (Person) cache_.find(id);

      assertEquals("age ", 20, joe.getAge());
      joe.setAge(30);
      assertEquals("age ", 30, joe.getAge());

      assertEquals("age ", p.getAge(), joe.getAge());

      assertFalse("Instance not equal (this is known side effect) ", joe == p);

      sanityCheck();
   }

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

      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);

      log.info("testMultipleReference(): verify");
      Address add1 = (Address) ((Person) cache_.find(id)).getAddress();
      Address add2 = (Address) ((Person) cache_.find(id)).getAddress();
      assertEquals(add1.getCity(), add2.getCity());
      addr.setCity("Santa Clara");
      assertEquals(add1.getCity(), add2.getCity());

      Thread.sleep(9100);// default is 3 seconds so joe should have been passivated.

      assertNull("Node should be evicted ", ((CacheSPI<Object, Object>) cache_.getCache()).peek(new Fqn<String>(id), false));

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

      Thread.sleep(9100);// default is 3 seconds so joe should have been passivated.
      assertEquals("City is ", "Santa Clara", joe.getAddress().getCity());

      cache_.detach(id);
      cache_.detach(id1);
   }

   public void testRemoveObject1() throws Exception
   {
      log.info("testRemoveObject1() ...");
      cache_.attach("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache_.find("/person/joe");
      cache_.attach("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache_.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 = (Address) ((Person) cache_.find("/person/joe")).getAddress();
      Address add2 = (Address) ((Person) cache_.find("/person/ben")).getAddress();
      assertEquals(add1.getCity(), add2.getCity());
      addr.setCity("Santa Clara");
      assertEquals(add1.getCity(), add2.getCity());

      Thread.sleep(9100);
      // Remove pojo joe will relocate the address field to ben's
      cache_.detach("/person/joe");
      add2 = (Address) ((Person) cache_.find("/person/ben")).getAddress();
      assertEquals("City ", "Santa Clara", add2.getCity());
   }

   public void testCircularReference1() throws Exception
   {
      log.info("testCircularReference1() ...");
      Link parent = new Link("parent");
      Link child = new Link("child");
      parent.setLink(child);
      child.setLink(parent);
      cache_.attach("/link/parent", parent);

      Thread.sleep(9100);
      assertEquals("parent", ((Link) cache_.find("/link/parent")).getName());
      assertEquals("child", ((Link) cache_.find("/link/parent")).getLink().getName());
   }





   @CacheListener
   public class MyCacheListener
   {
      int activation = 0;
      int passivation = 0;

      public int getActivationCount()
      {
         return activation;
      }

      public int getPassivationCount()
      {
         return passivation;
      }

      public void reset()
      {
         activation = 0;
         passivation = 0;
      }

      @NodeActivated
      public void nodeActivated(NodeEvent ne)
      {
         Fqn<?> fqn = ne.getFqn();
         if (!ne.isPre())
         {
            System.out.println("nodeActivated: " + fqn);
            activation++;
         }
      }

      @NodePassivated
      public void nodePassivated(NodeEvent ne)
      {
         if (!ne.isPre())
         {
            System.out.println("nodePassivated: " + ne.getFqn());
            passivation++;
         }
      }
   }
}
