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

package org.jboss.cache.aop;


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.aop.PojoCache;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.lock.LockStrategyFactory;
import org.jboss.cache.lock.UpgradeException;
import org.jboss.cache.lock.TimeoutException;
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.*;

/**
 * Replicated mode performance test for PojoCache to test out contention.
 *
 * @version $Revision: 1524 $
 * @author<a href="mailto:bwang@jboss.org">Ben Wang</a> May 20 2003
 */
public class ReplicatedSyncMapContentionAopTest extends TestCase
{
   PojoCache cache1_, cache2_;
   int cachingMode_ = TreeCache.REPL_ASYNC;
   final static Properties p_;
//   final static Log log_=LogFactory.getLog(LocalPerfAopTest.class);
   String oldFactory_ = null;
   final String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";
   DummyTransactionManager tm_;
   Map[] proxyMaps_ = null;
   ArrayList nodeList_;
   static final int depth_ = 1;
   static final int children_ = 1;
   static final int mapValueSize_ = 5;
   static final String seed1_ = "This is a test. ";
   static final String seed2_ = "THAT is a TEST. ";
   StringBuffer originalStrBuf_;
   StringBuffer newStrBuf_;
   static Throwable ex_ = null;

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

   public ReplicatedSyncMapContentionAopTest(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();
      cache1_ = initCaches();
      cache2_ = initCaches();
      tm_ = new DummyTransactionManager();
      Thread.sleep(5000);

      originalStrBuf_ = new StringBuffer();
      newStrBuf_ = new StringBuffer();
      generateString();
      log("ReplicatedSyncPerfAopTest: cacheMode=ReplSync");
      nodeList_ = nodeGen(depth_, children_);
      populateNode();
   }

   private void generateString()
   {
      int length = seed1_.length();
      boolean isTrue = false;
      while (originalStrBuf_.length() < mapValueSize_) {
         originalStrBuf_.append(seed1_);
         newStrBuf_.append(seed2_);
      }
   }

   private void populateNode() throws Exception
   {
      // Formating
      DecimalFormat form = new DecimalFormat("#.00");
      FieldPosition fieldPos = new FieldPosition(0);
      StringBuffer dumbStr = new StringBuffer();
      proxyMaps_ = new Map[nodeList_.size()];

      long time1 = System.currentTimeMillis();
      int nOps = 0;
      for (int i = 0; i < nodeList_.size(); i++) {
//            String key = Integer.toString(i);
         // put the cache in the aop first
         Map map = populateMap();
         cache1_.putObject((String) nodeList_.get(i), map);
         proxyMaps_[i] = (Map) cache1_.getObject((String) nodeList_.get(i));
      }

      nOps = nodeList_.size();
      long time2 = System.currentTimeMillis();
      double d = (double) (time2 - time1) / nOps;
      log("Time elapsed for one putObject and getObject entry is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");
   }

   private Map populateMap()
   {
      Map map = new HashMap();
      for (int i = 0; i < nodeList_.size(); i++) {
         String key = Integer.toString(i) + "aop";
         String value = originalStrBuf_.toString();
         map.put(key, value);
      }
      return map;
   }

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

      DummyTransactionManager.destroy();
      destroyCaches();

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

      proxyMaps_ = null;
   }

   PojoCache initCaches() throws Exception
   {
      PojoCache cache_;
      cache_ = new PojoCache();
      PropertyConfigurator config = new PropertyConfigurator();
      config.configure(cache_, "META-INF/replSync-service.xml"); // read in generic replSync xml
      cache_.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
//      cache_.setCacheMode(cachingMode_);
      cache_.startService();
      return cache_;
//        org.jgroups.log.Trace.init();
   }

   void destroyCaches() throws Exception
   {
   }

   public void testDummy()
   {

   }

   public void testAll() throws Exception
   {

      log("=== Start ===");
//        try { Thread.sleep(5000); } catch (Exception ex) {};

      boolean hasTx = true;

      // Step 1. Add entries to the cache
      Loader la = new Loader(nodeList_, proxyMaps_, hasTx, cache1_);
      Loader lb = new Loader(nodeList_, proxyMaps_, hasTx, cache2_);

//      la.start();
//      lb.start();

//      la.join();
//      lb.join();
      log("=== End ===");
   }

   protected void setLevelRW()
   {
      log("set lock level to RWUpgrade ...");
      LockStrategyFactory.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
   }

   protected void XsetLevelSerial()
   {
      log("set lock level to SimpleLock ...");
      LockStrategyFactory.setIsolationLevel(IsolationLevel.SERIALIZABLE);
   }


   /**
    * 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.
    * This strucutre is used to add, get, and remove for 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(ReplicatedSyncMapContentionAopTest.class);
   }

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

   public static class Loader extends Thread
   {
      List nodeList_;
      Map[] proxyMaps_;
      boolean hasTx;
      PojoCache cache_;

      public Loader(List nodeList, Map[] proxyMaps, boolean hasTx, PojoCache cache)
      {
         nodeList_ = nodeList;
         proxyMaps_ = proxyMaps;
         this.hasTx = hasTx;
         cache_ = cache;
      }

      public void run()
      {
         long counter = 0;
         while(ex_ == null && counter++ < 200)
         {
            long time = System.currentTimeMillis();
            try {
               Thread.sleep(10000);
            } catch (InterruptedException e) {

            }

            try {
               _put();
//               _get();
            } catch (Exception e) {
               if(e.getCause() instanceof UpgradeException ||
                       e.getCause() instanceof TimeoutException)
               {
                  System.err.println("TimeoutException: "+ e.getCause());
                  continue;
               }
               ex_ = e;
               throw new IllegalStateException("Exception in put(): "+e);
            }
            System.out.println("Loop counter: " +counter++);
         }
      }

      private void _put() throws Exception
      {
         UserTransaction tx = null;
         if (hasTx) {
            tx = (UserTransaction) new InitialContext(p_).lookup("UserTransaction");
         }

         String value = "Test";
         for (int i = 0; i < nodeList_.size(); i++) {
            String key = Integer.toString(i) + "aop";
            if (hasTx) {
               tx.begin();
               proxyMaps_[i].put(key, value);
               tx.commit();
            } else {
               proxyMaps_[i].put(key, value);
            }
         }
      }

      private void _get() throws Exception
      {
         UserTransaction tx = null;
         if (hasTx) {
            tx = (UserTransaction) new InitialContext(p_).lookup("UserTransaction");
         }

         String value = "Test";
         for (int i = 0; i < nodeList_.size(); i++) {
            String key = Integer.toString(i) + "aop";
            proxyMaps_[i].get(key);
         }
      }

   }
}
