/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.cache.pojo.optimistic;

import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import javax.transaction.SystemException;
import javax.transaction.TransactionManager;

import org.jboss.cache.CacheSPI;
import org.jboss.cache.Fqn;
import org.jboss.cache.config.CacheLoaderConfig;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.factories.XmlConfigurationParser;
import org.jboss.cache.interceptors.Interceptor;
import org.jboss.cache.interceptors.InvocationContextInterceptor;
import org.jboss.cache.interceptors.OptimisticCreateIfNotExistsInterceptor;
import org.jboss.cache.interceptors.OptimisticNodeInterceptor;
import org.jboss.cache.interceptors.OptimisticReplicationInterceptor;
import org.jboss.cache.interceptors.TxInterceptor;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.marshall.MethodCall;
import org.jboss.cache.marshall.MethodCallFactory;
import org.jboss.cache.marshall.MethodDeclarations;
import org.jboss.cache.misc.TestingUtil;
import org.jboss.cache.optimistic.DefaultDataVersion;
import org.jboss.cache.optimistic.TestListener;
import org.jboss.cache.pojo.PojoCache;
import org.jboss.cache.pojo.PojoCacheFactory;
import org.jboss.cache.transaction.DummyTransactionManager;
import org.jboss.cache.xml.XmlHelper;
import org.testng.annotations.AfterMethod;
import org.w3c.dom.Element;

/**
 * Base test for optimistic locking. Copied from Cache counterpart.
 */
public abstract class AbstractOptimisticTestCase
{
   private int instanceNumber;

   // some test data shared among all the test cases
   protected Fqn<String> fqn = Fqn.fromString("/blah");
   protected String key = "myKey", value = "myValue";

   protected String getTempDir()
   {
      return getTempDir("tempdir");
   }

   private String getTempDir(String name)
   {
      String tempDir = System.getProperty("java.io.tmpdir", "/tmp");
      tempDir = tempDir + File.separator + name;
      System.out.println("tmpdir property: " + System.getProperty("java.io.tmpdir"));
      System.out.println("Attempting to create dir [" + tempDir + "]");
      File tempDirFile = new File(tempDir);
      if (!tempDirFile.exists())
      {
         tempDirFile.mkdirs();
      }
      return tempDir;
   }

   protected PojoCache createCacheUnstarted() throws Exception
   {
      return createCacheUnstarted(true);
   }

   protected PojoCache createCacheUnstarted(boolean optimistic) throws Exception
   {
      Configuration c = new Configuration();
      if (optimistic) c.setNodeLockingScheme("OPTIMISTIC");

      c.setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
      c.setCacheMode("LOCAL");
      PojoCache cache = PojoCacheFactory.createCache(c, false);
      return cache;
   }

   protected PojoCache createCacheWithListener() throws Exception
   {
      return createCacheWithListener(new TestListener());
   }

   protected PojoCache createCacheWithListener(Object listener) throws Exception
   {
      PojoCache cache = createCacheUnstarted();
      cache.create();
      cache.start();
      cache.getCache().addCacheListener(listener);
      return cache;
   }

   /**
    * Returns a tree cache with passivation disabled in the loader.
    *
    * @return
    * @throws Exception
    */
   protected PojoCache createCacheWithLoader() throws Exception
   {
      return createCacheWithLoader(false);
   }

