package org.jboss.cache.eviction;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jboss.cache.Fqn;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.TreeCache;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.misc.TestingUtil;

/**
 * Unit tests for LRU Policy.
 *
 * @author Ben Wang, Feb 11, 2004
 * @author Daniel Huang - dhuang@jboss.org
 * @version $Revision: 3477 $
 */
public class LRUPolicyTest extends TestCase
{
   TreeCache cache_;
   int wakeupIntervalMillis_ = 0;
   final String ROOT_STR = "/test";
   Throwable t1_ex, t2_ex;
   final long DURATION = 10000;
   boolean isTrue;

   public LRUPolicyTest(String s)
   {
      super(s);
   }

   public void setUp() throws Exception
   {
      super.setUp();
      initCaches();
      wakeupIntervalMillis_ = cache_.getEvictionThreadWakeupIntervalSeconds() * 1000;
      log("wakeupInterval is " + wakeupIntervalMillis_);
      if (wakeupIntervalMillis_ < 0)
         fail("testEviction(): eviction thread wake up interval is illegal " + wakeupIntervalMillis_);

      t1_ex = t2_ex = null;
      isTrue = true;
   }

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

   public void tearDown() throws Exception
   {
      super.tearDown();
      cache_.stopService();
   }

   public void testInUseEviction() throws Exception
   {
      String rootStr = "/org/jboss/test/data/inuse/";
      Fqn fqn;
//      cache_.put("/", "a", "b");
//      TestingUtil.sleepThread(wakeupIntervalMillis_ + 500);

      for (int i = 0; i < 10; i++)
      {
         String str = rootStr + i;
         fqn = Fqn.fromString(str);
         cache_.put(fqn, str, str);
      }

      TestingUtil.sleepThread(wakeupIntervalMillis_ + 500);
      cache_.getEvictionRegionManager().markNodeCurrentlyInUse(Fqn.fromString(rootStr + 5), 0);

      for (int i = 10; i < 15; i++)
      {
         String str = rootStr + i;
         fqn = Fqn.fromString(str);
         cache_.put(fqn, str, str);
      }

      TestingUtil.sleepThread(wakeupIntervalMillis_ + 500);

      for (int i = 0; i < 5; i++)
      {
         assertNull(cache_.get(Fqn.fromString(rootStr + i)));
      }

      assertNotNull(cache_.get(Fqn.fromString(rootStr + 5)));

      for (int i = 6; i < 11; i++)
      {
         assertNull(cache_.get(Fqn.fromString(rootStr + i)));
      }
      for (int i = 11; i < 15; i++)
      {
         assertNotNull(cache_.get(Fqn.fromString(rootStr + i)));
      }

      TestingUtil.sleepThread(wakeupIntervalMillis_ *2 + 500);
      assertNotNull(cache_.get(Fqn.fromString(rootStr + 5)));

      cache_.getEvictionRegionManager().unmarkNodeCurrentlyInUse(Fqn.fromString(rootStr +5));
      TestingUtil.sleepThread(wakeupIntervalMillis_ *2 + 500);
      assertNull(cache_.get(Fqn.fromString(rootStr + 5)));
   }

   public void testEviction()
   {
      String rootStr = "/org/jboss/test/data/";
      for (int i = 0; i < 10; i++)
      {
         String str = rootStr + i;
         Fqn fqn = Fqn.fromString(str);
         try
         {
            cache_.put(fqn, str, str);
         }
         catch (Exception e)
         {
            fail("Failed to insert data" + e);
            e.printStackTrace();
         }
      }
      System.out.println(cache_.toString());
      TestingUtil.sleepThread(wakeupIntervalMillis_ + 500);
      System.out.println(cache_.toString());
      try
      {
         String val = (String) cache_.get(rootStr + "3", rootStr + "3");
         assertNull("DataNode should be empty ", val);
      }
      catch (Exception e)
      {
         e.printStackTrace();
         fail("Failed to get" + e);
      }
   }

