package org.jboss.cache;

import org.jboss.cache.config.Configuration;
import org.jboss.cache.marshall.NodeData;
import org.jboss.cache.mock.MockNodesFixture;
import org.jboss.cache.optimistic.DataVersion;
import org.jboss.cache.optimistic.DefaultDataVersion;
import org.jboss.cache.buddyreplication.BuddyFqnTransformer;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

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

/**
 * Tests functionality from DataContainer.
 *
 * @author Mircea.Markus@jboss.com
 * @since 2.2
 */
@Test(groups = "unit")
public class DataContainerTest
{
   private DataContainerImpl container;
   private MockNodesFixture nodes;

   //end of node structure.

   @BeforeMethod
   public void setUp()
   {
      nodes = new MockNodesFixture();
      container = new DataContainerImpl();
      container.setRoot(nodes.root);
      container.setBuddyFqnTransformer(new BuddyFqnTransformer());
   }

   /**
    * tests {@link DataContainerImpl#peek(Fqn, boolean, boolean)} method
    */
   public void testPeekNodesSimple()
   {
      assert nodes.root == container.peek(Fqn.ROOT, true, true);
      assert nodes.adfgNode == container.peek(nodes.adfg, false, false);
      assert nodes.adfgNode == container.peek(nodes.adfg, false, true);
      assert nodes.adfgNode == container.peek(nodes.adfg, true, true);
   }

   /**
    * tests {@link DataContainerImpl#peek(Fqn, boolean, boolean)} for invalid nodes.
    */
   public void testPeekInvalidNodes()
   {
      nodes.adfgNode.setValid(false, false);
      assert null == container.peek(nodes.adfg, true, false);
      assert nodes.adfgNode == container.peek(nodes.adfg, true, true);
   }

   /**
    * tests {@link DataContainerImpl#peek(Fqn, boolean, boolean)} method for deleted nodes.
    */
   public void testPeekDeletedNodes()
   {
      nodes.adfgNode.markAsDeleted(true);
      assert null == container.peek(nodes.adfg, false, false);
      assert nodes.adfgNode == container.peek(nodes.adfg, true, false);
   }

   /**
    * tests {@link DataContainerImpl#peekVersioned(Fqn, org.jboss.cache.optimistic.DataVersion, boolean)} method.
    */
   public void testPeekVersioned()
   {
      assert nodes.adfgNode == container.peekVersioned(nodes.adfg, null, true) : "if data version is null this returns same value as peek(boolean, boolean)";

      //test pessimistic loking
      Configuration config = new Configuration();
      config.setNodeLockingOptimistic(false);
      DataVersion dataVersion = new DefaultDataVersion(2);
      container.setDependencies(config, null, null);
      assert nodes.adfgNode == container.peekVersioned(nodes.adfg, dataVersion, true) : "if NOT opt locking same value as peek(boolean, boolean) expected";

      //test optimistic locking with same version
      config.setNodeLockingOptimistic(true);
      DataVersion adfgDataVersion = new DefaultDataVersion(2);
      nodes.adfgNode.setVersion(adfgDataVersion);
      assert nodes.adfgNode == container.peekVersioned(nodes.adfg, adfgDataVersion, true) : "same version, expcting node to be returned";

      //test optimistic locking with a an older
      try
      {
         container.peekVersioned(nodes.adfg, new DefaultDataVersion(1), true);
         assert false : "exception expected as version changed.";
      }
      catch (CacheException e)
      {
         //expected
      }
   }

   /**
    * tests {@link DataContainerImpl#peekStrict(org.jboss.cache.transaction.GlobalTransaction, Fqn, boolean)}.
    */
   public void testPeekStrict()
   {
      assert nodes.adfgNode == container.peekStrict(null, nodes.adfg, true) : "if data version is null this returns same value as peek(boolean, boolean)";

      try
      {
         container.peekStrict(null, nodes.notExistent, true);
         assert false : "excpetion expected as node does not exist";
      }
      catch (Exception e)
      {
         //expected
      }
   }

