/*
 *
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jboss.cache;


import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.TreeCache;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.transaction.DummyTransactionManager;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.transaction.UserTransaction;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.util.ArrayList;
import java.util.Properties;

/**
 * Replicated asynchronous mode performance test for transactional TreeCache
 *
 * @version $Revision: 2015 $
 * @author <a href="mailto:bwang@jboss.org">Ben Wang</a> May 20 2003
 */
public class ReplicatedAsyncPerfTest extends TestCase
{
   TreeCache cache1_, cache2_, cache3_;
   int cachingMode_ = TreeCache.REPL_ASYNC;
   final String groupName_ = "TreeCacheTestGroup";
   final static Properties p_;
//   final static Log log_=LogFactory.getLog(ReplicatedAsyncPerfAopTest.class);
   String oldFactory_ = null;
   final String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";

   ArrayList nodeList_;
   static final int depth_ = 3;
   static final int children_ = 4;
   DummyTransactionManager tm_;

   static
   {
      p_ = new Properties();
      p_.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.cache.transaction.DummyContextFactory");
   }

   public ReplicatedAsyncPerfTest(String name)
   {
      super(name);
   }

   public void setUp() throws Exception
   {
      super.setUp();

      oldFactory_ = System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
      System.setProperty(Context.INITIAL_CONTEXT_FACTORY, FACTORY);

      DummyTransactionManager.getInstance();
      nodeList_ = nodeGen(depth_, children_);
      tm_ = new DummyTransactionManager();

      log("ReplicatedAsyncPerfAopTest: cacheMode=REPL_ASYNC");
   }

   public void tearDown() throws Exception
   {
      super.tearDown();

      destroyCache(cache1_);
      destroyCache(cache2_);
      destroyCache(cache3_);

      DummyTransactionManager.destroy();

      if (oldFactory_ != null) {
         System.setProperty(Context.INITIAL_CONTEXT_FACTORY, oldFactory_);
         oldFactory_ = null;
      }

   }

   TreeCache createCache(IsolationLevel level) throws Exception
   {
      TreeCache cache = new TreeCache();
      PropertyConfigurator config = new PropertyConfigurator();
      config.configure(cache, "META-INF/replAsync-service.xml");
      cache.setIsolationLevel(level);
      System.out.println("cache isolation level is " + cache.getIsolationLevel());
      return cache;
   }

   private void destroyCache(TreeCache cache) throws Exception
   {
      if(cache != null) {
         cache.remove("/");
         cache.stopService();
      }
   }


   public void testOneCacheTx_RWLock() throws Exception
   {
      oneCacheTx(IsolationLevel.REPEATABLE_READ);
   }

   public void testOneCacheTx_SimpleLock() throws Exception
   {
      oneCacheTx(IsolationLevel.SERIALIZABLE);
   }

   protected void oneCacheTx(IsolationLevel level) throws Exception
   {
      log("=== 1 cache with transaction (no concurrent access) ===");
      cache1_ = createCache(level);
      cache1_.setTransactionManagerLookupClass("org.jboss.cache.JBossTransactionManagerLookup");
      cache1_.startService();

      // Formating
      DecimalFormat form = new DecimalFormat("#.00");
      FieldPosition fieldPos = new FieldPosition(0);
      StringBuffer dumbStr = new StringBuffer();
      boolean hasTx = true;

      // Step 1. Add entries to the cache
      long time1 = System.currentTimeMillis();
      int nOps = _add(cache1_, hasTx);
      long time2 = System.currentTimeMillis();
      double d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _add is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");
      dumbStr = new StringBuffer();

      // Step 2. Query the cache
      time1 = System.currentTimeMillis();
      nOps = _get(cache1_, hasTx);
      time2 = System.currentTimeMillis();
      d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _get is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");
      dumbStr = new StringBuffer();

      // Step 3. Remove entries from the cache
      time1 = System.currentTimeMillis();
      nOps = _remove(cache1_, hasTx);
      time2 = System.currentTimeMillis();
      d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _remove is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");

      destroyCache(cache1_);
   }

   public void test2CachesTx_RWLock() throws Exception
   {
      twoCachesTx(IsolationLevel.REPEATABLE_READ);
   }

   public void test2CachesTx_SimpleLock() throws Exception
   {
      twoCachesTx(IsolationLevel.SERIALIZABLE);
   }