   public void testNodeVisited()
   {
      String rootStr = "/org/jboss/test/data/";
      for (int i = 0; i < 10; i++)
      {
         String str = rootStr + i;
         Fqn fqn = Fqn.fromString(str);
         try
         {
            cache_.put(fqn, str, str);
         }
         catch (Exception e)
         {
            fail("Failed to insert data" + e);
            e.printStackTrace();
         }
      }
      System.out.println("cache is:\n" + cache_.toString(true));

      int period = (wakeupIntervalMillis_ / 2 + 500);
      log("sleeping for " + period + "ms");
      TestingUtil.sleepThread(period);  // it really depends the eviction thread time.
      String str = rootStr + "7";
      Fqn fqn = Fqn.fromString(str);
      try
      {
         cache_.get(fqn, str);   // just to keep it fresh
         System.out.println("-- sleeping for " + period + "ms");
         TestingUtil.sleepThread(period);  // it really depends the eviction thread time.
         cache_.get(fqn, str);   // just to keep it fresh
         System.out.println("-- sleeping for " + period + "ms");
         TestingUtil.sleepThread(period);  // it really depends the eviction thread time.
         String val = (String) cache_.get(rootStr + "3", rootStr + "3");
         System.out.println("-- val=" + val);
         assertNull("DataNode should be empty ", val);
         val = (String) cache_.get(rootStr + "7", rootStr + "7");
         System.out.println("-- val=" + val);
         assertNotNull("DataNode should not be empty ", val);
         System.out.println("-- sleeping for " + (wakeupIntervalMillis_ + 1000) + "ms");
         TestingUtil.sleepThread(wakeupIntervalMillis_ + 1000);
         val = (String) cache_.get(rootStr + "7", rootStr + "7");
         System.out.println("-- val=" + val);
         assertNull("DataNode should be empty ", val);
      }
      catch (Exception e)
      {
         e.printStackTrace();
         fail("Failed to evict" + e);
      }
   }

   public void testNodeRemoved()
   {
      String rootStr = "/org/jboss/test/data/";
      for (int i = 0; i < 10; i++)
      {
         String str = rootStr + i + "/" + i;
         Fqn fqn = Fqn.fromString(str);
         try
         {
            cache_.put(fqn, str, str);
         }
         catch (Exception e)
         {
            fail("Failed to insert data" + e);
            e.printStackTrace();
         }
      }

      int period = (wakeupIntervalMillis_ / 2 + 500);
      log("period is " + period);
//      TestingUtil.sleepThread(period);  // it really depends the eviction thread time.
      String str1 = rootStr + "7";
      Fqn fqn1 = Fqn.fromString(str1);
      String str2 = rootStr + "7/7";
      Fqn fqn2 = Fqn.fromString(str2);
      try
      {
         cache_.get(fqn1, str1);   // just to keep it fresh
         cache_.get(fqn2, str2);   // just to keep it fresh
         TestingUtil.sleepThread(period);  // it really depends the eviction thread time.
         cache_.get(fqn1, str1);   // just to keep it fresh
         cache_.get(fqn2, str2);   // just to keep it fresh
         TestingUtil.sleepThread(period);  // it really depends the eviction thread time.
         String val = (String) cache_.get(rootStr + "7/7", rootStr + "7/7");
         assertNotNull("DataNode should not be empty ", val);
         cache_.remove(fqn1);
         TestingUtil.sleepThread(wakeupIntervalMillis_ + 500);
         val = (String) cache_.get(rootStr + "7/7", rootStr + "7/7");
         assertNull("DataNode should be empty ", val);
      }
      catch (Exception e)
      {
         e.printStackTrace();
         fail("Failed to evict" + e);
      }
   }
   
   public void testCompleteRemoval() throws Exception
   {
      String rootStr = "/test/";
      
      // Add a parent, then a child. LRU will evict the parent, 
      // then the child, leaving behind an empty parent
      Fqn parent = Fqn.fromString(rootStr + "parent");
      cache_.put(parent, "key", "value");
      cache_.put(new Fqn(parent, "child"), "key", "value");
      
      // Give eviction time to run a few times, then confirm parent
      // is completely gone
      TestingUtil.sleepThread((wakeupIntervalMillis_ * 4) + 100);      
      assertFalse("Parent completely removed", cache_.exists(parent));            
   }


