package org.jboss.cache.options;

import junit.framework.Assert;
import junit.framework.TestCase;
import org.jboss.cache.Fqn;
import org.jboss.cache.OptimisticTreeNode;
import org.jboss.cache.TreeCache;
import org.jboss.cache.config.Option;
import org.jboss.cache.misc.TestingUtil;
import org.jboss.cache.optimistic.DataVersion;
import org.jboss.cache.optimistic.DefaultDataVersion;

import javax.transaction.RollbackException;
import javax.transaction.TransactionManager;

/**
 * Tests the passing in of explicit {@see DataVersion} instances when using optimistic locking + replication.
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
public class ExplicitVersionsReplTest extends TestCase
{

   private TreeCache cache[];
   private Fqn fqn = Fqn.fromString("/a");
   private String key = "key";
   private Option option = new Option();

   protected void setUp() throws Exception
   {
      if (cache != null) tearDown();
      cache = new TreeCache[2];
      cache[0] = createCache();
      cache[1] = createCache();
      TestingUtil.blockUntilViewsReceived(cache, 20000);
   }

   private TreeCache createCache() throws Exception
   {
      TreeCache cache = new TreeCache();
      cache.setCacheMode(TreeCache.REPL_SYNC);
      cache.setNodeLockingScheme("OPTIMISTIC");
      // give us lots of time to trace and debug shit
      cache.setSyncCommitPhase(true);
      cache.setSyncRollbackPhase(true);
      cache.setSyncReplTimeout(600000);
      cache.setLockAcquisitionTimeout(600000);
      cache.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
      cache.startService();
      return cache;
   }

   protected void tearDown()
   {
      if (cache != null)
      {
         for (int i = 0; i < cache.length; i++) destroyCache(cache[i]);
         cache = null;
      }
   }

   private void destroyCache(TreeCache c)
   {
      TransactionManager tm = c.getTransactionManager();
      try
      {
         if (tm != null && tm.getTransaction() != null) tm.getTransaction().rollback();
      }
      catch (Exception e)
      {
      }
      c.stopService();
   }

   /**
    * This test sets a custom data version first, expects it to replicate, and then does a put on the remote
    * cache using an implicit data version.  Should fail with a CCE.
    *
    * @throws Exception
    */
   public void testIncompatibleVersionTypes1() throws Exception
   {
      DataVersion version = new TestVersion("99");
      option.setDataVersion(version);
      cache[0].put(fqn, key, "value", option); // TestVersion-99 should be on both caches now

      TransactionManager mgr = cache[0].getTransactionManager();
      mgr.begin();
      // don't explicitly set a data version.

      System.out.println("************ stage 2");

      // force an IC scrub
      //cache[1].getInvocationContext().setOptionOverrides(null);
      cache[1].put(fqn, key, "value2");
      try
      {
         mgr.commit();

         System.out.println(cache[0].printDetails());
         System.out.println(cache[1].printDetails());

         Assert.assertTrue("expected to fail", false);
      }
      catch (RollbackException e)
      {
         // should fail.
         Assert.assertTrue("expected to fail with a nested ClassCastException", true);
      }
   }

   /**
    * This test sets a custom data version first, expects it to replicate, and then does a put on the remote
    * cache using a higher custom data version.  Should pass and not throw any exceptions.
    *
    * @throws Exception
    */
   public void testCompatibleVersionTypes1() throws Exception
   {
      DataVersion version = new TestVersion("99");
      option.setDataVersion(version);
      cache[0].put(fqn, key, "value", option); // TestVersion-99 should be on both caches now

      TransactionManager mgr = cache[0].getTransactionManager();
      mgr.begin();

      version = new TestVersion("999");
      option.setDataVersion(version);
      cache[1].put(fqn, key, "value2", option);
      mgr.commit();
   }


   /**
    * This test sets a custom data version first, expects it to replicate, and then does a put on the remote
    * cache using a lower custom data version.  Should fail.
    *
    * @throws Exception
    */
   public void testCompatibleVersionTypesOutDatedVersion1() throws Exception
   {
      DataVersion version = new TestVersion("99");
      option.setDataVersion(version);
      cache[0].put(fqn, key, "value", option); // TestVersion-99 should be on both caches now

      TransactionManager mgr = cache[0].getTransactionManager();
      mgr.begin();

      version = new TestVersion("29");
      option.setDataVersion(version);
      cache[1].put(fqn, key, "value2", option);
      try
      {
         mgr.commit();
         Assert.assertTrue("expected to fail", false);
      }
      catch (RollbackException e)
      {
         // should fail.
         Assert.assertTrue("expected to fail with a CacheException to do with a versioning mismatch", true);
      }
   }


   /**
    * This test sets an implicit data version first, expects it to replicate, and then does a put on the remote
    * cache using a custom data version.  Should fail with a CCE.
    *
    * @throws Exception
    */
