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

package org.jboss.cache.pojo;

import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.fail;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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;

/**
 */

@Test(groups = {"functional"})
public class LocalTxTest 
{
   Log log = LogFactory.getLog(LocalTxTest.class);
   PojoCache cache;
   final String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";
   DummyTransactionManager tx_mgr;
   Throwable t1_ex, t2_ex;
   long start = 0;



   @BeforeMethod(alwaysRun = true)
   protected void setUp() throws Exception
   {
      log.info("setUp() ....");
      String configFile = "META-INF/local-service.xml";
      boolean toStart = false;
      cache = PojoCacheFactory.createCache(configFile, toStart);
      cache.start();

      System.setProperty(Context.INITIAL_CONTEXT_FACTORY, FACTORY);

      tx_mgr = DummyTransactionManager.getInstance();
      t1_ex = t2_ex = null;
   }

   @AfterMethod(alwaysRun = true)
   protected void tearDown() throws Exception
   {
      cache.stop();

      DummyTransactionManager.destroy();
   }

//   public void testDummy() {}

   UserTransaction getTransaction() throws SystemException, NotSupportedException, NamingException
   {
      Properties prop = new Properties();
      prop.put(Context.INITIAL_CONTEXT_FACTORY,
               "org.jboss.cache.transaction.DummyContextFactory");
      return (UserTransaction) new InitialContext(prop).lookup("UserTransaction");
   }

   private Person createPerson(String id, String name, int age)
   {
      Person p = new Person();
      p.setName(name);
      p.setAge(age);
      cache.attach(id, p);
      return p;
   }

   public void testSimple() throws Exception
   {
      log.info("testSimple() ....");
      UserTransaction tx = getTransaction();
      tx.begin();
      Person p = createPerson("/person/test1", "Harald Gliebe", 32);
      tx.commit();
      tx.begin();
      p.setName("Benoit");
      tx.commit();
      Person p1 = (Person) cache.find("/person/test1");
      assertEquals("Benoit", p.getName());
      assertEquals("Benoit", p1.getName());
      tx.begin();
      p1.setAge(61);
      tx.commit();
      assertEquals(61, p.getAge());
      assertEquals(61, p1.getAge());
   }

   public void testModification() throws Exception
   {
      UserTransaction tx = getTransaction();
      tx.begin();
      Person p = createPerson("/person/test2", "Harald", 32);
      p.setName("Harald Gliebe");
      tx.commit();
      Person p1 = (Person) cache.find("/person/test2");
      tx.begin();
      p1.setName("Benoit");
      tx.commit();
      assertEquals(p.getName(), "Benoit");
      assertEquals(p1.getName(), "Benoit");
      tx.begin();
      p1.setName("Harald");
      tx.rollback();
      assertEquals(p.getName(), "Benoit");
      assertEquals(p1.getName(), "Benoit");
   }

   public void testConcurrentSimplePuts() throws Exception
   {
      Thread t1 = new Thread("t1")
      {
         public void run()
         {
            try
            {
               Person p = (Person) cache.find("/person/test6");
               Address addr = new Address();
               addr.setCity("San Jose");
               UserTransaction tx = getTransaction();
               tx.begin();
               // Note that this will create a write lock on p (on the JBossInternal node)
               p.setAddress(addr);
               TestingUtil.sleepThread(17000);
               tx.commit();
            }
            catch (RollbackException rollback)
            {
               ;
            }
            catch (Exception ex)
            {
               t1_ex = ex;
            }
         }
      };

      Thread t2 = new Thread("t2")
      {
         public void run()
         {
            UserTransaction tx = null;
            try
            {
               TestingUtil.sleepThread(1000); // give Thread1 time to createPerson
               Person p = (Person) cache.find("/person/test6");
               Address addr = new Address();
               addr.setCity("Santa Clara");
               tx = getTransaction();
               tx.begin();
               p.setAddress(addr);
               tx.commit();
            }
            catch (RollbackException rollback)
            {
               ;
            }
            catch (org.jboss.cache.pojo.PojoCacheException tex)
            {
               // This will be a timeout exception. OK.
            }
            catch (Exception ex)
            {
//               t2_ex = ex;
               if (tx != null)
               {
                  try
                  {
                     tx.rollback();
                  }
                  catch (SystemException e)
                  {
                     e.printStackTrace();
                     t2_ex = e;
                  }
               }
               t2_ex = ex;
               ex.printStackTrace();
            }
         }
      };

      Person p = createPerson("/person/test6", "p6", 50);

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

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

      // t2 should rollback due to timeout while t2 should succeed
      if (t2_ex != null)
         fail("Thread1 failed: " + t2_ex);
      if (t1_ex != null)
         fail("Thread2 failed: " + t1_ex);

      assertEquals("City ", "San Jose", p.getAddress().getCity());
   }

