/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt 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.test.cache.perf.basic;


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.lock.LockStrategyFactory;
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.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * Local mode performance test for TreeCache.
 *
 * @version $Revision: 1.1.6.2 $
 * @author<a href="mailto:bwang@jboss.org">Ben Wang</a> May 20 2003
 */
public class ReplicatedSyncMapPerfTestCase extends TestCase
{
   TreeCache cache_;
   int cachingMode_ = TreeCache.LOCAL;
   final static Properties p_;
//   final static Logger log_ = Logger.getLogger(LocalPerfAopTest.class);
   String oldFactory_ = null;
   final String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";
   DummyTransactionManager tm_;
   Map map_ = new HashMap();
//    final int SIZE = 100;
//    final String FQN = "/regular/test";
//    final String KEY = Integer.toString((SIZE/2));
   ArrayList nodeList_;
   static final int depth_ = 3;
   static final int children_ = 4;
   static final int mapValueSize_ = 100;
   static final String seed1_ = "This is a test. ";
   static final String seed2_ = "THAT is a TEST. ";
   StringBuffer originalStrBuf_;
   StringBuffer newStrBuf_;

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

   public ReplicatedSyncMapPerfTestCase(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();
      initCaches(TreeCache.LOCAL);
      tm_ = new DummyTransactionManager();

      originalStrBuf_ = new StringBuffer();
      newStrBuf_ = new StringBuffer();
      generateString();
      log("ReplicatedasyncMapPerfAopTest: cacheMode=replSync");
      populateMap();
      nodeList_ = nodeGen(depth_, children_);
   }

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

   private void populateMap()
   {
      int depth = depth_;
      int children = children_;
      int total = 0;
      while (depth > 0) {
         for (int i = 0; i < total; i++) {
            for (int j = 0; j < children; j++) {
               total++;
            }
         }
         depth--;
      }

      for (int i = 0; i < total; i++) {
         String key = Integer.toString(i);
         String value = originalStrBuf_.toString();
         map_.put(key, value);
      }
   }

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

      DummyTransactionManager.destroy();
      destroyCaches();

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

      map_.clear();
   }

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

   void destroyCaches() throws Exception
   {
      cache_.stopService();
      cache_ = null;
   }

   public void testAll() throws Exception
   {
      try {
         Thread.sleep(5000);
      } catch (Exception ex) {
      }
      log("=== Transaction ===");
      // Formating
      DecimalFormat form = new DecimalFormat("#.00");
      FieldPosition fieldPos = new FieldPosition(0);
      StringBuffer dumbStr = new StringBuffer();
      boolean hasTx = false;

      // Step 1. Add entries to the cache
      long time1 = System.currentTimeMillis();
      int nOps = _put(hasTx);
      long time2 = System.currentTimeMillis();
      double d = (double) (time2 - time1) / nOps;
      log("Time elapsed for put entry 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(hasTx);
      time2 = System.currentTimeMillis();
      d = (double) (time2 - time1) / nOps;
      log("Time elapsed for get entry 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(hasTx);
      time2 = System.currentTimeMillis();
      d = (double) (time2 - time1) / nOps;
      log("Time elapsed for remove entry is " + (time2 - time1) + " with " + nOps
            + " operations. Average per ops is: " + form.format(d, dumbStr, fieldPos) +
            " msec.");
   }

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

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

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

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

      return nodeList_.size();
   }

   private int _get(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();
            Map map = (Map) cache_.get((String) nodeList_.get(i), key);
            String str = (String) map.get(key);
//                log("_get(): key: " + key + " value: " +str);
            tx.commit();
         } else {
            Map map = (Map) cache_.get((String) nodeList_.get(i), key);
            String str = (String) map.get(key);
//                log("_get(): key: " + key + " value: " +str);
         }
      }

      return nodeList_.size();
   }

   private int _remove(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();
            Map map = (Map) cache_.get((String) nodeList_.get(i), key);
            map.remove(key);
            // Need to put this back in for the replication to take place.
            cache_.put((String) nodeList_.get(i), key, map);
            tx.commit();
         } else {
            Map map = (Map) cache_.get((String) nodeList_.get(i), key);
            map.remove(key);
            // Need to put this back in for the replication to take place.
            cache_.put((String) nodeList_.get(i), key, map);
         }
      }

      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.
    * 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(ReplicatedSyncMapPerfTestCase.class);
   }

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

}
