/*
 * Copyright (C) 2003-2008 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.jcr.impl.core.query;

import org.exoplatform.services.jcr.JcrImplBaseTest;
import org.exoplatform.services.jcr.access.PermissionType;
import org.exoplatform.services.jcr.core.CredentialsImpl;
import org.exoplatform.services.jcr.core.ExtendedNode;
import org.exoplatform.services.jcr.impl.core.query.lucene.TwoWayRangeIterator;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

import java.util.HashMap;
import java.util.NoSuchElementException;
import java.util.Random;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;

/**
 * Created by The eXo Platform SAS.
 * 
 * @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a>
 * @version $Id: $
 */
public class TestTwoWayRangeIterator extends JcrImplBaseTest
{
   /**
    * Class logger.
    */
   private final Log log = ExoLogger.getLogger("exo.jcr.component.core.TestScoreNodeIterator");

   private Random random = new Random();
   
   private Session userSession;

   private final int TEST_NODES_COUNT = 100;

   private final int ATTEMPTS = 20;

   private String testRootNodeName = "testRoot";

   public void prepareRoot(Node testRoot) throws RepositoryException
   {
      for (int i = 0; i < TEST_NODES_COUNT; i++)
      {
         Node subnode = testRoot.addNode("TestNode" + String.format("%07d", i));
         subnode.setProperty("val", i);
         ExtendedNode subnode2 = (ExtendedNode)testRoot.addNode("TestNode2-" + String.format("%07d", i));
         subnode2.setProperty("val", i);
         subnode2.addMixin("exo:privilegeable");
         HashMap<String, String[]> perm = new HashMap<String, String[]>();
         perm.put("admin", PermissionType.ALL);
         subnode2.setPermissions(perm);
      }

   }

   @Override
   public void setUp() throws Exception
   {
      super.setUp();
      Node testRoot = root.addNode(testRootNodeName);
      prepareRoot(testRoot);
      root.save();
      userSession = repository.login(new CredentialsImpl("john", "exo".toCharArray()), "ws");
   }
   
   @Override
   protected void tearDown() throws Exception
   {
      userSession.logout();
      super.tearDown();
   }

   // Check random skipping from start of set
   public void testSkipFromStart() throws Exception
   {

      for (int i = 0; i < ATTEMPTS; i++)
      {

         final int skip = random.nextInt(TEST_NODES_COUNT - 2) + 1;
         log.info("Test skip " + skip);
         ScoreNodeTester tester = new ScoreNodeTester()
         {

            public TwoWayRangeIterator execute(Query query) throws RepositoryException
            {
               QueryResult result = query.execute();
               TwoWayRangeIterator iterator = (TwoWayRangeIterator)result.getNodes();
               iterator.skip(skip);
               return iterator;
            }

         };
         checkPosition(tester, skip);
      }
   }

   // Test skip zero.
   public void testSkipZero() throws RepositoryException
   {
      ScoreNodeTester tester = new ScoreNodeTester()
      {

         public TwoWayRangeIterator execute(Query query) throws RepositoryException
         {
            QueryResult result = query.execute();
            TwoWayRangeIterator iterator = (TwoWayRangeIterator)result.getNodes();
            iterator.skip(0);
            return iterator;
         }

      };
      checkPosition(tester, 0);
   }

   /**
    * Test NoSuchElementException
    * 
    * @throws Exception
    */
   public void testSkipBeforeFirst() throws Exception
   {

      for (int i = 0; i < ATTEMPTS; i++)
      {

         final int skip = random.nextInt(TEST_NODES_COUNT - 2) + 1;

         ScoreNodeTester tester = new ScoreNodeTester()
         {

            public TwoWayRangeIterator execute(Query query) throws RepositoryException
            {
               QueryResult result = query.execute();
               TwoWayRangeIterator iterator = (TwoWayRangeIterator)result.getNodes();
               log.info("testSkipBeforeFirst" + skip);
               iterator.skip(skip);
               assertEquals(skip, iterator.getPosition());
               try
               {
                  int skip2 = skip + 1 + random.nextInt(TEST_NODES_COUNT);
                  log.info("testSkipBeforeFirst skip2=" + skip2);
                  iterator.skipBack(skip2);
                  fail();
               }
               catch (NoSuchElementException e)
               {
                  // ok

               }
               return iterator;
            }

         };
         checkPosition(tester, skip);
      }
   }

