/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.cache.pojo.passivation;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.NodeCreated;
import org.jboss.cache.notifications.annotation.NodeEvicted;
import org.jboss.cache.notifications.annotation.NodeModified;
import org.jboss.cache.notifications.annotation.NodePassivated;
import org.jboss.cache.notifications.event.Event;
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;

/**
 * Replicated passivation test.
 *
 * @author Ben Wang
 */

@Test(groups = {"functional"})
public class ReplicatedTest 
{
   Log log = LogFactory.getLog(ReplicatedTest.class);
   PojoCache cache_, cache1_;
   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");

      cache_.start();

      cache1_ = PojoCacheFactory.createCache("META-INF/pojocache-passivation-service2.xml", toStart);
      cache1_.getCache().getConfiguration().setTransactionManagerLookupClass("org.jboss.cache.transaction.BatchModeTransactionManagerLookup");

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

      cache1_.start();
   }

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

//   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() throws InterruptedException
   {
      Thread.sleep(100);
      if (listener_.getActivationCount() == 0 || listener_.getPassivationCount() == 0)
      {
         fail("Sanity checking for passivation failed. Counters: activation - " + listener_.getActivationCount()
                 + " passivation - " + listener_.getPassivationCount());
      }

      listener_.reset();
   }

   public void testFindAfterPassivation() throws Exception
   {
      log.info("testFindAfterPassivation() ....");
      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.

//      System.out.println(" Content: " + ((org.jboss.cache.CacheImpl)cache_.getCache()).printDetails());
// TODO: This will activate the node in 2.0 now.
//      assertFalse("Node should be evicted ",
//              cache_.getCache().getRoot().hasChild(new Fqn(id)));

      Person p = (Person) cache1_.find(id);
      assertNotNull("Person on remote node ", p);

      sanityCheck();
   }

   public void testRemoteSetAfterPassivation() throws Exception
   {
      log.info("testFindAfterPassivation() ....");
      String id = "person";
      Person joe = createPerson("Joe Black", 20);
      cache_.attach(id, joe);

      Person p = (Person) cache1_.find(id);
      assertNotNull("Person on remote node ", p);

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

// TODO: This will activate the node in 2.0 now.
//      assertFalse("Node should be evicted ",
//              cache_.getCache().getRoot().hasChild(new Fqn(id)));

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

      p.setAddress(addr);

      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) cache1_.find(id)).getAddress();
      Address add2 = (Address) ((Person) cache1_.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.
// TODO: This will activate the node in 2.0 now.

//      assertFalse("Node should be evicted ",
//              cache_.getCache().getRoot().hasChild(new Fqn(id)));

      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) cache1_.find("/person/joe")).getAddress();
      Address add2 = (Address) ((Person) cache1_.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
      System.out.println("=== DETACH====");
      cache_.detach("/person/joe");
      Thread.sleep(3000);
      System.out.println("=== FIND === ");
      add2 = (Address) ((Person) cache1_.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) cache1_.find("/link/parent")).getName());
      assertEquals("child", ((Link) cache1_.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;
      }


      @NodeCreated
      @NodeEvicted
      @NodeModified
      public void nodeAccessed(Event e)
      {
         System.out.println("Event: " + e);
      }

      @NodeActivated
      public void nodeActivated(Event e)
      {
         nodeAccessed(e);
         if (!e.isPre())
         {
            activation++;
         }
      }

      @NodePassivated
      public void nodePassivated(Event e)
      {
         nodeAccessed(e);
         if (e.isPre())
         {
            passivation++;
         }
      }
   }
}
