/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.interceptors;

import junit.framework.TestCase;
import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
import org.jboss.cache.TreeCache;
import org.jboss.cache.eviction.DummyEvictionPolicy;
import org.jboss.cache.eviction.EvictedEventNode;
import org.jboss.cache.eviction.LRUConfiguration;
import org.jboss.cache.eviction.Region;
import org.jboss.cache.eviction.RegionManager;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.marshall.MethodCallFactory;
import org.jboss.cache.marshall.MethodDeclarations;
import org.jgroups.blocks.MethodCall;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Daniel Huang (dhuang@jboss.org)
 * @version $Revision: $
 */
public class EvictionInterceptorTest extends TestCase
{
   private static final String fqn1 = "/a/b/c";
   private static final String fqn2 = "/a/b";
   private static final String fqn3 = "/a/b/d";

   private static final String fqn4 = "/d/e/f";

   private TreeCache cache;
   private Interceptor interceptor;
   private RegionManager regionManager;

   public void setUp() throws Exception
   {
      super.setUp();
      regionManager = new RegionManager();
      LRUConfiguration config = new LRUConfiguration();
      regionManager.createRegion("/a/b/c", new DummyEvictionPolicy(), config);
      regionManager.createRegion("/a/b/d", new DummyEvictionPolicy(), config);
      regionManager.createRegion("/a/b", new DummyEvictionPolicy(), config);

      regionManager.createRegion("/d/e/f", new DummyEvictionPolicy(), config);
      regionManager.createRegion("/d/e/g", new DummyEvictionPolicy(), config);
      regionManager.createRegion("/d/e", new DummyEvictionPolicy(), config);

      cache = new TreeCache();
      cache.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
      cache.setIsolationLevel(IsolationLevel.SERIALIZABLE);

/*CacheMgmtInterceptor
TxInterceptor
Unlock
PessimisticLockInterceptor
CallInterceptor
UnlockInterceptor
TxInterceptor
CacheMgmtInterceptor */

      // make the interceptor chain (separate from the TreeCache object.

      interceptor = new CacheMgmtInterceptor();
      interceptor.setCache(cache);

      TxInterceptor ti = new TxInterceptor();
      ti.setCache(cache);
      interceptor.setNext(ti);

      UnlockInterceptor ui = new UnlockInterceptor();
      ui.setCache(cache);
      ti.setNext(ui);

      PessimisticLockInterceptor pli = new PessimisticLockInterceptor();
      pli.setCache(cache);
      ui.setNext(pli);

      EvictionInterceptor ei = new EvictionInterceptor();
      ei.setCache(cache);
      ei.setRegionManager(regionManager);
      pli.setNext(ei);

      CallInterceptor ci = new CallInterceptor();
      ci.setCache(cache);
      ei.setNext(ci);

      cache.setCacheMode("LOCAL");
      cache.startService();
   }

   public void tearDown() throws Exception
   {
      super.tearDown();
      cache.remove("/");
      cache.stopService();
   }