   protected void twoCachesTx(IsolationLevel level) throws Exception
   {
      log("=== 2 caches with transaction (no concurrent access) ===");
      cache1_ = createCache(level);
      cache2_ = createCache(level);
      cache1_.setTransactionManagerLookupClass("org.jboss.cache.JBossTransactionManagerLookup");
      cache2_.setTransactionManagerLookupClass("org.jboss.cache.JBossTransactionManagerLookup");
      cache1_.startService();
      cache2_.startService();

      // Formating
      DecimalFormat form = new DecimalFormat("#.00");
      FieldPosition fieldPos = new FieldPosition(0);
      StringBuffer dumbStr = new StringBuffer();
      boolean hasTx = true;

      // Step 1. Add entries to the cache
      long time1 = System.currentTimeMillis();
      int nOps = _add(cache1_, hasTx);
      long time2 = System.currentTimeMillis();
      double d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _add is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");
      dumbStr = new StringBuffer();

      // Step 2. Query the cache
      time1 = System.currentTimeMillis();
      nOps = _get(cache1_, hasTx);
      time2 = System.currentTimeMillis();
      d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _get is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");
      dumbStr = new StringBuffer();

      // Step 3. Remove entries from the cache
      time1 = System.currentTimeMillis();
      nOps = _remove(cache2_, hasTx);
      time2 = System.currentTimeMillis();
      d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _remove is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");

      destroyCache(cache1_);
      destroyCache(cache2_);
   }

   public void test2CachesTxWithReplQueue_RWLock() throws Exception
   {
      twoCachesTxWithReplQueue(IsolationLevel.REPEATABLE_READ);
   }

   public void test2CachesTxWithReplQueue_SimpleLock() throws Exception
   {
      twoCachesTxWithReplQueue(IsolationLevel.SERIALIZABLE);
   }

   protected void twoCachesTxWithReplQueue(IsolationLevel level) throws Exception
   {
      log("=== 2 caches with transaction (no concurrent access) using repl queue ===");
      cache1_ = createCache(level);
      cache2_ = createCache(level);

      cache1_.setUseReplQueue(true);
      cache1_.setReplQueueInterval(5000);
      cache1_.setReplQueueMaxElements(100);

      cache2_.setUseReplQueue(true);
      cache2_.setReplQueueInterval(5000);
      cache2_.setReplQueueMaxElements(100);

      cache1_.setTransactionManagerLookupClass("org.jboss.cache.JBossTransactionManagerLookup");
      cache2_.setTransactionManagerLookupClass("org.jboss.cache.JBossTransactionManagerLookup");
      cache1_.startService();
      cache2_.startService();

      // Formating
      DecimalFormat form = new DecimalFormat("#.00");
      FieldPosition fieldPos = new FieldPosition(0);
      StringBuffer dumbStr = new StringBuffer();
      boolean hasTx = true;

      // Step 1. Add entries to the cache
      long time1 = System.currentTimeMillis();
      int nOps = _add(cache1_, hasTx);
      long time2 = System.currentTimeMillis();
      double d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _add is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");
      dumbStr = new StringBuffer();

      // Step 2. Query the cache
      time1 = System.currentTimeMillis();
      nOps = _get(cache1_, hasTx);
      time2 = System.currentTimeMillis();
      d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _get is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");
      dumbStr = new StringBuffer();

      // Step 3. Remove entries from the cache
      time1 = System.currentTimeMillis();
      nOps = _remove(cache2_, hasTx);
      time2 = System.currentTimeMillis();
      d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _remove is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");

      destroyCache(cache1_);
      destroyCache(cache2_);
   }

   public void test3CachesTx_RWLock() throws Exception
   {
      threeCachesTx(IsolationLevel.REPEATABLE_READ);
   }

   public void test3CachesTx_SimpleLock() throws Exception
   {
      threeCachesTx(IsolationLevel.SERIALIZABLE);
   }

