package org.jboss.cache.optimistic;

import org.jboss.cache.Cache;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.NodeSPI;
import org.jboss.cache.util.TestingUtil;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.transaction.DummyTransactionManagerLookup;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.List;

/**
 * Tests whether data versions are transferred along with state
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani</a>
 * @since 2.1.0
 */
@Test(groups = {"functional"})
public class DataVersionTransferTest
{
   private List<Cache<Object, Object>> caches = new ArrayList<Cache<Object, Object>>(2);

   @BeforeMethod
   public void setUp()
   {

      caches.add(new DefaultCacheFactory().createCache(false));
      caches.get(0).getConfiguration().setTransactionManagerLookupClass(DummyTransactionManagerLookup.class.getName());
      caches.get(0).getConfiguration().setNodeLockingScheme(Configuration.NodeLockingScheme.OPTIMISTIC);
      caches.get(0).getConfiguration().setCacheMode(Configuration.CacheMode.REPL_SYNC);
      caches.get(0).start();

      caches.add(new DefaultCacheFactory().createCache(false));
      caches.get(1).getConfiguration().setTransactionManagerLookupClass(DummyTransactionManagerLookup.class.getName());
      caches.get(1).getConfiguration().setNodeLockingScheme(Configuration.NodeLockingScheme.OPTIMISTIC);
      caches.get(1).getConfiguration().setCacheMode(Configuration.CacheMode.REPL_SYNC);
   }

   @AfterMethod
   public void tearDown()
   {
      if (caches != null)
      {
         for (Cache cache : caches)
         {
            try
            {
               cache.getConfiguration().getRuntimeConfig().getTransactionManager().rollback();
            }
            catch (Exception e)
            {
               // do nothing?
            }
            cache.stop();
         }
      }
   }

   public void testStateTransferDefaultVersions() throws Exception
   {
      Fqn f = Fqn.fromString("/one/two/three");
      caches.get(0).put(f, "k", "v");
      caches.get(0).put(f, "k1", "v1");
      caches.get(0).remove(f, "k1");

      NodeSPI n = (NodeSPI) caches.get(0).getRoot().getChild(f);
      DataVersion dv = n.getVersion();

      assert dv instanceof DefaultDataVersion : "Should be an instance of DefaultDataVersion";

      assert ((DefaultDataVersion) dv).getRawVersion() == 3 : "Should have accurate data version";

      // now start next cache instance
      caches.get(1).start();

      TestingUtil.blockUntilViewsReceived(10000, caches.get(0), caches.get(1));

      assert caches.get(1).get(f, "k").equals("v") : "Value should have transferred";

      n = (NodeSPI) caches.get(1).getRoot().getChild(f);

      dv = n.getVersion();

      assert dv instanceof DefaultDataVersion : "Should be an instance of DefaultDataVersion";

      assert ((DefaultDataVersion) dv).getRawVersion() == 3 : "Version should have transferred";
      // make sure leakage doesn't occur into data map
      assert n.getData().size() == 1;
   }

   public void testStateTransferCustomVersion() throws Exception
   {
      Fqn f = Fqn.fromString("/one/two/three");
      caches.get(0).getInvocationContext().getOptionOverrides().setDataVersion(new CharVersion('A'));
      caches.get(0).put(f, "k", "v");
      caches.get(0).getInvocationContext().getOptionOverrides().setDataVersion(new CharVersion('B'));
      caches.get(0).put(f, "k1", "v1");
      caches.get(0).getInvocationContext().getOptionOverrides().setDataVersion(new CharVersion('C'));
      caches.get(0).remove(f, "k1");

      NodeSPI n = (NodeSPI) caches.get(0).getRoot().getChild(f);
      DataVersion dv = n.getVersion();

      assert dv instanceof CharVersion : "Should be an instance of CharVersion";

      assert ((CharVersion) dv).version == 'C' : "Should have accurate data version";

      // now start next cache instance
      caches.get(1).start();

      TestingUtil.blockUntilViewsReceived(10000, caches.get(0), caches.get(1));

      assert caches.get(1).get(f, "k").equals("v") : "Value should have transferred";

      n = (NodeSPI) caches.get(1).getRoot().getChild(f);

      dv = n.getVersion();

      assert dv instanceof CharVersion : "Should be an instance of CharVersion";

      assert ((CharVersion) dv).version == 'C' : "Version should have transferred";
      // make sure leakage doesn't occur into data map
      assert n.getData().size() == 1;
   }

   public void testStateTransferIntermediateNodeDefaultVersions() throws Exception
   {
      Fqn f = Fqn.fromString("/one/two/three");
      Fqn intermediate = f.getParent();

      caches.get(0).put(f, "k", "v");
      caches.get(0).put(intermediate, "k", "v");

      NodeSPI n = (NodeSPI) caches.get(0).getRoot().getChild(intermediate);
      DataVersion dv = n.getVersion();

      assert dv instanceof DefaultDataVersion : "Should be an instance of DefaultDataVersion";

      assert ((DefaultDataVersion) dv).getRawVersion() == 1 : "Should have accurate data version";

      // now start next cache instance
      caches.get(1).start();

      TestingUtil.blockUntilViewsReceived(10000, caches.get(0), caches.get(1));

      assert caches.get(1).get(intermediate, "k").equals("v") : "Value should have transferred";

      n = (NodeSPI) caches.get(0).getRoot().getChild(intermediate);
      dv = n.getVersion();

      assert dv instanceof DefaultDataVersion : "Should be an instance of DefaultDataVersion";

      assert ((DefaultDataVersion) dv).getRawVersion() == 1 : "Should have accurate data version";
      // make sure leakage doesn't occur into data map
      assert n.getData().size() == 1;
   }

   public void testStateTransferIntermediateNodeCustomVersion() throws Exception
   {
      Fqn f = Fqn.fromString("/one/two/three");
      Fqn intermediate = f.getParent();

      caches.get(0).put(f, "k", "v");
      caches.get(0).getInvocationContext().getOptionOverrides().setDataVersion(new CharVersion('X'));
      caches.get(0).put(intermediate, "k", "v");

      NodeSPI n = (NodeSPI) caches.get(0).getRoot().getChild(intermediate);
      DataVersion dv = n.getVersion();

      assert dv instanceof CharVersion : "Should be an instance of CharVersion";

      assert ((CharVersion) dv).version == 'X' : "Should have accurate data version";

      // now start next cache instance
      caches.get(1).start();

      TestingUtil.blockUntilViewsReceived(10000, caches.get(0), caches.get(1));

      assert caches.get(1).get(intermediate, "k").equals("v") : "Value should have transferred";

      n = (NodeSPI) caches.get(0).getRoot().getChild(intermediate);
      dv = n.getVersion();

      assert dv instanceof CharVersion : "Should be an instance of CharVersion";

      assert ((CharVersion) dv).version == 'X' : "Should have accurate data version";
      // make sure leakage doesn't occur into data map
      assert n.getData().size() == 1;
   }

   public static class CharVersion implements DataVersion
   {
      private char version = 'A';

      public CharVersion(char version)
      {
         this.version = version;
      }

      public boolean newerThan(DataVersion other)
      {
         if (other instanceof CharVersion)
         {
            CharVersion otherVersion = (CharVersion) other;
            return version > otherVersion.version;
         }
         else
         {
            return true;
         }
      }
   }

}
