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.misc.TestingUtil;

/**
 * Unit tests for LRUAlgorithm.
 *
 * @author Ben Wang, Feb 11, 2004
 * @author Daniel Huang (dhuang@jboss.org)
 */
public class LRUAlgorithmTest extends TestCase
{
   RegionManager regionManager_;
   LRUAlgorithm algo_;
   LRUConfiguration config_;

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

   public void setUp() throws Exception
   {
      super.setUp();

      algo_ = new LRUAlgorithm();
      config_ = new LRUConfiguration();
      regionManager_ = new RegionManager();
      regionManager_.createRegion("/a/b", new DummyEvictionPolicy(), config_);
      /*
      try {
         Thread.sleep(10000);
      } catch (InterruptedException e) {
         e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      }
      */
   }

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

   /**
    * maxNodes = 1. Eception is evictFromCacheNode. Should be commented for now.
    */
   public void XtestEvictException()
   {
      Fqn fqn1 = Fqn.fromString("/a/b/c");
      Fqn fqn2 = Fqn.fromString("/a/b/d");
      Fqn fqn3 = Fqn.fromString("/a/b/e");
      Region region = regionManager_.getRegion("/a/b");
      LRUConfiguration config = (LRUConfiguration) region.getEvictionConfiguration();
      config.setMaxNodes(1);
      region.setAddedNode(fqn1);
      region.setAddedNode(fqn2);

      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size should be ", 1, algo_.getEvictionQueue().getNumberOfNodes());

      region.setAddedNode(fqn2);
      region.setAddedNode(fqn3);

      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size should be ", 1, algo_.getEvictionQueue().getNumberOfNodes());
   }


   /**
    * maxNodes = 0 case
    */
   public void testMaxNode1()
   {
      Fqn fqn1 = Fqn.fromString("/a/b/c");
      Fqn fqn2 = Fqn.fromString("/a/b/d");
      Region region = regionManager_.getRegion("/a/b");
      LRUConfiguration config = (LRUConfiguration) region.getEvictionConfiguration();
      config.setMaxNodes(0);
      region.setAddedNode(fqn1);
      region.setAddedNode(fqn2);

      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size should be ", 2, algo_.getEvictionQueue().getNumberOfNodes());

   }

   /**
    * maxNodes = 1
    */
   public void testMaxNode2()
   {
      Fqn fqn1 = Fqn.fromString("/a/b/c");
      Fqn fqn2 = Fqn.fromString("/a/b/d");
      Fqn fqn3 = Fqn.fromString("/a/b/e");
      Region region = regionManager_.getRegion("/a/b");
      LRUConfiguration config = (LRUConfiguration) region.getEvictionConfiguration();
      config.setMaxNodes(1);
      region.setAddedNode(fqn1);
      region.setAddedNode(fqn2);

      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size should be ", 1, algo_.getEvictionQueue().getNumberOfNodes());

      region.setAddedNode(fqn2);
      region.setAddedNode(fqn3);

      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size should be ", 1, algo_.getEvictionQueue().getNumberOfNodes());
   }

   /**
    * TimeToIdleSeconds = 0
    */
   public void testIdleTimeSeconds1()
   {
      Fqn fqn1 = Fqn.fromString("/a/b/c");
      Fqn fqn2 = Fqn.fromString("/a/b/d");
      Region region = regionManager_.getRegion("/a/b");
      LRUConfiguration config = (LRUConfiguration) region.getEvictionConfiguration();
      config.setMaxNodes(0);
      config.setTimeToLiveSeconds(0);
      region.setAddedNode(fqn1);
      region.setAddedNode(fqn2);
      TestingUtil.sleepThread(2000);
      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size should be ", 2, algo_.getEvictionQueue().getNumberOfNodes());

   }

   /**
    * TimeToIdleSeconds = 1
    */
   public void testIdleTimeSeconds2()
   {
      Fqn fqn1 = Fqn.fromString("/a/b/c");
      Fqn fqn2 = Fqn.fromString("/a/b/d");
      Fqn fqn3 = Fqn.fromString("/a/b/e");
      Region region = regionManager_.getRegion("/a/b");
      LRUConfiguration config = (LRUConfiguration) region.getEvictionConfiguration();
      config.setMaxNodes(0);
      config.setTimeToLiveSeconds(1);
      region.setAddedNode(fqn1);
      region.setAddedNode(fqn2);
      region.setAddedNode(fqn3);
      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size #1: ", 3, algo_.getEvictionQueue().getNumberOfNodes());
      TestingUtil.sleepThread(2000);
      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size #2: ", 0, algo_.getEvictionQueue().getNumberOfNodes());
   }