   protected void threeCachesTx(IsolationLevel level) throws Exception
   {
      log("=== 3 caches with transaction (no concurrent access) ===");
      cache1_ = createCache(level);
      cache2_ = createCache(level);
      cache3_ = createCache(level);
      cache1_.setTransactionManagerLookupClass("org.jboss.cache.JBossTransactionManagerLookup");
      cache2_.setTransactionManagerLookupClass("org.jboss.cache.JBossTransactionManagerLookup");
      cache3_.setTransactionManagerLookupClass("org.jboss.cache.JBossTransactionManagerLookup");
      cache1_.startService();
      cache2_.startService();
      cache3_.startService();

      // Formating
      DecimalFormat form = new DecimalFormat("#.00");
      FieldPosition fieldPos = new FieldPosition(0);
      StringBuffer dumbStr = new StringBuffer();
      boolean hasTx = true;

      // Step 1. Add entries to the cache
      long time1 = System.currentTimeMillis();
      int nOps = _add(cache1_, hasTx);
      long time2 = System.currentTimeMillis();
      double d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _add is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");
      dumbStr = new StringBuffer();

      // Step 2. Query the cache
      time1 = System.currentTimeMillis();
      nOps = _get(cache2_, hasTx);
      time2 = System.currentTimeMillis();
      d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _get is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");
      dumbStr = new StringBuffer();

      // Step 3. Remove entries from the cache
      time1 = System.currentTimeMillis();
      nOps = _remove(cache3_, hasTx);
      time2 = System.currentTimeMillis();
      d = (double) (time2 - time1) / nOps;
      log("Time elapsed for _remove is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");

      destroyCache(cache1_);
      destroyCache(cache2_);
      destroyCache(cache3_);
   }

   private int _add(TreeCache cache, boolean hasTx) throws Exception
   {
      UserTransaction tx = null;
      if (hasTx) {
         tx = (UserTransaction) new InitialContext(p_).lookup("UserTransaction");
      }

      for (int i = 0; i < nodeList_.size(); i++) {
         String key = Integer.toString(i);
         String value = Integer.toString(i);
         if (hasTx) {
            tx.begin();
            cache.put((String) nodeList_.get(i), key, value);
            tx.commit();
         } else {
            cache.put((String) nodeList_.get(i), key, value);
         }
      }

      return nodeList_.size();
   }

   private int _get(TreeCache cache, boolean hasTx) throws Exception
   {
      UserTransaction tx = null;
      if (hasTx) {
         tx = (UserTransaction) new InitialContext(p_).lookup("UserTransaction");
      }

      for (int i = 0; i < nodeList_.size(); i++) {
         String key = Integer.toString(i);
         if (hasTx) {
            tx.begin();
            cache.get((String) nodeList_.get(i), key);
            tx.commit();
         } else {
            cache.get((String) nodeList_.get(i), key);
         }
      }

      return nodeList_.size();
   }

   private int _remove(TreeCache cache, boolean hasTx) throws Exception
   {
      UserTransaction tx = null;
      if (hasTx) {
         tx = (UserTransaction) new InitialContext(p_).lookup("UserTransaction");
      }

      for (int i = nodeList_.size()-1; i >= 0; i--) {
         String key = Integer.toString(i);
         if (hasTx) {
            tx.begin();
            cache.remove((String) nodeList_.get(i), key);
            tx.commit();
         } else {
            cache.remove((String) nodeList_.get(i), key);
         }
      }

      return nodeList_.size();
   }

   /**
    * Generate the tree nodes quasi-exponentially. I.e., depth is the level
    * of the hierarchy and children is the number of children under each node.
    */
   private ArrayList nodeGen(int depth, int children)
   {
      ArrayList strList = new ArrayList();
      ArrayList oldList = new ArrayList();
      ArrayList newList = new ArrayList();

      oldList.add("/");
      newList.add("/");
      strList.add("/");

      while (depth > 0) {
         // Trying to produce node name at this depth.
         newList = new ArrayList();
         for (int i = 0; i < oldList.size(); i++) {
            for (int j = 0; j < children; j++) {
               String tmp = (String) oldList.get(i);
               tmp += Integer.toString(j);
               if (depth != 1) tmp += "/";
               newList.add(tmp);
            }
         }
         strList.addAll(newList);
         oldList = newList;
         depth--;
      }

      log("Nodes generated: " + strList.size());
      return strList;
   }

   public static Test suite() throws Exception
   {
      return new TestSuite(ReplicatedAsyncPerfTest.class);
   }

   private void log(String str)
   {
//     System.out.println(this.getClass().getName() +": " +str);
      System.out.println(str);
   }

}
