/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.api;

import org.jboss.cache.Cache;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
import org.jboss.cache.NodeNotExistsException;
import org.jboss.cache.NodeSPI;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertNull;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import javax.transaction.TransactionManager;

@Test(groups = {"functional", "jgroups"})
public class NodeReplicatedMoveTest
{
   protected Node<Object, Object> rootNode, nodeA, nodeB, nodeC, nodeD, nodeE;
   protected CacheSPI<Object, Object> cache1;
   protected CacheSPI<Object, Object> cache2;
   protected TransactionManager tm;
   protected static final Fqn<String> A = Fqn.fromString("/a"), B = Fqn.fromString("/b"), C = Fqn.fromString("/c"), D = Fqn.fromString("/d"), E = Fqn.fromString("/e");
   protected Object k = "key", vA = "valueA", vB = "valueB", vC = "valueC", vD = "valueD", vE = "valueE";

   protected boolean optimistic = false;

   @BeforeMethod(alwaysRun = true)
   public void setUp() throws Exception
   {
      // start a single cache instance
      cache1 = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC), false);
      cache1.getConfiguration().setSyncCommitPhase(true);
      cache1.getConfiguration().setSyncRollbackPhase(true);
      cache1.getConfiguration().setNodeLockingScheme(optimistic ? Configuration.NodeLockingScheme.OPTIMISTIC : Configuration.NodeLockingScheme.PESSIMISTIC);
      cache1.start();
      rootNode = cache1.getRoot();
      tm = cache1.getTransactionManager();

      //  start second instance
      cache2 = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC), false);
      cache2.getConfiguration().setSyncCommitPhase(true);
      cache2.getConfiguration().setSyncRollbackPhase(true);
      cache2.getConfiguration().setNodeLockingScheme(optimistic ? Configuration.NodeLockingScheme.OPTIMISTIC : Configuration.NodeLockingScheme.PESSIMISTIC);
      cache2.start();
   }

   @AfterMethod(alwaysRun = true)
   public void tearDown()
   {
      if (cache1 != null) cache1.stop();
      if (cache2 != null) cache2.stop();
      if (rootNode != null) rootNode = null;
   }

   public void testReplicatability()
   {
      nodeA = rootNode.addChild(A);
      nodeB = nodeA.addChild(B);

      nodeA.put(k, vA);
      nodeB.put(k, vB);

      assertEquals(vA, cache1.getRoot().getChild(A).get(k));
      assertEquals(vB, cache1.getRoot().getChild(A).getChild(B).get(k));

      assertEquals(vA, cache2.getRoot().getChild(A).get(k));
      assertEquals(vB, cache2.getRoot().getChild(A).getChild(B).get(k));

      // now move...
      cache1.move(nodeB.getFqn(), Fqn.ROOT);

      assertEquals(vA, cache1.getRoot().getChild(A).get(k));
      assertEquals(vB, cache1.getRoot().getChild(B).get(k));

      assertEquals(vA, cache2.getRoot().getChild(A).get(k));
      assertEquals(vB, cache2.getRoot().getChild(B).get(k));
   }

   public void testInvalidations() throws Exception
   {
      cache1.stop();
      cache2.stop();
      cache1.destroy();
      cache2.destroy();
      if (optimistic)
      {
         cache1.getConfiguration().setNodeLockingScheme(Configuration.NodeLockingScheme.OPTIMISTIC);
         cache2.getConfiguration().setNodeLockingScheme(Configuration.NodeLockingScheme.OPTIMISTIC);
      }
      cache1.getConfiguration().setCacheMode(Configuration.CacheMode.INVALIDATION_SYNC);
      cache2.getConfiguration().setCacheMode(Configuration.CacheMode.INVALIDATION_SYNC);
      cache1.start();
      cache2.start();

      nodeA = rootNode.addChild(A);
      nodeB = nodeA.addChild(B);

      nodeA.put(k, vA);
      nodeB.put(k, vB);

      assertEquals(vA, cache1.getRoot().getChild(A).get(k));
      assertEquals(vB, cache1.getRoot().getChild(A).getChild(B).get(k));

      assertInvalidated(cache2, A, "Should be invalidated", optimistic);
      assertInvalidated(cache2, Fqn.fromRelativeElements(A, B.getLastElement()), "Should be invalidated", optimistic);

      // now move...
      cache1.move(nodeB.getFqn(), Fqn.ROOT);

      assertEquals(vA, cache1.getRoot().getChild(A).get(k));
      assertEquals(vB, cache1.getRoot().getChild(B).get(k));

      assertInvalidated(cache2, A, "Should be invalidated", optimistic);
      assertInvalidated(cache2, B, "Should be invalidated", optimistic);

      // now make sure a node exists on cache 2
      cache2.getRoot().addChild(A).put("k2", "v2");

      // te invalidation will happen in afterCompletion, hence no exception!
      try
      {
         cache1.move(B, A);// should throw an NPE
         if (!optimistic) assert false : "Should throw an exception!";
      }
      catch (NodeNotExistsException expected)
      {
         if (optimistic) assert false : "Should not have thrown an exception!";
      }
   }

   private void assertInvalidated(Cache cache, Fqn fqn, String msg, boolean optimistic)
   {
      assert cache.getRoot().getChild(fqn) == null : msg;
      NodeSPI n = ((CacheSPI) cache).peek(fqn, true, true);
      assert n == null || optimistic : msg;
      assert !optimistic || !n.isValid() : msg;
   }

   public void testReplTxCommit() throws Exception
   {
      Fqn A_B = Fqn.fromRelativeFqn(A, B);
      nodeA = rootNode.addChild(A);
      nodeB = nodeA.addChild(B);

      nodeA.put(k, vA);
      nodeB.put(k, vB);

      assertEquals(vA, cache1.getRoot().getChild(A).get(k));
      assertEquals(vB, cache1.getRoot().getChild(A).getChild(B).get(k));

      assertEquals(vA, cache2.getRoot().getChild(A).get(k));
      assertEquals(vB, cache2.getRoot().getChild(A).getChild(B).get(k));

      // now move...
      tm.begin();
      cache1.move(nodeB.getFqn(), Fqn.ROOT);

      assertEquals(vA, cache1.get(A, k));
      assertNull(cache1.get(A_B, k));
      assertEquals(vB, cache1.get(B, k));
      tm.commit();

      assertEquals(vA, cache1.getRoot().getChild(A).get(k));
      assertEquals(vB, cache1.getRoot().getChild(B).get(k));
      assertEquals(vA, cache2.getRoot().getChild(A).get(k));
      assertEquals(vB, cache2.getRoot().getChild(B).get(k));

   }

   public void testReplTxRollback() throws Exception
   {
      nodeA = rootNode.addChild(A);
      nodeB = nodeA.addChild(B);

      nodeA.put(k, vA);
      nodeB.put(k, vB);

      assertEquals(vA, cache1.getRoot().getChild(A).get(k));
      assertEquals(vB, cache1.getRoot().getChild(A).getChild(B).get(k));
      assertEquals(vA, cache2.getRoot().getChild(A).get(k));
      assertEquals(vB, cache2.getRoot().getChild(A).getChild(B).get(k));

      // now move...
      tm.begin();
      cache1.move(nodeB.getFqn(), Fqn.ROOT);

      assertEquals(vA, cache1.get(A, k));
      assertEquals(vB, cache1.get(B, k));

      tm.rollback();

      assertEquals(vA, cache1.getRoot().getChild(A).get(k));
      assertEquals(vB, cache1.getRoot().getChild(A).getChild(B).get(k));
      assertEquals(vA, cache2.getRoot().getChild(A).get(k));
      assertEquals(vB, cache2.getRoot().getChild(A).getChild(B).get(k));
   }
}
