package org.jboss.cache.eviction;


import org.jboss.cache.*;
import org.jboss.cache.util.TestingUtil;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.EvictionConfig;
import org.jboss.cache.config.EvictionRegionConfig;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import org.jboss.cache.lock.IsolationLevel;
import static org.testng.AssertJUnit.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

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

/**
 * Unit tests for LRU Policy.
 *
 * @author Ben Wang, Feb 11, 2004
 * @author Daniel Huang - dhuang@jboss.org
 * @version $Revision: 5906 $
 */
@Test(groups = "functional")
public class LRUPolicyTest
{
   CacheSPI<Object, Object> cache;
   int wakeupIntervalMillis_ = 0;
   int dataRegionTTLMillis = 6000;
   int testRegionTTLMillis = 4000;

   final String ROOT_STR = "/test";
   Throwable t1_ex, t2_ex;
   final long DURATION = 10000;
   boolean isTrue;

   @BeforeMethod(alwaysRun = true)
   public void setUp() throws Exception
   {
      Configuration conf = UnitTestCacheConfigurationFactory.createConfiguration(Configuration.CacheMode.LOCAL, true);
      EvictionConfig evConfig = conf.getEvictionConfig();
      evConfig.setWakeupIntervalSeconds(1);
      List<EvictionRegionConfig> regionConfigs = new ArrayList<EvictionRegionConfig>();
      regionConfigs.add(UnitTestCacheConfigurationFactory.buildLruEvictionRegionConfig("/org/jboss/test/data", 5, dataRegionTTLMillis / 1000));
      regionConfigs.add(UnitTestCacheConfigurationFactory.buildLruEvictionRegionConfig("/test", 10000, testRegionTTLMillis / 1000));
      evConfig.setEvictionRegionConfigs(regionConfigs);
      conf.setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
      conf.setIsolationLevel(IsolationLevel.SERIALIZABLE);
      cache = (CacheSPI<Object, Object>) new DefaultCacheFactory<Object, Object>().createCache(conf);

      wakeupIntervalMillis_ = cache.getConfiguration().getEvictionConfig().getWakeupIntervalSeconds() * 1000;
      System.out.println("-- wakeupInterval is " + wakeupIntervalMillis_);
      if (wakeupIntervalMillis_ < 0)
      {
         fail("testEviction(): eviction thread wake up interval is illegal " + wakeupIntervalMillis_);
      }

      t1_ex = t2_ex = null;
      isTrue = true;
   }

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

   public void testInUseEviction() throws Exception
   {
      String rootStr = "/org/jboss/test/data/inuse/";
      Fqn fqn;

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

      System.out.println("-- Marking as in-use");
      cache.getRegionManager().getRegion(Fqn.fromString(rootStr + 5), false).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++)
      {
         Fqn f = Fqn.fromString(rootStr + i);
         assert null == cache.getNode(f) : f + " should be null";
      }

      assertNotNull(cache.getNode(Fqn.fromString(rootStr + 5)));