   /**
    * TimeToIdleSeconds = 1 with node visited in between.
    */
   public void testIdleTimeSeconds3()
   {
      Fqn fqn1 = Fqn.fromString("/a/b/c");
      Fqn fqn2 = Fqn.fromString("/a/b/d");
      Fqn fqn3 = Fqn.fromString("/a/b/e");
      Region region = regionManager_.getRegion("/a/b");
      LRUConfiguration config = (LRUConfiguration) region.getEvictionConfiguration();
      config.setMaxNodes(0);
      config.setTimeToLiveSeconds(1);
      region.setAddedNode(fqn1);
      region.setAddedNode(fqn2);
      region.setAddedNode(fqn3);
      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size #1: ", 3, algo_.getEvictionQueue().getNumberOfNodes());
      TestingUtil.sleepThread(2000);
      region.setVisitedNode(fqn2);
      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size #2: ", 1, algo_.getEvictionQueue().getNumberOfNodes());
   }

   /**
    * MaxAgeSeconds = 1 with 3 nodes.
    *
    * @throws Exception
    */
   public void testMaxAgeSeconds1() throws Exception
   {
      Fqn fqn1 = Fqn.fromString("/a/b/c");
      Fqn fqn2 = Fqn.fromString("/a/b/d");
      Fqn fqn3 = Fqn.fromString("/a/b/e");
      Region region = regionManager_.getRegion("/a/b");
      LRUConfiguration config = (LRUConfiguration) region.getEvictionConfiguration();
      config.setMaxNodes(0);
      config.setTimeToLiveSeconds(0);
      config.setMaxAgeSeconds(1);
      region.setAddedNode(fqn1);
      region.setAddedNode(fqn2);
      region.setAddedNode(fqn3);
      algo_.process(region);
      assertEquals("Queue size #1: ", 3, algo_.getEvictionQueue().getNumberOfNodes());
      TestingUtil.sleepThread(2000);
      algo_.process(region);
      assertEquals("Queue size #2: ", 0, algo_.getEvictionQueue().getNumberOfNodes());
   }

   /**
    * MaxAgeSeconds = 2 with 3 nodes.
    *
    * @throws Exception
    */
   public void testMaxAgeSeconds2() throws Exception
   {
      Fqn fqn1 = Fqn.fromString("/a/b/c");
      Fqn fqn2 = Fqn.fromString("/a/b/d");
      Fqn fqn3 = Fqn.fromString("/a/b/e");
      Region region = regionManager_.getRegion("/a/b");
      LRUConfiguration config = (LRUConfiguration) region.getEvictionConfiguration();

      config.setMaxNodes(0);
      config.setTimeToLiveSeconds(0);
      config.setMaxAgeSeconds(2);
      region.setAddedNode(fqn1);
      region.setAddedNode(fqn2);
      region.setAddedNode(fqn3);
      algo_.process(region);
      assertEquals("Queue size #1: ", 3, algo_.getEvictionQueue().getNumberOfNodes());
      TestingUtil.sleepThread(1000);
      algo_.process(region);
      assertEquals("Queue size #2: ", 3, algo_.getEvictionQueue().getNumberOfNodes());
      TestingUtil.sleepThread(1000);
      algo_.process(region);
      assertEquals("Queue size #3: ", 0, algo_.getEvictionQueue().getNumberOfNodes());
   }

