/**
 *
 */
package org.jboss.cache.optimistic;

import org.jboss.cache.Cache;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.util.TestingUtil;
import org.jboss.cache.commands.ReversibleCommand;
import org.jboss.cache.commands.VersionedDataCommand;
import org.jboss.cache.config.CacheLoaderConfig;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import org.jboss.cache.factories.XmlConfigurationParser;
import org.jboss.cache.interceptors.CacheMgmtInterceptor;
import org.jboss.cache.interceptors.CallInterceptor;
import org.jboss.cache.interceptors.NotificationInterceptor;
import org.jboss.cache.interceptors.OptimisticLockingInterceptor;
import org.jboss.cache.interceptors.OptimisticNodeInterceptor;
import org.jboss.cache.interceptors.OptimisticValidatorInterceptor;
import org.jboss.cache.interceptors.base.CommandInterceptor;
import org.jboss.cache.loader.DummyInMemoryCacheLoader;
import org.jboss.cache.loader.DummySharedInMemoryCacheLoader;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.marshall.MethodCall;
import org.jboss.cache.transaction.DummyTransactionManagerLookup;
import org.jboss.cache.transaction.GlobalTransaction;
import org.jboss.cache.transaction.TransactionSetup;
import org.jboss.cache.xml.XmlHelper;
import org.jgroups.Address;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import org.w3c.dom.Element;

import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

/**
 * @author manik
 */
@Test(groups = "functional")
public class AbstractOptimisticTestCase
{
   // some test data shared among all the test cases
   protected Fqn<String> fqn = Fqn.fromString("/blah");
   protected String key = "myKey", value = "myValue";

   protected CacheSPI<Object, Object> createCacheUnstarted() throws Exception
   {
      return createCacheUnstarted(true);
   }

   protected CacheSPI<Object, Object> createCacheUnstarted(boolean optimistic) throws Exception
   {

      CacheSPI<Object, Object> cache = (CacheSPI<Object, Object>) new DefaultCacheFactory<Object, Object>().createCache(UnitTestCacheConfigurationFactory.createConfiguration(Configuration.CacheMode.LOCAL), false);
      if (optimistic) cache.getConfiguration().setNodeLockingScheme("OPTIMISTIC");
      return cache;
   }

   protected CacheSPI<Object, Object> createCacheWithListener() throws Exception
   {
      return createCacheWithListener(new TestListener());
   }

   protected CacheSPI<Object, Object> createCacheWithListener(Object listener) throws Exception
   {
      CacheSPI<Object, Object> cache = createCacheUnstarted();
      cache.create();
      cache.start();
      cache.getNotifier().addCacheListener(listener);
      return cache;
   }

   /**
    * Returns a tree cache with passivation disabled in the loader.
    */
   protected CacheSPI<Object, Object> createCacheWithLoader() throws Exception
   {
      return createCacheWithLoader(false);
   }

   protected void setupTransactions(CacheSPI cache, Transaction tx)
   {
      cache.getInvocationContext().setTransaction(tx);
      GlobalTransaction gtx = cache.getCurrentTransaction(tx, true);
      cache.getInvocationContext().setGlobalTransaction(gtx);
      cache.getInvocationContext().setTransactionEntry(cache.getTransactionTable().get(gtx));
   }

   protected CacheLoaderConfig getCacheLoaderConfig(boolean shared, boolean passivation) throws Exception
   {
      String xml = "            <config>\n" +
            "                <passivation>" + passivation + "</passivation>\n" +
            "                <preload></preload>\n" +
            "                <shared>" + shared + "</shared>\n" +
            "                <cacheloader>\n" +
            "                    <class>" + (shared ? DummySharedInMemoryCacheLoader.class.getName() : DummyInMemoryCacheLoader.class.getName()) + "</class>\n" +
            "                    <properties>\n" +
            "                    </properties>\n" +
            "                    <async>false</async>\n" +
            "                    <fetchPersistentState>" + (!shared) + "</fetchPersistentState>\n" +
            "                    <ignoreModifications>false</ignoreModifications>\n" +
            "                </cacheloader>\n" +
            "            </config>";
      Element element = XmlHelper.stringToElement(xml);
      return XmlConfigurationParser.parseCacheLoaderConfig(element);
   }

   protected CacheSPI<Object, Object> createCacheWithLoader(boolean passivationEnabled) throws Exception
   {
      CacheSPI<Object, Object> cache = createCacheUnstarted();
      Configuration c = cache.getConfiguration();
      c.setCacheLoaderConfig(getCacheLoaderConfig(true, passivationEnabled));
      cache.create();
      cache.start();
      return cache;
   }


   protected CacheSPI<Object, Object> createCache() throws Exception
   {
      CacheSPI<Object, Object> cache = createCacheUnstarted();
      cache.create();
      cache.start();
      return cache;
   }

   protected void destroyCache(Cache<Object, Object> c)
   {
      TestingUtil.killCaches(c);
   }


   protected CacheSPI createPessimisticCache() throws Exception
   {
      CacheSPI cache = (CacheSPI) new DefaultCacheFactory().createCache(false);
      Configuration c = cache.getConfiguration();

      c.setClusterName("name");
      c.setStateRetrievalTimeout(5000);
      c.setCacheMode(Configuration.CacheMode.REPL_SYNC);
      c.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
      c.setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
      cache.create();
      cache.start();


      return cache;
   }