   public void testVisitNode() throws Throwable
   {
      // make sure node that doesn't exist does not result in a node visit event.

      MethodCall mc = MethodCallFactory.create(MethodDeclarations.getNodeMethodLocal,
            new Object[]{Fqn.fromString(fqn1)});
      interceptor.invoke(mc);
      Region regionABC = regionManager.getRegion(fqn1);
      assertNull(regionABC.takeLastEventNode());

      cache.put(fqn1, "key", "value");
      assertEquals("value", cache.get(fqn1, "key"));
      Node node = cache.get(fqn1);
      assertNotNull(node);
      assertEquals("value", node.get("key"));

      cache.put(fqn3, "key", "value");
      assertEquals("value", cache.get(fqn3, "key"));
      node = cache.get(fqn3);
      assertNotNull(node);
      assertEquals("value", node.get("key"));


      mc = MethodCallFactory.create(MethodDeclarations.getNodeMethodLocal,
            new Object[]{Fqn.fromString(fqn1)});
      interceptor.invoke(mc);

      regionABC = regionManager.getRegion(fqn1);
      EvictedEventNode event = regionABC.takeLastEventNode();
      assertEquals(EvictedEventNode.VISIT_NODE_EVENT, event.getEvent());
      assertEquals(fqn1, event.getFqn().toString());
      assertNull(regionABC.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.getNodeMethodLocal,
            new Object[]{Fqn.fromString(fqn2)});
      interceptor.invoke(mc);

      Region regionAB = regionManager.getRegion(fqn2);
      event = regionAB.takeLastEventNode();
      assertEquals(EvictedEventNode.VISIT_NODE_EVENT, event.getEvent());
      assertEquals(fqn2, event.getFqn().toString());
      assertNull(regionAB.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.getDataMapMethodLocal, new Object[]{Fqn.fromString(fqn3)});
      interceptor.invoke(mc);
      Region regionABD = regionManager.getRegion(fqn3);
      event = regionABD.takeLastEventNode();
      assertEquals(EvictedEventNode.VISIT_NODE_EVENT, event.getEvent());
      assertEquals(fqn3, event.getFqn().toString());
      assertNull(regionABD.takeLastEventNode());

      for (int i = 0; i < 10; i++)
      {
         Fqn fqn = Fqn.fromString(fqn3);
         mc = MethodCallFactory.create(MethodDeclarations.getNodeMethodLocal, new Object[]{fqn});
         interceptor.invoke(mc);
      }

      for (int i = 0; i < 10; i++)
      {
         Region region = regionManager.getRegion(fqn3);
         event = region.takeLastEventNode();
         assertEquals(EvictedEventNode.VISIT_NODE_EVENT, event.getEvent());
         assertEquals(fqn3, event.getFqn().toString());
      }

      assertNull(regionManager.getRegion(fqn3).takeLastEventNode());

      // check null handling.
      mc = MethodCallFactory.create(MethodDeclarations.getDataMapMethodLocal, new Object[]{null});
      interceptor.invoke(mc);

   }