   /**
    * Generic combo case.
    */
   public void testCombo1()
   {
      Fqn fqn1 = Fqn.fromString("/a/b/c");
      Fqn fqn2 = Fqn.fromString("/a/b/d");
      Fqn fqn3 = Fqn.fromString("/a/b/e");
      Fqn fqn4 = Fqn.fromString("/a/b/f");
      Region region = regionManager_.getRegion("/a/b");
      LRUConfiguration config = (LRUConfiguration) region.getEvictionConfiguration();

      config.setMaxNodes(2);
      config.setTimeToLiveSeconds(1);
      config.setMaxAgeSeconds(3);
      region.setAddedNode(fqn1);
      region.setAddedNode(fqn2);
      region.setAddedNode(fqn4);
      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size #1: ", 2, algo_.getEvictionQueue().getNumberOfNodes());
      region.setAddedNode(fqn3);
      TestingUtil.sleepThread(2000);
      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size #2: ", 1, algo_.getEvictionQueue().getNumberOfNodes());

      TestingUtil.sleepThread(3000);
      // visit the node now to prevent the idle time from doing the pruning - node still gets pruned but by
      // max age.
      region.setVisitedNode(fqn3);
      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxAge: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size #3: ", 0, algo_.getEvictionQueue().getNumberOfNodes());
   }

   /**
    * Generic combo case with newly added node should be around.
    */
   public void testCombo2()
   {
      Fqn fqn1 = Fqn.fromString("/a/b/c");
      Fqn fqn2 = Fqn.fromString("/a/b/d");
      Fqn fqn3 = Fqn.fromString("/a/b/e");
      Region region = regionManager_.getRegion("/a/b");
      LRUConfiguration config = (LRUConfiguration) region.getEvictionConfiguration();

      config.setMaxNodes(2);
      config.setTimeToLiveSeconds(1);
      config.setMaxAgeSeconds(3);
      region.setAddedNode(fqn1);
      region.setAddedNode(fqn2);
      region.setRemovedNode(fqn2);
      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size #1: ", 1, algo_.getEvictionQueue().getNumberOfNodes());
      region.setAddedNode(fqn3);
      TestingUtil.sleepThread(2000);
      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testMaxNode: process failed " + e);
         e.printStackTrace();
      }
      assertEquals("Queue size #2: ", 1, algo_.getEvictionQueue().getNumberOfNodes());

      TestingUtil.sleepThread(3000);
      region.setVisitedNode(fqn3);
      try
      {
         algo_.process(region);
      }
      catch (EvictionException e)
      {
         fail("testCombo: process failed " + e);
         e.printStackTrace();
      }

      assertEquals("Queue size #3: ", 0, algo_.getEvictionQueue().getNumberOfNodes());
   }

   public void testEvictionSortOrder() throws Exception
   {
      Region region = regionManager_.getRegion("/a/b");
      LRUConfiguration config = (LRUConfiguration) region.getEvictionConfiguration();

      config.setMaxAgeSeconds(1000);
      config.setMaxNodes(0);
      config.setTimeToLiveSeconds(1000);

      for (int i = 0; i < 100; i++)
      {
         Fqn fqn = Fqn.fromString("/a/b/" + Integer.toString(i));
         region.setAddedNode(fqn);
      }

      algo_.process(region);

      TestingUtil.sleepThread(5000);

      for (int i = 0; i < 100; i++)
      {
         Fqn fqn = Fqn.fromString("/a/b/" + Integer.toString(i));
         if (i % 2 == 0)
         {
            region.setVisitedNode(fqn);
         }
      }

      algo_.process(region);

      LRUQueue queue = (LRUQueue) algo_.getEvictionQueue();

      NodeEntry ne;
      int count = 0;
      while ((ne = queue.getFirstLRUNodeEntry()) != null)
      {
         if (count < 50)
         {
            assertEquals(1, ne.getNumberOfNodeVisits());
         }
         else
         {
            assertEquals(2, ne.getNumberOfNodeVisits());
         }
         queue.removeNodeEntry(ne);
         count++;
      }

      for (int i = 0; i < 100; i++)
      {
         Fqn fqn = Fqn.fromString("/a/b/" + Integer.toString(i));
         region.setAddedNode(fqn);
      }

      algo_.process(region);
      long lastCreateTimestamp = 0;
      while ((ne = queue.getFirstMaxAgeNodeEntry()) != null)
      {
         assertTrue(ne.getCreationTimeStamp() >= lastCreateTimestamp);
         lastCreateTimestamp = ne.getCreationTimeStamp();
         queue.removeNodeEntry(ne);
      }
   }

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

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

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

}