   /**
    * tests {@link DataContainerImpl#exists(Fqn)}
    */
   public void testsExists()
   {
      assert container.exists(nodes.ab) : "ab exists";
      nodes.abNode.markAsDeleted(true);
      assert !container.exists(nodes.ab) : "ab marked as deleted";
      assert container.exists(nodes.ad);
      nodes.adNode.setValid(false, false);
      assert !container.exists(nodes.ade) : "its parent was marked as invalid";
   }

   /**
    * tests {@link DataContainerImpl#hasChildren(Fqn)}
    */
   public void testHasChildren()
   {
      assert container.hasChildren(nodes.ad) : " ade is a child of ad";
      assert !container.hasChildren(nodes.notExistent) : " this one does not exist";
      assert !container.hasChildren(nodes.adfg) : "this one exists but does not have children";
      nodes.adNode.setValid(false, false);
      assert !container.hasChildren(nodes.ad) : "ad exists and has children but is invalid";
   }

   /**
    * test {@link DataContainerImpl#buildNodeData(java.util.List, NodeSPI)}
    */
   public void testBuildNodeData()
   {
      nodes.abNode.put("ab", "ab");
      nodes.abcNode.put("abc", "abc");
      List<NodeData> result = new ArrayList<NodeData>();
      container.buildNodeData(result, nodes.abNode);
      assert result.size() == 2;
      assert result.contains(new NodeData(nodes.ab, nodes.abNode.getData()));
      assert result.contains(new NodeData(nodes.abc, nodes.abcNode.getData()));
   }

   /**
    * tests {@link DataContainerImpl#getNodesForEviction(Fqn, boolean)} in a nonrecursive scenario.
    */
   public void testGetNodesForEvictionNonrecursive()
   {
      //check for root first
      List<Fqn> result = container.getNodesForEviction(Fqn.ROOT, false);
      assert result.size() == 1 : "for root the direct children are considered for eviction";
      assert result.contains(nodes.a);

      //check normal
      result = container.getNodesForEviction(nodes.ad, false);
      assert result.size() == 1 : "one child expected";
      assert result.contains(nodes.ad);

      //check resident scenario
      nodes.adNode.setResident(true);
      result = container.getNodesForEviction(nodes.ad, false);
      assert result.size() == 0 : "no children expected";
   }

   /**
    * tests {@link DataContainerImpl#getNodesForEviction(Fqn, boolean)} in a recursive scenario.
    */
   public void testGetNodesForEvictionRecursive()
   {
      //check for root first
      List<Fqn> result = container.getNodesForEviction(Fqn.ROOT, true);
      assert result.size() == 8 : "all children are considered for eviction";

      //check normal
      result = container.getNodesForEviction(nodes.ad, true);
      assert result.size() == 5 : "five childrens expected";
      assert result.contains(nodes.ad);
      assert result.contains(nodes.ade);
      assert result.contains(nodes.adf);
      assert result.contains(nodes.adfh);
      assert result.contains(nodes.adfg);

      //check resident scenario
      nodes.adNode.setResident(true);
      result = container.getNodesForEviction(nodes.ad, true);
      assert result.size() == 4 : "only children expected";
      assert result.contains(nodes.ade);
      assert result.contains(nodes.adf);
      assert result.contains(nodes.adfh);
      assert result.contains(nodes.adfg);
   }

   /**
    * tests {@link DataContainerImpl#getNodesForEviction(Fqn, boolean)} in a recursive scenario.
    */
   public void testGetNodesForEvictionRecursiveNullNodes()
   {
      container.removeFromDataStructure(nodes.ad, true);
      //check for root first
      List<Fqn> result = container.getNodesForEviction(Fqn.ROOT, true);
      assert result.size() == 3 : "all children are considered for eviction";

      //check normal
      // this node does not exist!!  Should NOT throw a NPE.
      result = container.getNodesForEviction(nodes.ad, true);
      assert result.isEmpty() : "Should be empty";
   }