   protected CacheSPI createPessimisticCacheLocal() throws Exception
   {
      CacheSPI cache = (CacheSPI) new DefaultCacheFactory().createCache(false);
      Configuration c = cache.getConfiguration();

      c.setClusterName("name");
      c.setStateRetrievalTimeout(5000);

      c.setCacheMode(Configuration.CacheMode.LOCAL);
      c.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
      c.setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
      cache.create();
      cache.start();


      return cache;
   }

   protected CacheSPI createReplicatedCache(Configuration.CacheMode mode) throws Exception
   {
      return createReplicatedCache("test", mode);
   }

   protected CacheSPI createReplicatedCache(String name, Configuration.CacheMode mode) throws Exception
   {
      return createReplicatedCache(name, mode, true);
   }

   protected CacheSPI<Object, Object> createReplicatedCache(String name, Configuration.CacheMode mode, boolean start) throws Exception
   {
      CacheSPI<Object, Object> cache = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache(false);
      Configuration c = cache.getConfiguration();

      c.setClusterName(name);
      c.setStateRetrievalTimeout(5000);
      c.setCacheMode(mode);
      if (mode == Configuration.CacheMode.REPL_SYNC)
      {
         // make sure commits and rollbacks are sync as well
         c.setSyncCommitPhase(true);
         c.setSyncRollbackPhase(true);
      }
      c.setNodeLockingScheme("OPTIMISTIC");
      c.setTransactionManagerLookupClass(TransactionSetup.getManagerLookup());

      if (start)
      {
         cache.create();
         cache.start();
      }

      return cache;
   }

   protected CacheSPI createReplicatedCacheWithLoader(boolean shared, Configuration.CacheMode cacheMode) throws Exception
   {
      return createReplicatedCacheWithLoader("temp-loader", shared, cacheMode);
   }

   protected CacheSPI<Object, Object> createReplicatedCacheWithLoader(boolean shared) throws Exception
   {
      return createReplicatedCacheWithLoader("temp-loader", shared, Configuration.CacheMode.REPL_SYNC);
   }

   protected CacheSPI createReplicatedCacheWithLoader(String name, boolean shared) throws Exception
   {
      return createReplicatedCacheWithLoader(name, shared, Configuration.CacheMode.REPL_SYNC);
   }

   protected CacheSPI<Object, Object> createReplicatedCacheWithLoader(String name, boolean shared, Configuration.CacheMode cacheMode) throws Exception
   {
      CacheSPI<Object, Object> cache = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache(false);
      Configuration c = cache.getConfiguration();
      c.setClusterName(name);
      c.setStateRetrievalTimeout(5000);
      c.setCacheMode(cacheMode);
      c.setSyncCommitPhase(true);
      c.setSyncRollbackPhase(true);
      c.setNodeLockingScheme("OPTIMISTIC");
      c.setTransactionManagerLookupClass(DummyTransactionManagerLookup.class.getName());
      c.setCacheLoaderConfig(getCacheLoaderConfig(shared, false));

      cache.create();
      cache.start();
      return cache;
   }

   protected Random random = new Random();

   protected void randomSleep(int min, int max)
   {
      long l = -1;
      while (l < min) l = random.nextInt(max);
      TestingUtil.sleepThread(l);
   }

   @AfterMethod(alwaysRun = true)
   public void tearDown()
   {
      TransactionManager mgr = TransactionSetup.getManager();
      try
      {
         if (mgr.getTransaction() != null)
         {
            mgr.rollback();
         }
      }
      catch (SystemException e)
      {
         // do nothing
      }
   }

   protected void setAlteredInterceptorChain(CommandInterceptor newLast, CacheSPI<Object, Object> spi)
   {
      spi.removeInterceptor(CacheMgmtInterceptor.class);
      spi.removeInterceptor(NotificationInterceptor.class);
      spi.removeInterceptor(OptimisticLockingInterceptor.class);
      spi.removeInterceptor(OptimisticValidatorInterceptor.class);
      spi.removeInterceptor(CallInterceptor.class);
      spi.addInterceptor(newLast, OptimisticNodeInterceptor.class);
   }

   public abstract class ExceptionThread extends Thread
   {
      protected Exception exception;

      public void setException(Exception e)
      {
         exception = e;
      }

      public Exception getException()
      {
         return exception;
      }
   }

   protected List<ReversibleCommand> injectDataVersion(List<ReversibleCommand> modifications)
   {
      List<MethodCall> newList = new LinkedList<MethodCall>();
      for (ReversibleCommand c : modifications)
      {
         if (c instanceof VersionedDataCommand)
         {
            ((VersionedDataCommand) c).setDataVersion(new DefaultDataVersion());
         }
//         Object[] oa = c.getArgs();
//         Object[] na = new Object[oa.length + 1];
//         System.out.println("*** " + oa.length);
//         System.arraycopy(oa, 0, na, 0, oa.length);
//         na[oa.length] = new DefaultDataVersion();
//         newList.add(MethodCallFactory.create(MethodDeclarations.getVersionedMethodId(c.getMethodId()), na));
      }
      return modifications;
   }

   protected class DummyAddress implements Address
   {
      private static final long serialVersionUID = -2628268587640985944L;

      public int compareTo(Object arg0)
      {
         return 0;
      }

      public void readFrom(DataInputStream
            arg0)
      {
      }

      public void writeTo(DataOutputStream
            arg0)
      {
      }

      public void readExternal(ObjectInput
            arg0)
      {
      }

      public void writeExternal(ObjectOutput
            arg0)
      {
      }

      public int size()
      {
         return 0;
      }

      public boolean isMulticastAddress()
      {
         return false;
      }

   }

}
