/*
 * Copyright (C) 2010 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xcmis.search.query;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.xcmis.search.model.constraint.Operator.EQUAL_TO;
import static org.xcmis.search.model.constraint.Operator.GREATER_THAN;
import static org.xcmis.search.model.constraint.Operator.GREATER_THAN_OR_EQUAL_TO;
import static org.xcmis.search.model.constraint.Operator.LESS_THAN;
import static org.xcmis.search.model.constraint.Operator.LESS_THAN_OR_EQUAL_TO;
import static org.xcmis.search.model.constraint.Operator.LIKE;
import static org.xcmis.search.model.constraint.Operator.NOT_EQUAL_TO;

import org.apache.commons.io.FileUtils;
import org.apache.tika.exception.TikaException;
import org.apache.tika.mime.MimeTypeException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.xcmis.search.InvalidQueryException;
import org.xcmis.search.SearchService;
import org.xcmis.search.SearchServiceException;
import org.xcmis.search.config.IndexConfiguration;
import org.xcmis.search.config.SearchServiceConfiguration;
import org.xcmis.search.content.InMemorySchema;
import org.xcmis.search.content.Schema;
import org.xcmis.search.content.InMemorySchema.Builder;
import org.xcmis.search.content.Schema.Column;
import org.xcmis.search.content.Schema.Table;
import org.xcmis.search.content.interceptors.ContentReaderInterceptor;
import org.xcmis.search.lucene.content.SchemaTableResolver;
import org.xcmis.search.model.Query;
import org.xcmis.search.model.constraint.Operator;
import org.xcmis.search.model.source.SelectorName;
import org.xcmis.search.query.QueryBuilder.ComparisonBuilder;
import org.xcmis.search.query.QueryBuilder.ConstraintBuilder;
import org.xcmis.search.value.CastSystem;
import org.xcmis.search.value.NameConverter;
import org.xcmis.search.value.PropertyType;
import org.xcmis.search.value.ToStringNameConverter;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author <a href="mailto:Sergey.Kabashnyuk@exoplatform.org">Sergey
 *         Kabashnyuk</a>
 * @version $Id: exo-jboss-codetemplates.xml 34360 2009-07-22 23:58:59Z ksm $
 *          "String"
 */
public class AvailableQueryOperatorsTest
{
   private QueryBuilder builder;

   private File tempDir;

   private Builder schemaBuilder;

   private Schema schema;

   private QueryBuilder qBuilder;

   private SearchService searchService;

   private final static String TABLE_NAME = "someTable";

   @Before
   public void beforeEach() throws SearchServiceException, MimeTypeException, IOException, TikaException
   {
      builder = new QueryBuilder(mock(CastSystem.class));

      schemaBuilder = InMemorySchema.createBuilder();

      schema =
         schemaBuilder
            .addTable(TABLE_NAME, "column1")
            //
            .addColumn(TABLE_NAME, "booleanColumn", PropertyType.STRING, true, new Operator[]{EQUAL_TO})
            //
            .addColumn(TABLE_NAME, "idColumn", PropertyType.STRING, true, new Operator[]{EQUAL_TO, NOT_EQUAL_TO})
            //
            .addColumn(TABLE_NAME, "integerColumn", PropertyType.STRING, true, new Operator[]{EQUAL_TO, GREATER_THAN, //
               GREATER_THAN_OR_EQUAL_TO, LESS_THAN, LESS_THAN_OR_EQUAL_TO, NOT_EQUAL_TO})
            //
            .addColumn(
               TABLE_NAME,
               "dateTimeColumn",
               PropertyType.STRING,
               true,
               new Operator[]{EQUAL_TO, GREATER_THAN, GREATER_THAN_OR_EQUAL_TO, LESS_THAN, LESS_THAN_OR_EQUAL_TO,
                  NOT_EQUAL_TO})
            .addColumn(
               TABLE_NAME,
               "decimalColumn",
               PropertyType.STRING,
               true,
               new Operator[]{EQUAL_TO, GREATER_THAN, GREATER_THAN_OR_EQUAL_TO, LESS_THAN, LESS_THAN_OR_EQUAL_TO,
                  NOT_EQUAL_TO})
            .addColumn(TABLE_NAME, "htmlColumn", PropertyType.STRING, true,
               new Operator[]{EQUAL_TO, GREATER_THAN, LIKE, NOT_EQUAL_TO})
            .addColumn(TABLE_NAME, "stringColumn", PropertyType.STRING, true,
               new Operator[]{EQUAL_TO, GREATER_THAN, LIKE, NOT_EQUAL_TO})

            .addColumn(TABLE_NAME, "uriColumn", PropertyType.STRING, true, new Operator[]{EQUAL_TO, NOT_EQUAL_TO, LIKE})
            .build();

      tempDir = new File(System.getProperty("java.io.tmpdir"), "search-service");
      if (tempDir.exists())
      {
         assertThat(FileUtils.deleteQuietly(tempDir), is(true));
      }
      assertThat(tempDir.mkdirs(), is(true));

      qBuilder = new QueryBuilder(mock(CastSystem.class));
      //value
      NameConverter<String> nameConverter = new ToStringNameConverter();
      SchemaTableResolver tableResolver = new SchemaTableResolver(nameConverter, schema);

      //index configuration
      IndexConfiguration indexConfuration = new IndexConfiguration("rootParentUuid", "rootUuid");

      //search service configuration
      SearchServiceConfiguration configuration =
         new SearchServiceConfiguration(schema, tableResolver, mock(ContentReaderInterceptor.class), indexConfuration);
      searchService = new SearchService(configuration);
      searchService.start();

   }

