/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.exoplatform.services.jcr.api.core.query;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.query.InvalidQueryException;

/**
 * <code>UpperLowerCaseQueryTest</code> tests the functions fn:lower-case() and
 * fn:upper-case() in XPath, LOWER() and UPPER() in SQL and UpperCase and
 * LowerCase in JQOM.
 */
public class UpperLowerCaseQueryTest extends AbstractQueryTest
{

   //    /**
   //     * Maps operator strings to QueryObjectModelConstants.
   //     */
   //    private static final Map OPERATORS = new HashMap();
   //
   //    static {
   //        OPERATORS.put("=", new Integer(OPERATOR_EQUAL_TO));
   //        OPERATORS.put(">", new Integer(OPERATOR_GREATER_THAN));
   //        OPERATORS.put(">=", new Integer(OPERATOR_GREATER_THAN_OR_EQUAL_TO));
   //        OPERATORS.put("<", new Integer(OPERATOR_LESS_THAN));
   //        OPERATORS.put("<=", new Integer(OPERATOR_LESS_THAN_OR_EQUAL_TO));
   //        OPERATORS.put("like", new Integer(OPERATOR_LIKE));
   //        OPERATORS.put("!=", new Integer(OPERATOR_NOT_EQUAL_TO));
   //    }

   public void testEqualsGeneralComparison() throws RepositoryException
   {
      check(new String[]{"foo", "Foo", "fOO", "FOO", "fooBar", "fo", "fooo"}, "=", "foo", new boolean[]{true, true,
         true, true, false, false, false});
      check(new String[]{"foo"}, "=", "", new boolean[]{false});
      check(new String[]{""}, "=", "", new boolean[]{true});
   }

   public void testGreaterThanGeneralComparison() throws RepositoryException
   {
      // check edges
      check(new String[]{"foo", "FOO", "FoO", "fOo", "FON", "fon", "fo", "FO"}, ">", "foo", new boolean[]{false, false,
         false, false, false, false, false, false});
      check(new String[]{"foo ", "FOOa", "FoOO", "fOo1", "FOp", "foP", "fp", "g", "G"}, ">", "foo", new boolean[]{true,
         true, true, true, true, true, true, true, true});
      // check combinations
      check(new String[]{"foo", "fooo", "FooO", "fo", "FON", "fon"}, ">", "foo", new boolean[]{false, true, true,
         false, false, false});
   }

   public void testLessThanGeneralComparison() throws RepositoryException
   {
      // check edges
      check(new String[]{"foo", "FOO", "FoO", "fOo", "foOo", "foo ", "fooa", "fop"}, "<", "foo", new boolean[]{false,
         false, false, false, false, false, false, false});
      check(new String[]{"fo", "FOn", "FoN", "fO", "FO1", "fn", "fN", "E", "e"}, "<", "foo", new boolean[]{true, true,
         true, true, true, true, true, true, true});
      // check combinations
      check(new String[]{"foo", "fooo", "FooO", "fo", "FON", "fon"}, "<", "foo", new boolean[]{false, false, false,
         true, true, true});
   }

   public void testGreaterEqualsGeneralComparison() throws RepositoryException
   {
      // check edges
      check(new String[]{"fo", "FO", "Fon", "fONo", "FON", "fO", "fo", "FO"}, ">=", "foo", new boolean[]{false, false,
         false, false, false, false, false, false});
      check(new String[]{"foo", "FoO", "FoOO", "fOo1", "FOp", "foP", "fp", "g", "G"}, ">=", "foo", new boolean[]{true,
         true, true, true, true, true, true, true, true});
      // check combinations
      check(new String[]{"foo", "fooo", "FOo", "fo", "FON", "fon"}, ">=", "foo", new boolean[]{true, true, true, false,
         false, false});
   }

   public void testLessEqualsGeneralComparison() throws RepositoryException
   {
      // check edges
      check(new String[]{"fooo", "FOoo", "Fop", "fOpo", "FOP", "fOo ", "fp", "G"}, "<=", "foo", new boolean[]{false,
         false, false, false, false, false, false, false});
      check(new String[]{"foo", "FoO", "Foo", "fOn", "FO", "fo", "f", "E", "e"}, "<=", "foo", new boolean[]{true, true,
         true, true, true, true, true, true, true});
      // check combinations
      check(new String[]{"foo", "fo", "FOo", "fop", "FOP", "fooo"}, "<=", "foo", new boolean[]{true, true, true, false,
         false, false});
   }

   public void testNotEqualsGeneralComparison() throws RepositoryException
   {
      // check edges
      check(new String[]{"fooo", "FOoo", "Fop", "fOpo", "FOP", "fOo ", "fp", "G", ""}, "!=", "foo", new boolean[]{true,
         true, true, true, true, true, true, true, true});
      check(new String[]{"foo", "FoO", "Foo", "foO", "FOO"}, "!=", "foo", new boolean[]{false, false, false, false,
         false});
      // check combinations
      check(new String[]{"foo", "fo", "FOo", "fop", "FOP", "fooo"}, "!=", "foo", new boolean[]{false, true, false,
         true, true, true});
   }

   public void testLikeComparison() throws RepositoryException
   {
      check(new String[]{"foo", "Foo", "fOO", "FO "}, "like", "fo_", new boolean[]{true, true, true, true});
      check(new String[]{"foo", "Foo", "fOO", "FOO"}, "like", "f_o", new boolean[]{true, true, true, true});
      check(new String[]{"foo", "Foo", "fOO", " OO"}, "like", "_oo", new boolean[]{true, true, true, true});
      check(new String[]{"foo", "Foa", "fOO", "FO", "foRm", "fPo", "fno", "FPo", "Fno"}, "like", "fo%", new boolean[]{
         true, true, true, true, true, false, false, false, false});
   }

