/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jboss.cache.pojo.region;

import java.util.ArrayList;
import java.util.Properties;
import java.util.Random;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.transaction.UserTransaction;

import org.jboss.cache.Fqn;
import org.jboss.cache.lock.UpgradeException;
import org.jboss.cache.misc.TestingUtil;
import org.jboss.cache.pojo.PojoCache;
import org.jboss.cache.pojo.PojoCacheFactory;
import org.jboss.cache.pojo.test.Address;
import org.jboss.cache.pojo.test.Person;
import org.jboss.cache.transaction.DummyTransactionManager;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * Local concurrent test for PojoCache. Test attach and detach under load
 * and concurrency.
 *
 * @version $Revision: 5370 $
 * @author<a href="mailto:bwang@jboss.org">Ben Wang</a> December 2004
 */
@Test(groups = {"functional"})
public class LocalConcurrentTest 
{
   static PojoCache cache_;
   Properties p_;
   String oldFactory_ = null;
   final String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";
   static ArrayList<String> nodeList_;
   static final int depth_ = 2;
   static final int children_ = 2;
   static final int MAX_LOOP = 100;
   static final int SLEEP_TIME = 50;
   static Exception thread_ex = null;
   UserTransaction tx_ = null;

   @BeforeMethod(alwaysRun = true)
   public void setUp() throws Exception
   {
      oldFactory_ = System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
      System.setProperty(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
      DummyTransactionManager.getInstance();
      if (p_ == null)
      {
         p_ = new Properties();
         p_.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.cache.transaction.DummyContextFactory");
      }

      tx_ = (UserTransaction) new InitialContext(p_).lookup("UserTransaction");

      initCaches();
      nodeList_ = nodeGen(depth_, children_);

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

   @AfterMethod(alwaysRun = true)
   public void tearDown() throws Exception
   {
      thread_ex = null;
      DummyTransactionManager.destroy();
      destroyCaches();

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

   }

   void initCaches() throws Exception
   {
      boolean toStart = false;
      cache_ = PojoCacheFactory.createCache("META-INF/local-service.xml", toStart);
      cache_.start();
   }

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

   public void testAll_RWLock() throws Exception
   {
      try
      {
         all();
      }
      catch (UpgradeException ue)
      {
         log("Upgrade exception. Can ingore for repeatable read. " + ue);
      }
      catch (Exception ex)
      {
         log("Exception: " + ex);
         throw ex;
      }
   }

   private void all() throws Exception
   {
      RunThread t1 = new RunThread(1, "t1");
      RunThread t2 = new RunThread(2, "t2");
      RunThread t3 = new RunThread(3, "t3");
      RunThread t4 = new RunThread(4, "t4");

      t1.start();
      TestingUtil.sleepThread(100);
      t2.start();
      TestingUtil.sleepThread(100);
      t3.start();
      TestingUtil.sleepThread(100);
      t4.start();

      t1.join(60000); // wait for 20 secs
      t2.join(60000); // wait for 20 secs
      t3.join(60000); // wait for 20 secs
      t4.join(60000); // wait for 20 secs

      if (thread_ex != null)
         throw thread_ex;
   }

   class RunThread extends Thread
   {
      final int seed_;
      Random random_;
      Person person_;

      public RunThread(int seed, String threadName)
      {
         super(threadName);
         seed_ = seed;
         random_ = new Random(seed);
      }

      private void createPerson()
      {
         person_ = new Person();
         person_.setName("Ben");
         person_.setAge(18);
         ArrayList<String> lang = new ArrayList<String>();
         lang.add("English");
         lang.add("French");
         lang.add("Mandarin");
         person_.setLanguages(lang);
         Address addr = new Address();
         addr.setZip(95123);
         addr.setStreet("Almeria");
         addr.setCity("San Jose");
         person_.setAddress(addr);
      }

      public void run()
      {
         try
         {
            cache_.getCache().getRegion(Fqn.fromString(Thread.currentThread().getName()), true);
            _run();
         }
         catch (Exception e)
         {
            thread_ex = e;
         }
      }

      /**
       */
      public void _run() throws Exception
      {
         for (int loop = 0; loop < MAX_LOOP; loop++)
         {
            createPerson();   // create a new person instance every loop.
            op1();
         }
      }

      // Operation 1
      private void op1()
      {
         int i = random_.nextInt(nodeList_.size() - 1);
         if (i == 0) return; // it is meaningless to test root
         String node = nodeList_.get(i) + "/aop";
         cache_.attach(node, person_);
         TestingUtil.sleepThread(random_.nextInt(SLEEP_TIME)); // sleep for max 200 millis
         TestingUtil.sleepThread(random_.nextInt(SLEEP_TIME)); // sleep for max 200 millis
         cache_.detach(node);
      }
   }

   /**
    * 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<String> nodeGen(int depth, int children)
   {
      ArrayList<String> strList = new ArrayList<String>();
      ArrayList<String> oldList = new ArrayList<String>();
      ArrayList<String> newList = new ArrayList<String>();

      // Skip root node
      String str = Thread.currentThread().getName();
      oldList.add(str);
      newList.add(str);
      strList.add(str);

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

               newList.add(tmp);
            }
         }
         strList.addAll(newList);
         oldList = newList;
         depth--;
      }

      // let's prune out root node
      for (int i = 0; i < strList.size(); i++)
      {
         if (strList.get(i).equals("/"))
         {
            strList.remove(i);
            break;
         }
      }
      log("Nodes generated: " + strList.size());
      return strList;
   }


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

}
