package org.jboss.cache.eviction;

import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.EvictionConfig;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import org.jboss.cache.factories.XmlConfigurationParser;
import org.jboss.cache.interceptors.EvictionInterceptor;
import org.jboss.cache.util.TestingUtil;
import org.jboss.cache.transaction.DummyTransactionManagerLookup;
import org.jboss.cache.xml.XmlHelper;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.w3c.dom.Element;

import javax.transaction.TransactionManager;
import java.util.Iterator;
import java.util.List;

/**
 * Tests the eviction and the possible lack of locking nodes.
 * The configuration is with an aggressive eviction policy, 100 objects 2 seconds interval.
 * <p/>
 * It is possible that the number needs to be changed a little, depending on the machine speed.
 *
 * @author fhenning
 */
@Test(groups = {"functional"})
public class OptimisticEvictionTest
{

   //Maximum number of runs 2^20
   private static final int NUMBER_OF_RUNS = 1 << 20;
   //Initial number of nodes
   private static final int NUMBER_NODES = 256;

   private Fqn region = Fqn.fromElements("testingRegion");
   private TransactionManager txManager;
   private CacheSPI<Object, Object> cache;

   @BeforeMethod(alwaysRun = true)
   public void setUp() throws Exception
   {
      txManager = new DummyTransactionManagerLookup().getTransactionManager();
      Configuration config = UnitTestCacheConfigurationFactory.createConfiguration(Configuration.CacheMode.LOCAL, true);
      config.setNodeLockingOptimistic(true);
      config.setEvictionConfig(buildEvictionConfig());
      cache = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache(config);
   }

   private EvictionConfig buildEvictionConfig() throws Exception
   {
      String xml = " <attribute name=\"EvictionPolicyConfig\">\n" +
            "         <config>\n" +
            "            <attribute name=\"wakeUpIntervalSeconds\">1</attribute>\n" +
            "            <!-- Name of the DEFAULT eviction policy class.-->\n" +
            "            <attribute name=\"policyClass\">org.jboss.cache.eviction.LRUPolicy</attribute>\n" +
            "            <region name=\"/_default_\">\n" +
            "               <attribute name=\"maxNodes\">10</attribute>\n" +
            "               <attribute name=\"timeToLiveSeconds\">0</attribute>\n" +
            "               <attribute name=\"maxAgeSeconds\">0</attribute>\n" +
            "            </region>\n" +
            "            <region name=\"/testingRegion\">\n" +
            "               <attribute name=\"maxNodes\">10</attribute>\n" +
            "               <attribute name=\"timeToLiveSeconds\">0</attribute>\n" +
            "               <attribute name=\"maxAgeSeconds\">0</attribute>\n" +
            "            </region>\n" +
            "            <region name=\"/timeBased\">\n" +
            "               <attribute name=\"maxNodes\">10</attribute>\n" +
            "               <attribute name=\"timeToLiveSeconds\">1</attribute>\n" +
            "               <attribute name=\"maxAgeSeconds\">1</attribute>\n" +
            "            </region>\n" +
            "         </config>\n" +
            "      </attribute>";
      Element element = XmlHelper.stringToElement(xml);
      return XmlConfigurationParser.parseEvictionConfig(element);
   }

   @AfterMethod(alwaysRun = true)
   public void tearDown() throws Exception
   {
      if (cache != null)
      {
         // shut down any existing gtx2EntryMap
         try
         {
            if (cache.getTransactionManager().getTransaction() != null)
            {
               cache.getTransactionManager().rollback();
            }
         }
         catch (Exception e)
         {
            // ignore
         }
         cache.stop();
         cache = null;
      }
   }


   public void testEvictionError() throws Exception
   {
      //Initialize the cache via a map
      for (int i = 0; i < NUMBER_NODES; i++)
      {
         cache.put(Fqn.fromRelativeElements(region, i), i, i);
      }

      for (int i = 0; i < NUMBER_OF_RUNS; i++)
      {
         txManager.begin();
         cache.get(region, i % NUMBER_NODES);
         txManager.commit();
      }
   }


   public void testEvictionOccurence() throws Exception
   {
      cache.put("/timeBased/test", "key", "value");
      assertTrue(cache.exists("/timeBased/test"));

      // wait for it to be evicted.
      TestingUtil.sleepThread(3000);
      assertTrue(!cache.exists("/timeBased/test"));
   }

   public void testInterceptorChain() throws Exception
   {
      List interceptors = cache.getInterceptorChain();
      System.out.println(interceptors);
      Iterator i = interceptors.iterator();
      boolean found = false;

      while (i.hasNext())
      {
         Object o = i.next();
         if (o instanceof EvictionInterceptor)
         {
            found = true;
         }
      }

      assertTrue("Eviction interceptor should be in interceptor chain.", found);
   }

   public void testCompleteRemoval() throws Exception
   {
      String rootStr = "/timeBased/";

      // 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
      TestingUtil.sleepThread(5500);
      assertFalse("Parent completely removed", cache.getRoot().hasChild(parent));
   }

}