   public void testLikeComparisonRandom() throws RepositoryException
   {
      String abcd = "abcd";
      Random random = new Random();
      for (int i = 0; i < 50; i++)
      {
         String pattern = "";
         pattern += getRandomChar(abcd, random);
         pattern += getRandomChar(abcd, random);

         // create 10 random values with 4 characters
         String[] values = new String[10];
         boolean[] matches = new boolean[10];
         for (int n = 0; n < 10; n++)
         {
            // at least the first character always matches
            String value = String.valueOf(pattern.charAt(0));
            for (int r = 1; r < 4; r++)
            {
               char c = getRandomChar(abcd, random);
               if (random.nextBoolean())
               {
                  c = Character.toUpperCase(c);
               }
               value += c;
            }
            matches[n] = value.toLowerCase().startsWith(pattern);
            values[n] = value;
         }
         pattern += "%";
         check(values, "like", pattern, matches);
      }
   }

   public void testRangeWithEmptyString() throws RepositoryException
   {
      check(new String[]{" ", "a", "A", "1", "3", "!", "@"}, ">", "", new boolean[]{true, true, true, true, true, true,
         true});
      check(new String[]{"", "a", "A", "1", "3", "!", "@"}, ">=", "", new boolean[]{true, true, true, true, true, true,
         true});
      check(new String[]{"", "a", "A", "1", "3", "!", "@"}, "<", "", new boolean[]{false, false, false, false, false,
         false, false});
      check(new String[]{"", "a", "A", "1", "3", "!", "@"}, "<=", "", new boolean[]{true, false, false, false, false,
         false, false});
   }

   public void testInvalidQuery() throws RepositoryException
   {
      try
      {
         executeXPathQuery("//*[fn:lower-case(@foo) = 123]", new Node[]{});
         fail("must throw InvalidQueryException");
      }
      catch (InvalidQueryException e)
      {
         // correct
      }

      try
      {
         executeSQLQuery("select * from nt:base where LOWER(foo) = 123", new Node[]{});
         fail("must throw InvalidQueryException");
      }
      catch (InvalidQueryException e)
      {
         // correct
      }
   }

   public void testWrongCaseNeverMatches() throws RepositoryException
   {
      Node n = testRootNode.addNode("node");
      n.setProperty("foo", "Bar");
      testRootNode.save();
      executeXPathQuery(testPath + "/*[jcr:like(fn:lower-case(@foo), 'BA%')]", new Node[]{});
   }

   //----------------------------< internal >----------------------------------

   private void check(String[] values, String operation, String queryTerm, boolean[] matches)
      throws RepositoryException
   {
      if (values.length != matches.length)
      {
         throw new IllegalArgumentException("values and matches must have same length");
      }
      // create log message
      StringBuffer logMsg = new StringBuffer();
      logMsg.append("queryTerm: ").append(queryTerm);
      logMsg.append(" values: ");
      String separator = "";
      for (int i = 0; i < values.length; i++)
      {
         logMsg.append(separator);
         separator = ", ";
         if (matches[i])
         {
            logMsg.append("+");
         }
         else
         {
            logMsg.append("-");
         }
         logMsg.append(values[i]);
      }
      log.println(logMsg.toString());
      for (NodeIterator it = testRootNode.getNodes(); it.hasNext();)
      {
         it.nextNode().remove();
      }
      List matchingNodes = new ArrayList();
      for (int i = 0; i < values.length; i++)
      {
         Node n = testRootNode.addNode("node" + i);
         n.setProperty(propertyName1, values[i]);
         if (matches[i])
         {
            matchingNodes.add(n);
         }
      }
      testRootNode.save();

      Node[] nodes = (Node[])matchingNodes.toArray(new Node[matchingNodes.size()]);
      String sqlOperation = operation;
      if (operation.equals("!="))
      {
         sqlOperation = "<>";
      }

      // run queries with lower-case
      String xpath = testPath;
      if (operation.equals("like"))
      {
         xpath += "/*[jcr:like(fn:lower-case(@" + propertyName1 + "), '" + queryTerm.toLowerCase() + "')]";
      }
      else
      {
         xpath += "/*[fn:lower-case(@" + propertyName1 + ") " + operation + " '" + queryTerm.toLowerCase() + "']";
      }
      executeXPathQuery(xpath, nodes);

      String sql =
         "select * from nt:base where jcr:path like '" + testRoot + "/%' and LOWER(" + propertyName1 + ") "
            + sqlOperation + " '" + queryTerm.toLowerCase() + "'";
      executeSQLQuery(sql, nodes);

      // run queries with upper-case
      xpath = testPath;
      if (operation.equals("like"))
      {
         xpath += "/*[jcr:like(fn:upper-case(@" + propertyName1 + "), '" + queryTerm.toUpperCase() + "')]";
      }
      else
      {
         xpath += "/*[fn:upper-case(@" + propertyName1 + ") " + operation + " '" + queryTerm.toUpperCase() + "']";
      }
      executeXPathQuery(xpath, nodes);

      sql =
         "select * from nt:base where jcr:path like '" + testRoot + "/%' and UPPER(" + propertyName1 + ") "
            + sqlOperation + " '" + queryTerm.toUpperCase() + "'";
      executeSQLQuery(sql, nodes);

   }

   private char getRandomChar(String pool, Random random)
   {
      return pool.charAt(random.nextInt(pool.length()));
   }

   //    protected static int getOperatorForString(String operator) {
   //        Integer i = (Integer) OPERATORS.get(operator);
   //        if (i == null) {
   //            throw new IllegalArgumentException("unknown operator: " + operator);
   //        }
   //        return i.intValue();
   //    }
}