   public void testVisitElement() throws Throwable
   {
      // make sure a get/visit on an empty node and empty element results in no cache events being added to event queue
      // aka Region.
      Fqn fqn = Fqn.fromString(fqn4);
      Object key = "key";
      MethodCall mc = MethodCallFactory.create(MethodDeclarations.getKeyValueMethodLocal, new Object[]{fqn, key, Boolean.FALSE});
      interceptor.invoke(mc);
      Region region = regionManager.getRegion(fqn.toString());
      assertNull(region.takeLastEventNode());

      // add the node but try to get on a null element should result in no cache events being added to Region.
      cache.put(fqn, "wrongkey", "");
      mc = MethodCallFactory.create(MethodDeclarations.getKeyValueMethodLocal, new Object[]{fqn, key, Boolean.FALSE});
      interceptor.invoke(mc);
      assertNull(region.takeLastEventNode());

      // now make sure if we try to get on the node/key we just created in cache, that this DOES add a EvictedEventNode to
      // the Region.
      mc = MethodCallFactory.create(MethodDeclarations.getKeyValueMethodLocal, new Object[]{fqn, "wrongkey", Boolean.FALSE});
      interceptor.invoke(mc);
      EvictedEventNode event = region.takeLastEventNode();
      assertEquals(fqn, event.getFqn());
      assertEquals(EvictedEventNode.VISIT_NODE_EVENT, event.getEvent());

      assertNull(region.takeLastEventNode());

      cache.put(fqn4, key, "value");
      // test on element granularity configured node.
      fqn = Fqn.fromString(fqn4);
      mc = MethodCallFactory.create(MethodDeclarations.getKeyValueMethodLocal, new Object[]{fqn, key, Boolean.FALSE});
      interceptor.invoke(mc);

      region = regionManager.getRegion(fqn.toString());
      event = region.takeLastEventNode();

      assertEquals(EvictedEventNode.VISIT_NODE_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());

      assertNull(region.takeLastEventNode());

      fqn = Fqn.fromString("/d/e/g");
      for (int i = 0; i < 1000; i++)
      {
         key = new Integer(i);

         cache.put("/d/e/g", key, "");

         mc = MethodCallFactory.create(MethodDeclarations.getKeyValueMethodLocal, new Object[]{fqn, key, Boolean.TRUE});
         interceptor.invoke(mc);
      }

      region = regionManager.getRegion(fqn.toString());
      for (int i = 0; i < 1000; i++)
      {
         event = region.takeLastEventNode();
         assertEquals(fqn, event.getFqn());
         assertEquals(EvictedEventNode.VISIT_NODE_EVENT, event.getEvent());
      }

      cache.put("/a/b/c", key, "");
      fqn = Fqn.fromString("/a/b/c");
      mc = MethodCallFactory.create(MethodDeclarations.getKeyValueMethodLocal, new Object[]{fqn, key, Boolean.FALSE});
      interceptor.invoke(mc);

      region = regionManager.getRegion(fqn.toString());
      event = region.takeLastEventNode();

      assertNull(region.takeLastEventNode());
      assertEquals(EvictedEventNode.VISIT_NODE_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());

      for (int i = 0; i < 1000; i++)
      {
         key = new Integer(i);

         cache.put(fqn.toString(), key, "");

         mc = MethodCallFactory.create(MethodDeclarations.getKeyValueMethodLocal, new Object[]{fqn, key, Boolean.TRUE});
         interceptor.invoke(mc);
      }

      for (int i = 0; i < 1000; i++)
      {
         event = region.takeLastEventNode();
         assertEquals(EvictedEventNode.VISIT_NODE_EVENT, event.getEvent());
         assertEquals(fqn, event.getFqn());
      }

      assertNull(region.takeLastEventNode());

      // check null handling
      mc = MethodCallFactory.create(MethodDeclarations.getKeyValueMethodLocal, new Object[]{null, key, Boolean.TRUE});
      interceptor.invoke(mc);
      assertNull(region.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.getKeyValueMethodLocal, new Object[]{null, null, Boolean.TRUE});
      interceptor.invoke(mc);
      assertNull(region.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.getKeyValueMethodLocal, new Object[]{fqn, null, Boolean.TRUE});
      interceptor.invoke(mc);
      assertNull(region.takeLastEventNode());
   }