   /**
    * Test IllegalArgumentException
    * 
    * @throws Exception
    */
   public void testSkipNegative() throws Exception
   {

      for (int i = 0; i < ATTEMPTS; i++)
      {

         final int skip = random.nextInt(TEST_NODES_COUNT - 2) + 1;

         ScoreNodeTester tester = new ScoreNodeTester()
         {

            public TwoWayRangeIterator execute(Query query) throws RepositoryException
            {
               QueryResult result = query.execute();
               TwoWayRangeIterator iterator = (TwoWayRangeIterator)result.getNodes();
               iterator.skip(skip);
               assertEquals(skip, iterator.getPosition());
               try
               {
                  iterator.skipBack(-1 * skip);
                  fail();
               }
               catch (IllegalArgumentException e)
               {
                  // ok

               }
               return iterator;
            }

         };
         checkPosition(tester, skip);
      }
   }

   /**
    * Test skip to first element
    * 
    * @throws Exception
    */
   public void testSkipAtFirst() throws Exception
   {

      for (int i = 0; i < ATTEMPTS; i++)
      {

         final int skip = random.nextInt(TEST_NODES_COUNT - 2) + 1;

         ScoreNodeTester tester = new ScoreNodeTester()
         {

            public TwoWayRangeIterator execute(Query query) throws RepositoryException
            {
               QueryResult result = query.execute();
               TwoWayRangeIterator iterator = (TwoWayRangeIterator)result.getNodes();
               iterator.skip(skip);
               assertEquals(skip, iterator.getPosition());
               iterator.skipBack(skip);
               return iterator;
            }

         };
         checkPosition(tester, 0);
      }
   }

   /**
    * Test random skip back
    * 
    * @throws Exception
    */
   public void testRandomSkipBack() throws Exception
   {
      for (int i = 0; i < ATTEMPTS; i++)
      {
         final int skip = random.nextInt(TEST_NODES_COUNT - 10);
         log.info("Test skip " + skip);
         ScoreNodeTester tester = new ScoreNodeTester()
         {

            public TwoWayRangeIterator execute(Query query) throws RepositoryException
            {
               QueryResult result = query.execute();
               TwoWayRangeIterator iterator = (TwoWayRangeIterator)result.getNodes();
               iterator.skip(5);
               iterator.skip(skip);
               iterator.skipBack(skip);
               return iterator;
            }

         };
         checkPosition(tester, 5);
      }
   }

   protected void checkPosition(ScoreNodeTester testAction, long expectedPosition) throws RepositoryException
   {

      QueryManager qm = userSession.getWorkspace().getQueryManager();

      // Doc order
      String strDocOrder = "select * from nt:unstructured where jcr:path like '/" + testRootNodeName + "/%'";

      Query selectChildQuery = qm.createQuery(strDocOrder, Query.SQL);
      TwoWayRangeIterator iterator = testAction.execute(selectChildQuery);
      assertTrue(iterator.hasNext());
      assertEquals(expectedPosition, iterator.getPosition());
      assertEquals(expectedPosition, ((Node)iterator.next()).getProperty("val").getLong());

      // Order by
      String strOrder = "select * from nt:unstructured where jcr:path like '/" + testRootNodeName + "/%' order by val";

      Query selectOrderChildQuery = qm.createQuery(strOrder, Query.SQL);
      TwoWayRangeIterator orderIterator = testAction.execute(selectOrderChildQuery);
      assertTrue(orderIterator.hasNext());
      assertEquals(expectedPosition, orderIterator.getPosition());
      Node nextNode = (Node)orderIterator.next();

      if (nextNode.getProperty("val").getLong() != expectedPosition)
      {
         orderIterator.skipBack(orderIterator.getPosition());
         while (orderIterator.hasNext())
         {
            Node nooode = (Node)orderIterator.next();
            log.info("node " + nooode.getPath() + " " + nooode.getProperty("val").getLong() + "  "
               + orderIterator.getPosition());

         }
      }
      assertEquals(expectedPosition, nextNode.getProperty("val").getLong());

   }

   private interface ScoreNodeTester
   {
      public TwoWayRangeIterator execute(Query query) throws RepositoryException;
   }

}