   /**
    * tests {@link DataContainerImpl#getNumberOfNodes()}
    */
   public void testGetNumberOfNodes()
   {
      assert container.getNumberOfNodes() == 8 : "eoght nodes expected";
   }

   /**
    * tests {@link DataContainerImpl#removeFromDataStructure(Fqn, boolean)} having skipMarkerCheck set to false.
    */
   public void removeFromDataStructureNoSkip1()
   {
      //check inexisten node
      assert !container.removeFromDataStructure(nodes.notExistent, false);

      //check root - all the subnodes should be deleted and marked as invalid, but the root itself
      nodes.root.markAsDeleted(true);
      assert container.removeFromDataStructure(Fqn.ROOT, false);
      assert !nodes.aNode.isValid();
      assert !nodes.abNode.isValid();
      assert !nodes.abcNode.isValid();
      assert !nodes.adNode.isValid();
      assert !nodes.adeNode.isValid();
      assert !nodes.adfNode.isValid();
      assert !nodes.adfgNode.isValid();
      assert !nodes.adfhNode.isValid();
      assert nodes.root.isValid();
   }

   /**
    * tests {@link DataContainerImpl#removeFromDataStructure(Fqn, boolean)} having skipMarkerCheck set to false.
    */
   public void removeFromDataStructureNoSkip2()
   {
      //check root - all the subnodes should be deleted and marked as invalid, but the root itself
      nodes.root.markAsDeleted(false);
      assert !container.removeFromDataStructure(Fqn.ROOT, false);

      //check a normal node
      nodes.adNode.markAsDeleted(true);
      assert container.removeFromDataStructure(nodes.ad, false);
      assert !nodes.adeNode.isValid();
      assert !nodes.adfNode.isValid();
      assert !nodes.adfhNode.isValid();
      assert !nodes.adfhNode.isValid();
   }

   /**
    * tests {@link DataContainerImpl#removeFromDataStructure(Fqn, boolean)} having skipMarkerCheck set to true.
    */
   public void removeFromDataStructureWithSkip()
   {
      //check inexisten node
      assert !container.removeFromDataStructure(nodes.notExistent, false);

      //check root - all the subnodes should be deleted and marked as invalid, but the root itself
      assert container.removeFromDataStructure(Fqn.ROOT, true);
      assert !nodes.aNode.isValid();
      assert !nodes.abNode.isValid();
      assert !nodes.abcNode.isValid();
      assert !nodes.adNode.isValid();
      assert !nodes.adeNode.isValid();
      assert !nodes.adfNode.isValid();
      assert !nodes.adfgNode.isValid();
      assert !nodes.adfhNode.isValid();
      assert nodes.root.isValid();
   }

   /**
    * tests {@link DataContainerImpl#evict(Fqn)}
    */
   public void testEvict()
   {
      //tests eviction of leaf nodes
      assert container.evict(nodes.abc);
      assert !nodes.abcNode.isValid();
      assert !nodes.abNode.hasChild("c");

      //test eviction of intermediate nodes
      nodes.adNode.put("key", "value");
      assert !container.evict(nodes.ad);
      assert nodes.adNode.isValid();
      assert nodes.adNode.getData().isEmpty();
      assert nodes.aNode.hasChild("d");
      assert nodes.adNode.hasChild("e");
   }

   /**
    * test {@link DataContainerImpl#createNodes(Fqn)}
    */
   public void testCreateNodes()
   {
      Object[] objects = container.createNodes(Fqn.fromString("/a/x/y/z"));
      List result = (List) objects[0];
      assert result.size() == 3;
      assert ((NodeSPI) result.get(0)).getFqn().equals(Fqn.fromString("/a/x"));
      assert ((NodeSPI) result.get(1)).getFqn().equals(Fqn.fromString("/a/x/y"));
      assert ((NodeSPI) result.get(2)).getFqn().equals(Fqn.fromString("/a/x/y/z"));
      NodeSPI target = (NodeSPI) objects[1];
      assert target != null;
      assert target.getFqn().toString().equals("/a/x/y/z");
   }
}