   public void testCreateNode() throws Throwable
   {
      Map data = new HashMap();
      for (int i = 0; i < 100; i++)
      {
         Integer in = new Integer(i);
         data.put(in, in);
      }

      // this region is node granularity
      Fqn fqn = Fqn.fromString("/a/b/c");

      MethodCall mc = MethodCallFactory.create(MethodDeclarations.putDataMethodLocal, new Object[]{null, fqn, data, Boolean.FALSE});
      interceptor.invoke(mc);

      Region region = regionManager.getRegion(fqn.toString());
      EvictedEventNode event = region.takeLastEventNode();

      assertEquals(EvictedEventNode.ADD_NODE_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertEquals(100, event.getElementDifference());

      Node node = cache.get(fqn.toString());
      assertNotNull(node);

      for (int i = 0; i < 100; i++)
      {
         Integer in = new Integer(i);
         assertTrue(node.containsKey(in));
         assertEquals(in, node.get(in));
      }

      for (int i = 0; i < 100; i++)
      {
         mc = MethodCallFactory.create(MethodDeclarations.putKeyValMethodLocal, new Object[]{null, fqn, new Integer(i),
               "value", Boolean.FALSE});
         interceptor.invoke(mc);

         assertEquals("value", cache.get(fqn.toString(), new Integer(i)));
      }

      for (int i = 0; i < 100; i++)
      {
         event = region.takeLastEventNode();
         assertNotNull(event);
         assertEquals(fqn, event.getFqn());
         assertEquals(EvictedEventNode.ADD_ELEMENT_EVENT, event.getEvent());
      }

      assertNull(region.takeLastEventNode());

      fqn = Fqn.fromString("/a/b");
      mc = MethodCallFactory.create(MethodDeclarations.putDataEraseMethodLocal, new Object[]{null, fqn, data, Boolean.FALSE, Boolean.FALSE});
      interceptor.invoke(mc);
      event = regionManager.getRegion(fqn.toString()).takeLastEventNode();
      assertFalse(event.isResetElementCount());
      assertEquals(EvictedEventNode.ADD_NODE_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertEquals(100, event.getElementDifference());
      assertNull(regionManager.getRegion(fqn.toString()).takeLastEventNode());
      node = cache.get(fqn.toString());
      assertEquals(100, node.numAttributes());

      assertNotNull(node);

      for (int i = 0; i < 100; i++)
      {
         Integer in = new Integer(i);
         assertTrue(node.containsKey(in));
         assertEquals(in, node.get(in));
      }

      mc = MethodCallFactory.create(MethodDeclarations.putDataEraseMethodLocal, new Object[]{null, fqn, data, Boolean.FALSE, Boolean.TRUE});
      interceptor.invoke(mc);
      event = regionManager.getRegion(fqn.toString()).takeLastEventNode();
      assertEquals(EvictedEventNode.ADD_NODE_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertEquals(100, event.getElementDifference());
      assertTrue(event.isResetElementCount());
      assertNull(regionManager.getRegion(fqn.toString()).takeLastEventNode());


      node = cache.get(fqn.toString());
      assertEquals(100, node.numAttributes());
      assertNotNull(node);

      for (int i = 0; i < 100; i++)
      {
         Integer in = new Integer(i);
         assertTrue(node.containsKey(in));
         assertEquals(in, node.get(in));
      }

   }

   public void testCreateElement() throws Throwable
   {
      Fqn fqn = Fqn.fromString("/d/e/f");
      Region region = regionManager.getRegion(fqn.toString());
      Object key = "key";
      Object value = "value";

      MethodCall mc = MethodCallFactory.create(MethodDeclarations.putKeyValMethodLocal,
            new Object[]{null, fqn, key, value, Boolean.FALSE});
      interceptor.invoke(mc);
      assertEquals("value", cache.get(fqn, key));
      EvictedEventNode event = region.takeLastEventNode();
      assertEquals(EvictedEventNode.ADD_ELEMENT_EVENT, event.getEvent());
      assertEquals(1, event.getElementDifference());
      assertEquals(fqn, event.getFqn());
      assertEquals("value", cache.get(fqn, key));
      assertNull(region.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.putFailFastKeyValueMethodLocal,
            new Object[]{null, fqn, key, value, Boolean.FALSE, new Long(1000)});
      interceptor.invoke(mc);
      assertEquals("value", cache.get(fqn, key));
      event = region.takeLastEventNode();
      assertEquals(EvictedEventNode.ADD_ELEMENT_EVENT, event.getEvent());
      assertEquals(1, event.getElementDifference());
      assertEquals(fqn, event.getFqn());
      assertEquals("value", cache.get(fqn, key));
      assertNull(region.takeLastEventNode());

   }

   public void testRemoveNode() throws Throwable
   {
      Fqn fqn = Fqn.fromString("/a/b/c");
      cache.put(fqn, "a", "b");
      cache.put(fqn, "b", "c");

      MethodCall mc = MethodCallFactory.create(MethodDeclarations.removeDataMethodLocal, new Object[]{
            null, fqn, Boolean.FALSE});
      interceptor.invoke(mc);

      assertEquals(0, cache.get(fqn).numAttributes());
      Region region = regionManager.getRegion(fqn.toString());
      EvictedEventNode event = region.takeLastEventNode();
      assertEquals(EvictedEventNode.REMOVE_NODE_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertNull(region.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.removeNodeMethodLocal, new Object[]{
            null, fqn, Boolean.FALSE
      });
      interceptor.invoke(mc);

      assertNull(cache.get(fqn));
      event = region.takeLastEventNode();
      assertEquals(EvictedEventNode.REMOVE_NODE_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertNull(region.takeLastEventNode());
   }

   public void testRemoveElement() throws Throwable
   {
      Fqn fqn = Fqn.fromString("/a/b/c");
      cache.put(fqn, "a", "b");
      cache.put(fqn, "b", "c");

      MethodCall mc = MethodCallFactory.create(MethodDeclarations.removeKeyMethodLocal,
            new Object[]{null, fqn, "a", Boolean.FALSE});
      interceptor.invoke(mc);

      assertNull(cache.get(fqn, "a"));
      Region region = regionManager.getRegion(fqn.toString());
      EvictedEventNode event = region.takeLastEventNode();
      assertEquals(EvictedEventNode.REMOVE_ELEMENT_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertEquals(1, event.getElementDifference());
      assertNull(region.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.removeKeyMethodLocal,
            new Object[]{null, fqn, "b", Boolean.FALSE});
      interceptor.invoke(mc);

      assertNull(cache.get(fqn, "b"));
      event = region.takeLastEventNode();
      assertEquals(EvictedEventNode.REMOVE_ELEMENT_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertEquals(1, event.getElementDifference());
      assertNull(region.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.removeKeyMethodLocal,
            new Object[]{null, fqn, "b", Boolean.FALSE});
      interceptor.invoke(mc);

      event = region.takeLastEventNode();
      assertNull(event);
   }

   public void testMixedEvent() throws Throwable
   {
      Map data = new HashMap();
      for (int i = 0; i < 100; i++)
      {
         Integer in = new Integer(i);
         data.put(in, in);
      }

      // this region is node granularity
      Fqn fqn = Fqn.fromString("/a/b/c");

      MethodCall mc = MethodCallFactory.create(MethodDeclarations.putDataMethodLocal, new Object[]{null, fqn, data, Boolean.FALSE});
      interceptor.invoke(mc);

      Region region = regionManager.getRegion(fqn.toString());
      EvictedEventNode event = region.takeLastEventNode();

      assertEquals(EvictedEventNode.ADD_NODE_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertEquals(100, event.getElementDifference());
      assertNull(region.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.getNodeMethodLocal,
            new Object[]{fqn});
      interceptor.invoke(mc);
      event = region.takeLastEventNode();
      assertEquals(EvictedEventNode.VISIT_NODE_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertNull(region.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.removeNodeMethodLocal, new Object[]{
            null, fqn, Boolean.FALSE
      });
      interceptor.invoke(mc);
      assertNull(cache.get(fqn));
      event = region.takeLastEventNode();
      assertEquals(EvictedEventNode.REMOVE_NODE_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertNull(region.takeLastEventNode());

      Object key = "key";
      Object value = "value";
      mc = MethodCallFactory.create(MethodDeclarations.putFailFastKeyValueMethodLocal,
            new Object[]{null, fqn, key, value, Boolean.FALSE, new Long(1000)});
      interceptor.invoke(mc);
      assertEquals("value", cache.get(fqn, key));
      event = region.takeLastEventNode();
      assertEquals(EvictedEventNode.ADD_ELEMENT_EVENT, event.getEvent());
      assertEquals(1, event.getElementDifference());
      assertEquals(fqn, event.getFqn());
      assertEquals("value", cache.get(fqn, key));
      assertNull(region.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.getKeyValueMethodLocal, new Object[]{fqn, key, Boolean.FALSE});
      interceptor.invoke(mc);
      region = regionManager.getRegion(fqn.toString());
      event = region.takeLastEventNode();
      assertEquals(EvictedEventNode.VISIT_NODE_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertNull(region.takeLastEventNode());

      mc = MethodCallFactory.create(MethodDeclarations.removeKeyMethodLocal,
            new Object[]{null, fqn, key, Boolean.FALSE});
      interceptor.invoke(mc);

      assertNull(cache.get(fqn, key));
      event = region.takeLastEventNode();
      assertEquals(EvictedEventNode.REMOVE_ELEMENT_EVENT, event.getEvent());
      assertEquals(fqn, event.getFqn());
      assertEquals(1, event.getElementDifference());
      assertNull(region.takeLastEventNode());

   }

}