      for (int i = 6; i < 11; i++)
      {
         Fqn f = Fqn.fromString(rootStr + i);
         assert null == cache.getNode(f) : f + " should be null";
      }
      for (int i = 11; i < 15; i++)
      {
         Fqn f = Fqn.fromString(rootStr + i);
         assert null != cache.getNode(f) : f + " should not be null";
      }
   }

   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());
      String val = (String) cache.get(rootStr + "3", rootStr + "3");
      assertNull("Node should be empty ", val);
   }

   public void testNodeVisited()
   {
      String rootStr = "/org/jboss/test/data/";

      System.out.println("REGIONS: " + cache.getRegionManager().dumpRegions());

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

      int period = (wakeupIntervalMillis_ / 2 + 500);
      System.out.println("-- sleeping for " + period + "ms");
      TestingUtil.sleepThread(period);
      String str = rootStr + "7";
      Fqn fqn = Fqn.fromString(str);
      cache.get(fqn, str);// just to keep it fresh
      System.out.println("-- sleeping for " + period + "ms");
      TestingUtil.sleepThread(period);
      cache.get(fqn, str);// just to keep it fresh
      System.out.println("-- sleeping for " + period + "ms");
      TestingUtil.sleepThread(period);
      String val = (String) cache.get(rootStr + "3", rootStr + "3");
      System.out.println("-- val=" + val);
      assertNull("Node should be empty ", val);
      val = (String) cache.get(rootStr + "7", rootStr + "7");
      System.out.println("-- val=" + val);
      assertNotNull("Node should not be empty ", val);
      period = dataRegionTTLMillis + wakeupIntervalMillis_ + 500; // this is the TTL for nodes + time for the eviction thread to kick in
      System.out.println("-- sleeping for " + period + "ms");
      TestingUtil.sleepThread(period);
      val = (String) cache.get(rootStr + "7", rootStr + "7");
      System.out.println("-- val=" + val);
      assertNull("Node should be empty ", val);
   }

   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);
         cache.put(fqn, str, str);
      }

      int period = (wakeupIntervalMillis_ / 2 + 500);
      System.out.println("-- 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);
      cache.get(fqn1, str1);// just to keep it fresh
      cache.get(fqn2, str2);// just to keep it fresh
      TestingUtil.sleepThread(period);
      cache.get(fqn1, str1);// just to keep it fresh
      cache.get(fqn2, str2);// just to keep it fresh
      TestingUtil.sleepThread(period);
      String val = (String) cache.get(rootStr + "7/7", rootStr + "7/7");
      assertNotNull("Node should not be empty ", val);
      cache.removeNode(fqn1);
      TestingUtil.sleepThread(wakeupIntervalMillis_ + 500);
      val = (String) cache.get(rootStr + "7/7", rootStr + "7/7");
      assertNull("Node should be empty ", val);
   }

   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<String> parent = Fqn.fromString(rootStr + "parent");
      cache.put(parent, "key", "value");
      cache.put(Fqn.fromRelativeElements(parent, "child"), "key", "value");

      // Give eviction time to run a few times, then confirm parent
      // is completely gone
      int period = (wakeupIntervalMillis_ + testRegionTTLMillis) * 2;
      System.out.println("-- Sleeping for " + period);
      TestingUtil.sleepThread(period);
      assertFalse("Parent not completely removed", cache.getRoot().hasChild(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", i);
               sleep(1);
            }
            catch (IllegalStateException ise)
            {
               // this will happen since some threads are bound to continue running while the test stops.
               // do nothing.
            }
            catch (Throwable e)
            {
               e.printStackTrace();
               if (t1_ex == null)
               {
                  t1_ex = e;
               }
            }
         }
      }
   }


   public void testConcurrentPutAndEvict() throws Exception
   {
      cache.stop();
      cache.destroy();
      cache.getConfiguration().setIsolationLevel(IsolationLevel.REPEATABLE_READ);
      cache.create();
      cache.start();
      cache.put(ROOT_STR + "/concurrentPutAndEvict", "value", 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);
         }
         System.out.println("-- nodes/locks: " + cache.getNumberOfNodes() + "/" + cache.getNumberOfLocksHeld());
         TestingUtil.sleepThread(1000);
         if (counter > 10)
         {// run for 10 seconds
            isTrue = false;
            break;
         }
      }
   }

   public void testForEvictionInternalError()
   {
      String rootStr = "/test/testdata";

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

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

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

      // reinsert the elements
      for (int i = 0; i < 10; i++)
      {
         String str = rootStr + i;
         Fqn fqn = Fqn.fromString(str);
         cache.put(fqn, str, str);
      }

      // clear the root
      cache.removeNode(Fqn.ROOT);

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

      val = (String) cache.get(rootStr + "3", rootStr + "3");
      assertNull("Node should be empty ", val);
   }

   public void testOvereviction() throws Exception
   {
      Configuration c = UnitTestCacheConfigurationFactory.createConfiguration(Configuration.CacheMode.LOCAL, true);
      Cache<Object, Object> cache1 = new DefaultCacheFactory().createCache(c, true);
      cache1.put(Fqn.fromString("/base/" + 0), "key", "base" + 0);
      Node node = cache1.getRoot().getChild(Fqn.fromString("/base/"));
      node.setResident(true);

      for (int i = 1; i < 5100; i++)
      {
         cache1.put(Fqn.fromString("/base/" + i), "key", "base" + i);
      }
      Thread.sleep(5000);
      assertEquals(5000, cache1.getRoot().getChild(Fqn.fromString("/base")).getChildren().size());

   }
}