   @Test
   public void availableQueryOperatorsTest() throws QueryExecutionException, InvalidQueryException
   {
      Table table = schema.getTable(new SelectorName(TABLE_NAME));

      for (Column column : table.getColumns())
      {
         checkValid(column);
         checkInValid(column);
      }
   }

   /**
    * Check invalid operator's
    * 
    * @param column
    * @throws QueryExecutionException
    */
   private void checkInValid(Column column) throws QueryExecutionException
   {

      Operator[] unAvailableQueryOperators = getUnAvailableQueryOperators(column.getAvailableQueryOperators());
      for (Operator operator : unAvailableQueryOperators)
      {
         ComparisonBuilder q =
            qBuilder.select(column.getName()).from(TABLE_NAME).where().propertyValue(TABLE_NAME, column.getName());

         ConstraintBuilder resultQ = null;
         switch (operator)
         {

            case EQUAL_TO :
               resultQ = q.isEqualTo().literal(new Long(1));
               break;
            case GREATER_THAN :
               resultQ = q.isGreaterThan().literal(new Long(1));
               break;
            case GREATER_THAN_OR_EQUAL_TO :
               resultQ = q.isGreaterThanOrEqualTo().literal(new Long(1));
               break;
            case LESS_THAN :
               resultQ = q.isLessThan().literal(new Long(1));
               break;
            case LESS_THAN_OR_EQUAL_TO :
               resultQ = q.isLessThanOrEqualTo().literal(new Long(1));
               break;
            case LIKE :
               resultQ = q.isLike().literal(new Long(1));
               break;
            case NOT_EQUAL_TO :
               resultQ = q.isNotEqualTo().literal(new Long(1));
               break;
            default :
               Assert.fail("unknown operator " + operator);
               break;
         }
         Query query = resultQ.end().query();
         try
         {
            searchService.execute(query);
            Assert.fail("InvalidQueryException should be thrown for invalid operator " + operator + " for columnt ='"
               + column.getName() + "'");
         }
         catch (InvalidQueryException e)
         {
            //ok
         }
      }
   }

   /**
    * Return the array of unAvailableQueryOperators
    * 
    * @param availableQueryOperators
    * @return
    */
   private Operator[] getUnAvailableQueryOperators(Operator[] availableQueryOperators)
   {
      List<Operator> result = new ArrayList<Operator>();
      for (Operator operator : Operator.ALL)
      {
         boolean isValid = false;
         for (Operator availableQueryOperator : availableQueryOperators)
         {
            if (operator.equals(availableQueryOperator))
            {
               isValid = true;
               break;
            }
         }
         if (!isValid)
         {
            result.add(operator);
         }
      }
      return result.toArray(new Operator[result.size()]);
   }

   /**
    * Check valid operator's
    * 
    * @param column
    * @throws InvalidQueryException
    * @throws QueryExecutionException
    */
   private void checkValid(Column column) throws QueryExecutionException, InvalidQueryException
   {

      for (Operator operator : column.getAvailableQueryOperators())
      {
         ComparisonBuilder q =
            qBuilder.select(column.getName()).from(TABLE_NAME).where().propertyValue(TABLE_NAME, column.getName());

         ConstraintBuilder resultQ = null;
         switch (operator)
         {

            case EQUAL_TO :
               resultQ = q.isEqualTo().literal(new Long(1));
               break;
            case GREATER_THAN :
               resultQ = q.isGreaterThan().literal(new Long(1));
               break;
            case GREATER_THAN_OR_EQUAL_TO :
               resultQ = q.isGreaterThanOrEqualTo().literal(new Long(1));
               break;
            case LESS_THAN :
               resultQ = q.isLessThan().literal(new Long(1));
               break;
            case LESS_THAN_OR_EQUAL_TO :
               resultQ = q.isLessThanOrEqualTo().literal(new Long(1));
               break;
            case LIKE :
               resultQ = q.isLike().literal(new Long(1));
               break;
            case NOT_EQUAL_TO :
               resultQ = q.isNotEqualTo().literal(new Long(1));
               break;
            default :
               Assert.fail("unknown operator " + operator);
               break;
         }
         Query query = resultQ.end().query();
         searchService.execute(query);
      }
   }
}
