/*
 * JBoss, Home of Professional Open Source
 * Copyright 2012 Red Hat Inc. and/or its affiliates and other
 * contributors as indicated by the @author tags. All rights reserved.
 * See the copyright.txt in the distribution for a full listing of
 * individual contributors.
 *
 * 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.infinispan.query.projection;

import org.apache.lucene.search.Query;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.query.CacheQuery;
import org.infinispan.query.FetchOptions;
import org.infinispan.query.ProjectionConstants;
import org.infinispan.query.ResultIterator;
import org.infinispan.query.Search;
import org.infinispan.query.SearchManager;
import org.infinispan.test.SingleCacheManagerTest;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.junit.Assert;
import org.testng.annotations.Test;

import java.util.Arrays;
import java.util.List;

@Test(groups = "functional", testName = "query.projection.ProjectionTest")
public class ProjectionTest extends SingleCacheManagerTest {

   private SearchManager searchManager;

   @Override
   protected EmbeddedCacheManager createCacheManager() throws Exception {
      ConfigurationBuilder cfg = getDefaultStandaloneCacheConfig(true);
      cfg.indexing().enable()
            .addProperty("default.directory_provider", "ram")
            .addProperty("lucene_version", "LUCENE_CURRENT");
      EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createCacheManager(cfg);
      Cache<Object, Object> cache = cacheManager.getCache();
      searchManager = Search.getSearchManager(cache);
      return cacheManager;
   }

   @Test
   public void testQueryProjectionWithSingleField() throws Exception {
      cache.put("1", new Foo("bar1", "baz1"));
      CacheQuery cacheQuery = createProjectionQuery("bar");
      assertQueryReturns(cacheQuery, new Object[] { "bar1" });
   }

   @Test
   public void testQueryProjectionWithMultipleFields() throws Exception {
      cache.put("1", new Foo("bar1", "baz1"));
      CacheQuery cacheQuery = createProjectionQuery("bar", "baz");
      assertQueryReturns(cacheQuery, new Object[] { "bar1", "baz1" });
   }

   @Test
   public void testKeyProjectionConstant() throws Exception {
      cache.put("1", new Foo("bar1", "baz1"));
      CacheQuery cacheQuery = createProjectionQuery(ProjectionConstants.KEY);
      assertQueryReturns(cacheQuery, new Object[] { "1" });
   }

   @Test
   public void testValueProjectionConstant() throws Exception {
      Foo foo = new Foo("bar1", "baz1");
      cache.put("1", foo);
      CacheQuery cacheQuery = createProjectionQuery(ProjectionConstants.VALUE);
      assertQueryReturns(cacheQuery, new Object[] { foo });
   }

   @Test
   public void testMixedProjections() throws Exception {
      Foo foo = new Foo("bar1", "baz4");
      cache.put("1", foo);
      CacheQuery cacheQuery = createProjectionQuery(
            ProjectionConstants.KEY,
            ProjectionConstants.VALUE,
            ProjectionConstants.VALUE,
            org.hibernate.search.ProjectionConstants.OBJECT_CLASS,
            "baz",
            "bar"
            );
      assertQueryReturns(cacheQuery, new Object[] { "1", foo, foo, foo.getClass(), foo.baz, foo.bar });
   }

   private CacheQuery createProjectionQuery(String... projection) {
      QueryBuilder queryBuilder = searchManager.buildQueryBuilderForClass(Foo.class).get();
      Query query = queryBuilder.keyword().onField("bar").matching("bar1").createQuery();
      CacheQuery cacheQuery = searchManager.getQuery(query);
      cacheQuery.projection(projection);
      return cacheQuery;
   }

   private void assertQueryReturns(CacheQuery cacheQuery, Object[] expected) {
      assertQueryListContains(cacheQuery.list(), expected);
      assertQueryIteratorContains(cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.EAGER)), expected);
      assertQueryIteratorContains(cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.LAZY)), expected);
   }

   private void assertQueryListContains(List list, Object[] expected) {
      assert list.size() == 1;
      Object[] array = (Object[]) list.get(0);
      Assert.assertArrayEquals(expected, array);
   }

   private void assertQueryIteratorContains(ResultIterator iterator, Object[] expected) {
      assert iterator.hasNext();
      Object[] array = (Object[]) iterator.next();
      assert Arrays.equals(array, expected);
      assert !iterator.hasNext();
   }

   @Indexed(index = "FooIndex")
   public static class Foo {
      private String bar;
      private String baz;

      public Foo(String bar, String baz) {
         this.bar = bar;
         this.baz = baz;
      }

      @Field(name = "bar", store = Store.YES)
      public String getBar() {
         return bar;
      }

      @Field(name = "baz", store = Store.YES)
      public String getBaz() {
         return baz;
      }

      @Override
      public boolean equals(Object o) {
         if (this == o)
            return true;
         if (o == null || getClass() != o.getClass())
            return false;

         Foo foo = (Foo) o;

         if (bar != null ? !bar.equals(foo.bar) : foo.bar != null)
            return false;
         if (baz != null ? !baz.equals(foo.baz) : foo.baz != null)
            return false;

         return true;
      }

      @Override
      public int hashCode() {
         return bar.hashCode();
      }
   }

}
