package org.jboss.cache.optimistic;

import junit.framework.Assert;
import org.jboss.cache.DataNode;
import org.jboss.cache.Fqn;
import org.jboss.cache.TreeCache;
import org.jboss.cache.interceptors.Interceptor;
import org.jboss.cache.interceptors.OptimisticCreateIfNotExistsInterceptor;
import org.jboss.cache.interceptors.OptimisticInterceptor;
import org.jboss.cache.interceptors.OptimisticLockingInterceptor;
import org.jboss.cache.lock.IdentityLock;
import org.jgroups.blocks.MethodCall;

import javax.transaction.TransactionManager;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * // Test for JBCACHE-1228
 *
 * @author Manik Surtani (<a href="mailto:manik@jboss.org">manik@jboss.org</a>)
 */
public class OptimisticLockInterceptorTest extends AbstractOptimisticTestCase
{
   private TreeCache cache;
   private LockReportInterceptor lri;
   private Fqn parent = Fqn.fromString("/parent");
   private Fqn child = Fqn.fromString("/parent/child");
   private TransactionManager tm;

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

   protected void setUp() throws Exception
   {
      cache = createCache();
      lri = new LockReportInterceptor();
      lri.setCache(cache);

      Interceptor i = (Interceptor) cache.getInterceptors().get(0);
      Interceptor lockInterceptor = i;
      // find the OptimisticLockInterceptor and insert LockReportInterceptor after that.
      while (!(lockInterceptor instanceof OptimisticLockingInterceptor)) lockInterceptor = lockInterceptor.getNext();
      Interceptor next = lockInterceptor.getNext();

      lockInterceptor.setNext(lri);
      lri.setNext(next);

      cache.setInterceptorChain(i);

      cache.put(child, "key", "value");

      tm = cache.getTransactionManager();
   }

   protected void tearDown()
   {
      cache.stop();
   }

   public void testPut() throws Exception
   {
      tm.begin();
      cache.put(child, "key2", "value2");
      lri.reset();
      lri.expectsReadLock(Fqn.ROOT);
      lri.expectsReadLock(parent);
      lri.expectsWriteLock(child);
      tm.commit();
      lri.assertReceivedExpectedLocks();

      assertNoStaleLocks();
   }

   public void testGet() throws Exception
   {
      tm.begin();
      cache.get(child, "key2");
      lri.reset();
      // nothing is stale, expecting nothing here.
      tm.commit();
      lri.assertReceivedExpectedLocks();

      assertNoStaleLocks();
   }

   public void testRemove() throws Exception
   {
      tm.begin();
      cache.remove(child, "key2");
      lri.reset();
      lri.expectsReadLock(Fqn.ROOT);
      lri.expectsReadLock(parent);
      lri.expectsWriteLock(child);
      tm.commit();
      lri.assertReceivedExpectedLocks();

      assertNoStaleLocks();
   }

   public void testPutLockParentForCIR() throws Exception
   {
      cache.setLockParentForChildInsertRemove(true);
      cache.remove(parent);
      cache.put(parent, "k", "v");

      tm.begin();
      cache.put(child, "key2", "value2");
      lri.reset();
      lri.expectsReadLock(Fqn.ROOT);
      lri.expectsWriteLock(parent);
      lri.expectsWriteLock(child);
      tm.commit();
      lri.assertReceivedExpectedLocks();

      assertNoStaleLocks();
   }

   public void testGetLockParentForCIR() throws Exception
   {
      cache.setLockParentForChildInsertRemove(true);
      tm.begin();
      cache.get(child, "key2");
      lri.reset();
      // nothing is stale, expecting nothing here.
      tm.commit();
      lri.assertReceivedExpectedLocks();

      assertNoStaleLocks();
   }

   public void testRemoveLockParentForCIR() throws Exception
   {
      cache.setLockParentForChildInsertRemove(true);
      tm.begin();
      cache.remove(child);
      lri.reset();
      lri.expectsReadLock(Fqn.ROOT);
      lri.expectsWriteLock(parent);
      lri.expectsWriteLock(child);
      tm.commit();
      lri.assertReceivedExpectedLocks();

      assertNoStaleLocks();
   }


   public void testPutNodeNotExists() throws Exception
   {
      cache.remove(Fqn.ROOT);
      tm.begin();
      cache.put(child, "key2", "value2");
      lri.reset();
      lri.expectsReadLock(Fqn.ROOT);
      lri.expectsWriteLock(parent);
      lri.expectsWriteLock(child);
      tm.commit();
      lri.assertReceivedExpectedLocks();

      assertNoStaleLocks();
   }

   public void testGetNodeNotExists() throws Exception
   {
      cache.remove(Fqn.ROOT);
      tm.begin();
      cache.get(child, "key2");
      lri.reset();
      // nothing is stale, expecting nothing here.
      tm.commit();
      lri.assertReceivedExpectedLocks();

      assertNoStaleLocks();
   }

   public void testRemoveNodeNotExists() throws Exception
   {
      cache.remove(Fqn.ROOT);
      tm.begin();
      cache.remove(child, "key2");
      lri.reset();
      // nothing is stale, expecting nothing here.
      tm.commit();
      lri.assertReceivedExpectedLocks();

      assertNoStaleLocks();
   }

   private void assertNoStaleLocks()
   {
      assertEquals(cache.getNumberOfLocksHeld(), 0);
   }
}

class LockReportInterceptor extends OptimisticInterceptor
{
   private Object READ = "READ";
   private Object WRITE = "WRITE";
   private Map expected = new HashMap();
   private Map actual = new HashMap();

   void reset()
   {
      expected.clear();
      actual.clear();
   }

   void assertReceivedExpectedLocks()
   {
      Assert.assertEquals(expected, actual);
   }

   void expectsReadLock(Fqn f)
   {
      expected.put(f, READ);
   }

   void expectsWriteLock(Fqn f)
   {
      expected.put(f, WRITE);
   }

   public Object invoke(MethodCall call) throws Throwable
   {
      TransactionWorkspace w = getTransactionWorkspace(getInvocationContext().getGlobalTransaction());
      Map nodeMap = w.getNodes();
      for (Iterator i = nodeMap.keySet().iterator(); i.hasNext();)
      {
         WorkspaceNode wn = (WorkspaceNode) nodeMap.get(i.next());
         DataNode n = wn.getNode();
         IdentityLock lock = n.getLock();
         if (lock.isLocked())
         {
            actual.put(n.getFqn(), lock.isReadLocked() ? READ : WRITE);
         }
      }

      return super.invoke(call);
   }


}