   protected CacheLoaderConfig getCacheLoaderConfig(boolean shared, String filename, boolean passivation) throws Exception
   {
      String xml = "            <config>\n" +
              "                <passivation>" + passivation + "</passivation>\n" +
              "                <preload></preload>\n" +
              "                <shared>" + shared + "</shared>\n" +
              "                <cacheloader>\n" +
              "                    <class>org.jboss.cache.loader.FileCacheLoader</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 PojoCache createCacheWithLoader(boolean passivationEnabled) throws Exception
   {
      PojoCache cache = createCacheUnstarted();
      Configuration c = cache.getCache().getConfiguration();
      c.setCacheLoaderConfig(getCacheLoaderConfig(true, getTempDir(), passivationEnabled));
      cache.create();
      cache.start();
      return cache;
   }

   protected PojoCache createCache() throws Exception
   {
      PojoCache cache = createCacheUnstarted();
      cache.create();
      cache.start();
      return cache;
   }

   protected void destroyCache(PojoCache c)
   {
      c.stop();
      c.destroy();
   }


   protected PojoCache createPessimisticCache() throws Exception
   {
      Configuration c = new Configuration();
      c.setClusterName("name");
      c.setStateRetrievalTimeout(5000);
      c.setClusterConfig(getDefaultProperties());
      c.setCacheMode(Configuration.CacheMode.REPL_SYNC);
      c.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
      c.setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");

      PojoCache cache = PojoCacheFactory.createCache(c, false);
      cache.create();
      cache.start();


      return cache;
   }

   protected PojoCache createPessimisticCacheLocal() throws Exception
   {
      Configuration c = new Configuration();

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

      c.setCacheMode(Configuration.CacheMode.LOCAL);
      c.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
      c.setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
      PojoCache cache = PojoCacheFactory.createCache(c, false);
      cache.create();
      cache.start();

      return cache;
   }

   protected String getDefaultProperties()
   {
      return "UDP(mcast_addr=228.1.2.3;mcast_port=48866;ip_ttl=32;" +
              "mcast_send_buf_size=150000;mcast_recv_buf_size=80000;loopback=true;ip_mcast=true;bind_addr=127.0.0.1):" +
              "PING(timeout=1000;num_initial_members=2):" +
              "MERGE2(min_interval=5000;max_interval=10000):" +
              "FD_SOCK:" +
              "VERIFY_SUSPECT(timeout=1500):" +
              "pbcast.NAKACK(gc_lag=50;max_xmit_size=8192;retransmit_timeout=600,1200,2400,4800):" +
              "UNICAST(timeout=600,1200,2400,4800):" +
              "pbcast.STABLE(desired_avg_gossip=20000):" +
              "FRAG(frag_size=8192;down_thread=false;up_thread=false):" +
              "pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;" +
              "shun=false;print_local_addr=true):" +
              "pbcast.STATE_TRANSFER";
   }

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

   protected PojoCache createReplicatedCache(String name, Configuration.CacheMode mode) throws Exception
   {
      Configuration c = new Configuration();

      c.setClusterName(name);
      c.setStateRetrievalTimeout(5000);
      c.setClusterConfig(getDefaultProperties());
      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("org.jboss.cache.transaction.DummyTransactionManagerLookup");
      PojoCache cache = PojoCacheFactory.createCache(c, false);
      cache.create();
      cache.start();

      return cache;
   }

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

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

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

   protected PojoCache createReplicatedCacheWithLoader(String name, boolean shared, Configuration.CacheMode cacheMode) throws Exception
   {
      Configuration c = new Configuration();
      c.setClusterName(name);
      c.setStateRetrievalTimeout(5000);
      c.setClusterConfig(getDefaultProperties());
      c.setCacheMode(cacheMode);
      c.setSyncCommitPhase(true);
      c.setSyncRollbackPhase(true);
      c.setNodeLockingScheme("OPTIMISTIC");
      c.setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
      c.setCacheLoaderConfig(getCacheLoaderConfig(shared, shared ? getTempDir(name + "-shared") : getTempDir(name + instanceNumber++), false));

      PojoCache cache = PojoCacheFactory.createCache(c, false);
      cache.create();
      cache.start();
      return cache;
   }

   protected Random random;

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

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

   protected Interceptor getAlteredInterceptorChain(Interceptor newLast, CacheSPI<Object, Object> spi, boolean replicated)
   {
      Interceptor ici = new InvocationContextInterceptor();
      ici.setCache(spi);

      Interceptor txInterceptor = new TxInterceptor();
      txInterceptor.setCache(spi);

      Interceptor replicationInterceptor = new OptimisticReplicationInterceptor();
      replicationInterceptor.setCache(spi);

      Interceptor createInterceptor = new OptimisticCreateIfNotExistsInterceptor();
      createInterceptor.setCache(spi);

      Interceptor nodeInterceptor = new OptimisticNodeInterceptor();
      nodeInterceptor.setCache(spi);

      ici.setNext(txInterceptor);
      if (replicated)
      {
         txInterceptor.setNext(replicationInterceptor);
         replicationInterceptor.setNext(createInterceptor);
      }
      else
      {
         txInterceptor.setNext(createInterceptor);
      }
      createInterceptor.setNext(nodeInterceptor);
      nodeInterceptor.setNext(newLast);

      return ici;
   }

   public abstract class ExceptionThread extends Thread
   {
      protected Exception exception;

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

      public Exception getException()
      {
         return exception;
      }
   }

   protected List<MethodCall> injectDataVersion(List<MethodCall> modifications)
   {
      List<MethodCall> newList = new LinkedList<MethodCall>();
      for (MethodCall c : modifications)
      {
         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(c.getMethodId(), na));
      }
      return newList;
   }

}
