package org.infinispan.test.integration.as;

import static org.junit.Assert.assertEquals;

import java.io.IOException;

import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.SortedDocValuesField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.facet.FacetField;
import org.apache.lucene.facet.FacetResult;
import org.apache.lucene.facet.Facets;
import org.apache.lucene.facet.FacetsCollector;
import org.apache.lucene.facet.FacetsConfig;
import org.apache.lucene.facet.taxonomy.FastTaxonomyFacetCounts;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Terms;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.grouping.GroupingSearch;
import org.apache.lucene.search.grouping.TopGroups;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef;
import org.infinispan.Cache;
import org.infinispan.Version;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.lucene.directory.DirectoryBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.descriptor.api.Descriptors;
import org.jboss.shrinkwrap.descriptor.api.spec.se.manifest.ManifestDescriptor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;


/**
 * Test infinispan-lucene-directory server module
 *
 * @author gustavonalle
 * @since 7.0
 */
@RunWith(Arquillian.class)
public class InfinispanLuceneDirectoryIT {

   private Directory directory;
   private EmbeddedCacheManager cacheManager;
   private Cache<?, ?> cache;

   @Before
   public void setup() {
      cacheManager = new DefaultCacheManager();
      cacheManager.defineConfiguration("index", new ConfigurationBuilder().build());
      cache = cacheManager.getCache("index");
      directory = DirectoryBuilder.newDirectoryInstance(cache, cache, cache, "index").create();
   }

   @After
   public void tearDown() throws IOException {
      directory.close();
      cache.stop();
      cacheManager.stop();
   }

   @Deployment
   public static Archive<?> deployment() {
      String dependencies = deps(
            dep("org.infinispan", Version.getModuleSlot()),
            dep("org.infinispan.lucene-directory", Version.getModuleSlot())
      );
      StringAsset manifest = new StringAsset(
            Descriptors.create(ManifestDescriptor.class).attribute("Dependencies", dependencies).exportAsString());
      return ShrinkWrap.create(WebArchive.class, "lucene.war")
            .addClass(InfinispanLuceneDirectoryIT.class)
            .add(manifest, "META-INF/MANIFEST.MF");
   }

   @Test
   public void testCoreLucene() throws IOException {
      Document document = buildSimpleLuceneDoc("field1", "The quick brown fox jumped over the lazy dog");
      index(document);

      assertEquals(9, terms("field1").size());
   }

   @Test
   public void testQParserLucene() throws IOException, ParseException {
      Document document = buildSimpleLuceneDoc("field1", "The quick brown fox jumped over the lazy dog");
      index(document);
      Query matchingQuery = buildQuery("field1:box~");
      Query nonMatchingQuery = buildQuery("-field1:over");
      IndexSearcher indexSearcher = openSearcher();

      assertEquals(1, indexSearcher.search(matchingQuery, 1).totalHits);
      assertEquals(0, indexSearcher.search(nonMatchingQuery, 1).totalHits);
   }

   @Test
   public void testGrouping() throws IOException {
      Document doc1 = buildDocValueLuceneDoc("field1", "value1");
      Document doc2 = buildDocValueLuceneDoc("field1", "value1");
      Document doc3 = buildDocValueLuceneDoc("field1", "value2");
      index(doc1, doc2, doc3);
      GroupingSearch groupingSearch = new GroupingSearch("field1");
      TopGroups<Object> topGroups = groupingSearch.search(openSearcher(), new MatchAllDocsQuery(), 0, 10);

      assertEquals(3, topGroups.totalHitCount);
      assertEquals(2, topGroups.groups.length);
   }

   @Test
   public void testFaceting() throws Exception {
      Directory taxonomyDirectory =
            DirectoryBuilder.newDirectoryInstance(cache, cache, cache, "taxonomy").create();
      DirectoryTaxonomyWriter tw = new DirectoryTaxonomyWriter(taxonomyDirectory);
      FacetsConfig cfg = new FacetsConfig();
      Document doc1 = new Document();
      Document doc2 = new Document();
      Document doc3 = new Document();
      doc1.add(new FacetField("category", "c2"));
      doc2.add(new FacetField("category", "c2"));
      doc3.add(new FacetField("category", "c1"));
      index(cfg.build(tw, doc1), cfg.build(tw, doc2), cfg.build(tw, doc3));
      tw.close();
      DirectoryTaxonomyReader tr = new DirectoryTaxonomyReader(taxonomyDirectory);
      FacetsCollector fc = new FacetsCollector();
      FacetsCollector.search(openSearcher(), new MatchAllDocsQuery(), 10, fc);
      Facets facets = new FastTaxonomyFacetCounts(tr, cfg, fc);
      FacetResult category = facets.getTopChildren(10, "category");

      assertEquals(2, category.childCount);

   }

   private Query buildQuery(String q) throws ParseException {
      QueryParser queryParser = new QueryParser("field1", new WhitespaceAnalyzer());
      return queryParser.parse(q);
   }

   private IndexSearcher openSearcher() throws IOException {
      return new IndexSearcher(DirectoryReader.open(directory));
   }

   private Terms terms(String field) throws IOException {
      DirectoryReader reader = DirectoryReader.open(directory);
      LeafReaderContext readerContext = reader.getContext().leaves().iterator().next();
      return readerContext.reader().terms(field);
   }

   private void index(Document... documents) throws IOException {
      IndexWriterConfig iwc = new IndexWriterConfig(new WhitespaceAnalyzer());
      IndexWriter indexWriter = new IndexWriter(directory, iwc);
      for (Document doc : documents) {
         indexWriter.addDocument(doc);
      }
      indexWriter.close();
   }

   private Document buildDocValueLuceneDoc(String fieldName, String contents) {
      Document document = new Document();
      Field field = new SortedDocValuesField(fieldName, new BytesRef(contents));
      document.add(field);
      return document;
   }

   private Document buildSimpleLuceneDoc(String fieldName, String contents) {
      Document document = new Document();
      Field field = new TextField(fieldName, contents, Field.Store.NO);
      document.add(field);
      return document;
   }

   private static String dep(String name, String version) {
      return name + ":" + version + " services";
   }

   private static String deps(String... dep) {
      return String.join(",", dep);
   }

}
