/*
 * 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.stress;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jboss.cache.CacheException;
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.*;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Random;

//import java.text.DecimalFormat;
//import java.text.FieldPosition;

/**
 * Local mode stress test for TreeCache.
 *
 * @version $Revision: 1.1.2.4 $
 * @author<a href="mailto:bwang@jboss.org">Ben Wang</a> May 20 2003
 */
public class EvictionLocalStressTestCase extends TestCase
{
   static TreeCache cache_;
   int cachingMode_ = TreeCache.LOCAL;
   final static Properties p_;
   //   final static Logger log_ = Logger.getLogger(LocalStressTestCase.class);
   String oldFactory_ = null;
   final String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";
   static ArrayList nodeList_;
   static final int depth_ = 4;
   static final int children_ = 4;
   DummyTransactionManager tm_;
   static final int MAX_LOOP = 100;
   static final int SLEEP_TIME = 20;
   static final String ROOT = "/stress/";

   static Exception thread_ex;

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

   public EvictionLocalStressTestCase(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);
      nodeList_ = nodeGen(depth_, children_);
      tm_ = new DummyTransactionManager();

      log("LocalStressTestCase: cacheMode=TRANSIENT, one cache");
   }

   public void tearDown() throws Exception
   {
      super.tearDown();
      thread_ex=null;
      DummyTransactionManager.destroy();
      destroyCaches();

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

   }

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

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

   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);
   }

   public void testAllTx_RWLock() throws Exception
   {
      setLevelRW();
      allTx();
   }

   public void XtestAllTx_SimpleLock() throws Exception
   {
      setLevelSerial();
      allTx();
   }

   private void allTx() throws Exception
   {
      UserTransaction tx = null;
      tx = (UserTransaction) new InitialContext(p_).lookup("UserTransaction");

      RunThread t1 = new RunThread(tx, 1);
      RunThread t2 = new RunThread(tx, 2);

      t1.start();
      t2.start();

      t1.join(60000);
      t2.join(60000);

      if(thread_ex != null)
         throw thread_ex;
   }

   static class RunThread extends Thread
   {
      final int seed_;
      Random random_;
      UserTransaction tx_ = null;

      public RunThread(UserTransaction tx, int seed)
      {
         seed_ = seed;
         random_ = new Random(seed);
         tx_ = tx;
      }

      public void run()
      {
         try {
            _run();
         }
         catch(Exception ex) {
            thread_ex=ex;
         }
      }

      public void _run() throws CacheException, HeuristicMixedException,
                                SystemException, NotSupportedException, HeuristicRollbackException, RollbackException {
         for(int outer = 0; outer < 4; outer++) {
            for (int loop = 0; loop < MAX_LOOP; loop++) {
               sleep_(random_.nextInt(SLEEP_TIME));
               log("Executing op1. Loop-" + loop);
               op1();
               sleep_(random_.nextInt(SLEEP_TIME));
               log("Executing op2...");
               op2();
               sleep_(random_.nextInt(SLEEP_TIME));
               log("Executing op3...");
               op3();
            }
         }
      }

      // Operation 1
      private void op1() throws CacheException {
         int i = random_.nextInt(nodeList_.size() - 1);
         String key = Integer.toString(i);
         String value = Integer.toString(i);

//            tx_.begin();
         String node = (String) nodeList_.get(i);
         cache_.get(node, key);
         sleep_(random_.nextInt(SLEEP_TIME)); // sleep for max 200 millis
         cache_.put(node, key, value);
//            tx_.commit();
      }

      // Operation 2
      private void op2() throws CacheException, SystemException, NotSupportedException,
                                HeuristicMixedException, HeuristicRollbackException, RollbackException {
         int i = random_.nextInt(nodeList_.size() - 1);
         String key = Integer.toString(i);

         tx_.begin();
         String node = (String) nodeList_.get(i);
         cache_.get(node, key);
         sleep_(random_.nextInt(SLEEP_TIME)); // sleep for max 200 millis
         cache_.get(node, key);
         sleep_(random_.nextInt(SLEEP_TIME)); // sleep for max 200 millis
         tx_.commit();
      }

      // Operation 3
      private void op3() throws SystemException, NotSupportedException, CacheException,
                                HeuristicMixedException, HeuristicRollbackException, RollbackException {
         int i = random_.nextInt(nodeList_.size() - 1);
         String key = Integer.toString(i);
         String value = Integer.toString(i);

         tx_.begin();
         String node = (String) nodeList_.get(i);
         cache_.put(node, key, value);
         sleep_(random_.nextInt(SLEEP_TIME)); // sleep for max 200 millis
         cache_.remove(node, key);
         tx_.commit();
      }

      private void sleep_(long msecs)
      {
         try {
            Thread.sleep(msecs);
         } catch (Exception ex) {
         }
      }
   }

   /**
    * 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(ROOT);
      newList.add(ROOT);
      strList.add(ROOT);

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

   private static void log(String str)
   {
      System.out.println("Thread: " + Thread.currentThread() + ": " + str);
//        System.out.println(str);
   }

}