   class MyPutter extends Thread
   {

      public MyPutter(String name)
      {
         super(name);
      }

      public void run()
      {
         int i = 0;
         final String myName = ROOT_STR + "/test1/node" + getName();
         while (isTrue)
         {
            try
            {
               cache_.put(myName + i++, "value", new Integer(i));
               sleep(1);
            }
            catch (Throwable e)
            {
               e.printStackTrace();
               if (t1_ex == null)
                  t1_ex = e;
            }
         }
      }
   }


   public void testConcurrentPutAndEvict() throws Exception
   {
      cache_.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
      cache_.put(ROOT_STR + "/concurrentPutAndEvict", "value", new Integer(1));

      for (int i = 0; i < 10; i++)
      {
         new MyPutter("Putter" + i).start();
      }

      int counter = 0;
      while (true)
      {
         counter++;
         if (t1_ex != null)
         {
            fail("Exception generated in put() " + t1_ex);
         }
         log("nodes/locks: " + cache_.getNumberOfNodes() + "/" + cache_.getNumberOfLocksHeld());
         TestingUtil.sleepThread(1000);
         if (counter > 10)
         { // run for 10 seconds
            isTrue = false;
            break;
         }
      }
   }

   public void testForEvictionInternalError()
   {
      try
      {
//       String rootStr = "org/jboss/test/data/";
         String rootStr = "/test/testdata";

         for (int i = 0; i < 10; i++)
         {
            String str = rootStr + i;
            Fqn fqn = Fqn.fromString(str);
            try
            {
               cache_.put(fqn, str, str);
            }
            catch (Exception e)
            {
               fail("Failed to insert data" + e);
               e.printStackTrace();
            }
         }

         // wait for an eviction
         TestingUtil.sleepThread(2 * wakeupIntervalMillis_ + 2000);

         String val = (String) cache_.get(rootStr + "3", rootStr + "3");
         assertNull("DataNode should be empty ", val);

         // reinsert the elements
         for (int i = 0; i < 10; i++)
         {
            String str = rootStr + i;
            Fqn fqn = Fqn.fromString(str);
            try
            {
               cache_.put(fqn, str, str);
            }
            catch (Exception e)
            {
               fail("Failed to insert data" + e);
               e.printStackTrace();
            }
         }

         // clear the root
         Fqn root = cache_.get("/").getFqn();
         cache_.remove(root);

         // wait for an eviction
         TestingUtil.sleepThread(2 * wakeupIntervalMillis_ + 1000);

         val = (String) cache_.get(rootStr + "3", rootStr + "3");
         assertNull("DataNode should be empty ", val);

      }
      catch (Exception e)
      {
         e.printStackTrace();
         fail("Failed to get" + e);
      }
   }

   public void testLegacyAPI() throws Exception
   {
      RegionManager regionManager = cache_.getEvictionRegionManager();
      Region region = regionManager.createRegion("/123", new LRUAlgorithm());

      region.setTimeToLiveSeconds(5);
      region.setMaxNodes(5000);

      assertEquals(5000, ((LRUConfiguration) region.getEvictionConfiguration()).getMaxNodes());
      assertEquals(5, ((LRUConfiguration) region.getEvictionConfiguration()).getTimeToLiveSeconds());

      String rootStr = "/123/node";
      for (int i = 0; i < 10; i++)
      {
         Fqn fqn = Fqn.fromString(rootStr + Integer.toString(i));
         cache_.put(fqn, fqn.toString(), fqn.toString());
      }

      assertEquals(rootStr + "5", cache_.get(rootStr + "5", rootStr + "5"));

      TestingUtil.sleepThread(2 * wakeupIntervalMillis_ + 2000);

      assertNull("DataNode should be null", cache_.get(rootStr + "5"));
   }

   void log(String msg)
   {
      System.out.println("-- " + msg);
   }

   public static Test suite()
   {
      return new TestSuite(LRUPolicyTest.class);
   }

   public static void main(String[] args)
   {
      junit.textui.TestRunner.run(suite());
   }

}