//   public void testIncompatibleVersionTypes2() throws Exception
//   {
//      cache[0].put(fqn, key, "value"); // default data version should be on both caches now
//
//      TransactionManager mgr = cache[0].getTransactionManager();
//      mgr.begin();
//
//      // explicitly set data version
//      DataVersion version = new TestVersion("99");
//      option.setDataVersion(version);
//
//      try
//      {
//         cache[1].put(fqn, key, "value2", option);
//         mgr.commit();
//
//         OptimisticTreeNode otn = (OptimisticTreeNode) cache[1].get(fqn);
//         System.out.println("***" + otn.getVersion());
//
//         otn = (OptimisticTreeNode) cache[0].get(fqn);
//         System.out.println("***" + otn.getVersion());
//
//         Assert.assertTrue("expected to fail", false);
//      }
//      catch (Exception e)
//      {
//         // should fail.
//         Assert.assertTrue("expected to fail", true);
//      }
//   }

   /**
    * This test sets an implicit data version first, expects it to replicate, and then does a put on the remote
    * cache using a higher implicit data version.  Should pass and not throw any exceptions.
    *
    * @throws Exception
    */
   public void testCompatibleVersionTypes2() throws Exception
   {
      cache[0].put(fqn, key, "value"); // TestVersion-99 should be on both caches now

      TransactionManager mgr = cache[0].getTransactionManager();
      mgr.begin();

      DataVersion version = new DefaultDataVersion(300);
      option.setDataVersion(version);
      cache[1].put(fqn, key, "value2", option);
      mgr.commit();
   }


   /**
    * This test sets an implicit data version first, expects it to replicate, and then does a put on the remote
    * cache using a lower implicit data version.  Should fail.
    *
    * @throws Exception
    */
   public void testCompatibleVersionTypesOutDatedVersion2() throws Exception
   {
      DataVersion version = new DefaultDataVersion(200);
      option.setDataVersion(version);
      cache[0].put(fqn, key, "value", option); // TestVersion-99 should be on both caches now

      TransactionManager mgr = cache[0].getTransactionManager();
      mgr.begin();

      version = new DefaultDataVersion(100);
      option.setDataVersion(version);
      cache[1].put(fqn, key, "value2", option);
      try
      {
         // this call will use implicit versioning and will hence fail.
         mgr.commit();
         Assert.assertTrue("expected to fail", false);
      }
      catch (Exception e)
      {
         // should fail.
         Assert.assertTrue("expected to fail with a CacheException to do with a versioning mismatch", true);
      }
   }

   public void testPropagationOfDefaultVersions() throws Exception
   {
      DefaultDataVersion expected = new DefaultDataVersion();
      expected = (DefaultDataVersion) expected.increment();

      cache[0].put(fqn, key, "value");

      assertEquals("value", cache[0].get(fqn, key));
      assertEquals("value", cache[1].get(fqn, key));
      assertEquals(expected, ((OptimisticTreeNode) cache[0].get(fqn)).getVersion());
      assertEquals(expected, ((OptimisticTreeNode) cache[1].get(fqn)).getVersion());

      cache[1].put(fqn, key, "value2");
      expected = (DefaultDataVersion) expected.increment();

      assertEquals("value2", cache[0].get(fqn, key));
      assertEquals("value2", cache[1].get(fqn, key));
      assertEquals(expected, ((OptimisticTreeNode) cache[0].get(fqn)).getVersion());
      assertEquals(expected, ((OptimisticTreeNode) cache[1].get(fqn)).getVersion());
   }

   public void testPropagationOfCustomVersions() throws Exception
   {
      TestVersion expected = new TestVersion("100");
      option = new Option();
      option.setDataVersion(expected);

      cache[0].put(fqn, key, "value", option);

      assertEquals("value", cache[0].get(fqn, key));
      assertEquals("value", cache[1].get(fqn, key));
      assertEquals(expected, ((OptimisticTreeNode) cache[0].get(fqn)).getVersion());
      assertEquals(expected, ((OptimisticTreeNode) cache[1].get(fqn)).getVersion());

      expected = new TestVersion("200");
      option = new Option();
      option.setDataVersion(expected);
      cache[1].put(fqn, key, "value2", option);

      assertEquals("value2", cache[0].get(fqn, key));
      assertEquals("value2", cache[1].get(fqn, key));
      assertEquals(expected, ((OptimisticTreeNode) cache[0].get(fqn)).getVersion());
      assertEquals(expected, ((OptimisticTreeNode) cache[1].get(fqn)).getVersion());
   }

   public void testExplicitVersionOnRoot() throws Exception
   {
      TestVersion newVersion = new TestVersion("100");
      option = new Option();
      option.setDataVersion(newVersion);

      cache[0].getTransactionManager().begin();
      cache[0].put(Fqn.ROOT, "k", "v", option);

      // should not barf
      cache[0].getTransactionManager().commit();
   }
   
   public void testExplicitVersionOnLeaf() throws Exception
   {
      cache[0].put("/org/domain/Entity", null);
      assertEquals(1, ((DefaultDataVersion)((OptimisticTreeNode)cache[0].get("/org/domain/Entity")).getVersion()).getRawVersion());
      assertEquals(1, ((DefaultDataVersion)((OptimisticTreeNode)cache[1].get("/org/domain/Entity")).getVersion()).getRawVersion());

      TestVersion v = new TestVersion("Arse");
      Option o = new Option();
      o.setDataVersion(v);

      cache[0].put(Fqn.fromString("/org/domain/Entity/EntityInstance#1"), "k", "v", o);

      assertEquals(1, ((DefaultDataVersion)((OptimisticTreeNode)cache[0].get("/org/domain/Entity")).getVersion()).getRawVersion());
      assertEquals(v, ((OptimisticTreeNode)cache[0].get("/org/domain/Entity/EntityInstance#1")).getVersion());
      assertEquals(1, ((DefaultDataVersion)((OptimisticTreeNode)cache[1].get("/org/domain/Entity")).getVersion()).getRawVersion());
      assertEquals(v, ((OptimisticTreeNode)cache[1].get("/org/domain/Entity/EntityInstance#1")).getVersion());

   }

   public void testExplicitVersionOnLeafImplicitParentCreation() throws Exception
   {
      TestVersion v = new TestVersion("Arse");
      Option o = new Option();
      o.setDataVersion(v);

      cache[0].put(Fqn.fromString("/org/domain/Entity/EntityInstance#1"), "k", "v", o);

      assertEquals(1, ((DefaultDataVersion)((OptimisticTreeNode)cache[0].get("/org/domain/Entity")).getVersion()).getRawVersion());
      assertEquals(v, ((OptimisticTreeNode)cache[0].get("/org/domain/Entity/EntityInstance#1")).getVersion());
      assertEquals(1, ((DefaultDataVersion)((OptimisticTreeNode)cache[1].get("/org/domain/Entity")).getVersion()).getRawVersion());
      assertEquals(v, ((OptimisticTreeNode)cache[1].get("/org/domain/Entity/EntityInstance#1")).getVersion());

   }

   public void testExplicitVersionOnParentAndChild() throws Exception
   {
      TestVersion vParent = new TestVersion("Parent-Version");
      Option o = new Option();
      o.setDataVersion(vParent);

      cache[0].getTransactionManager().begin();
      cache[0].put(Fqn.fromString("/parent"), "k", "v", o);
      cache[0].getTransactionManager().commit();

      assertEquals(0, ((DefaultDataVersion)((OptimisticTreeNode)cache[0].get("/")).getVersion()).getRawVersion());
      assertEquals(vParent, ((OptimisticTreeNode)cache[0].get("/parent")).getVersion());
      assertEquals(0, ((DefaultDataVersion)((OptimisticTreeNode)cache[1].get("/")).getVersion()).getRawVersion());
      assertEquals(vParent, ((OptimisticTreeNode)cache[1].get("/parent")).getVersion());


      TestVersion vChild = new TestVersion("Child-Version");
      o = new Option();
      o.setDataVersion(vChild);

      cache[0].getTransactionManager().begin();
      cache[0].put(Fqn.fromString("/parent/child"), "k", "v", o);
      cache[0].getTransactionManager().commit();

      assertEquals(0, ((DefaultDataVersion)((OptimisticTreeNode)cache[0].get("/")).getVersion()).getRawVersion());
      assertEquals(vParent, ((OptimisticTreeNode)cache[0].get("/parent")).getVersion());
      assertEquals(vChild, ((OptimisticTreeNode)cache[0].get("/parent/child")).getVersion());
      assertEquals(0, ((DefaultDataVersion)((OptimisticTreeNode)cache[1].get("/")).getVersion()).getRawVersion());
      assertEquals(vParent, ((OptimisticTreeNode)cache[1].get("/parent")).getVersion());
      assertEquals(vChild, ((OptimisticTreeNode)cache[1].get("/parent/child")).getVersion());

   }

}