   public void testConcurrentPuts() throws Exception
   {
      Thread t1 = new Thread("t1")
      {
         public void run()
         {
            try
            {
               List<String> lang = ((Person) cache.find("/person/test6")).getLanguages();
               UserTransaction tx = getTransaction();
               tx.begin();
               lang.add("German");
               TestingUtil.sleepThread(17000);
               tx.commit();
            }
            catch (RollbackException rollback)
            {
               ;
            }
            catch (Exception ex)
            {
               t1_ex = ex;
            }
         }
      };

      Thread t2 = new Thread("t2")
      {
         public void run()
         {
            UserTransaction tx = null;
            try
            {
               TestingUtil.sleepThread(1000); // give Thread1 time to createPerson
               List<String> lang = ((Person) cache.find("/person/test6")).getLanguages();
               tx = getTransaction();
               tx.begin();
               lang.add("English");
               tx.commit();
            }
            catch (RollbackException rollback)
            {
               ;
            }
            catch (org.jboss.cache.lock.TimeoutException tex)
            {
               //
            }
            catch (Exception ex)
            {
               if (tx != null)
               {
                  try
                  {
                     tx.rollback();
                  }
                  catch (SystemException e)
                  {
                     e.printStackTrace();
                     t2_ex = e;
                  }
               }
               t2_ex = ex;
               ex.printStackTrace();
            }
         }
      };

      Person p = createPerson("/person/test6", "p6", 50);
      List<String> lang = new ArrayList<String>();
      lang.add("German");
      p.setLanguages(lang);

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

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

      // t2 should rollback due to timeout while t2 should succeed
      if (t2_ex != null)
         fail("Thread1 failed: " + t2_ex);
      if (t1_ex != null)
         fail("Thread2 failed: " + t1_ex);

      int size = ((Person) cache.find("/person/test6")).getLanguages().size();
      assertEquals("number of languages ",
                   2, size);
   }

   public void testConcurrentPutsNoWait() throws Exception
   {
      Thread t1 = new Thread("t1")
      {
         UserTransaction tx;

         public void run()
         {
            try
            {
               for (int i = 0; i < 100; i++)
               {
                  String id = "/p1/test7";
                  Person p = createPerson(id, "p6", 50);
                  cache.detach(id);
                  p = createPerson(id, "p6", 51);
                  List<String> lang = new ArrayList<String>();
                  tx = getTransaction();
                  tx.begin();
                  lang.add("German");
                  lang.add("English");
                  try
                  {
                     p.setLanguages(lang);
                  }
                  catch (PojoCacheException ex)
                  {
                     ex.printStackTrace();
                  }
                  tx.commit();
                  TestingUtil.sleepThread(20); // give Thread1 time to createPerson
               }
            }
            catch (RollbackException rollback)
            {
               rollback.printStackTrace();
            }
            catch (PojoCacheException pe)
            {
               pe.printStackTrace();
            }
            catch (Exception ex)
            {
               t1_ex = ex;
            }
         }
      };

      Thread t2 = new Thread("t2")
      {
         UserTransaction tx;

         public void run()
         {
            try
            {
               for (int i = 0; i < 100; i++)
               {
                  String id = "/p1/test8";
                  Person p = createPerson(id, "p6", 50);
                  cache.detach(id);
                  p = createPerson(id, "p6", 51);
                  List<String> lang = new ArrayList<String>();
                  tx = getTransaction();
                  tx.begin();
                  lang.add("German");
                  lang.add("English");
                  try
                  {
                     p.setLanguages(lang);
                  }
                  catch (PojoCacheException ex)
                  {
                     ex.printStackTrace();
                  }
                  tx.commit();
                  TestingUtil.sleepThread(20); // give Thread1 time to createPerson
               }
            }
            catch (RollbackException rollback)
            {
               rollback.printStackTrace();
            }
            catch (PojoCacheException pe)
            {
               pe.printStackTrace();
            }
            catch (Exception ex)
            {
               t2_ex = ex;
               ex.printStackTrace();
            }
         }
      };

      createPerson("/p1/test6", "p6", 50);
      createPerson("/p2/test6", "p6", 50);

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

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

      // t2 should rollback due to timeout while t2 should succeed
      if (t2_ex != null)
         fail("Thread1 failed: " + t2_ex);
      if (t1_ex != null)
         fail("Thread2 failed: " + t1_ex);

//      int size = ((Person) cache.find("/p1/test7")).getLanguages().size();
//      assertEquals("number of languages ",
//              2, size);
   }

   void log(String s)
   {
      long now;
      if (start == 0)
         start = System.currentTimeMillis();
      now = System.currentTimeMillis();

      System.out.println("[" + Thread.currentThread().getName() + "] [" + (now - start) + "] " + s);
   }





}
