package org.infinispan.hibernate.search;

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

import org.apache.lucene.index.Term;
import org.apache.lucene.search.TermQuery;
import org.hibernate.Transaction;
import org.hibernate.cfg.Environment;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.test.util.FullTextSessionBuilder;
import org.infinispan.hibernate.search.impl.DefaultCacheManagerService;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

/**
 * Verifies we're able to start from an existing index in Infinispan, stored in a CacheLoader. Requires a persistent
 * database so that we can shutdown the SessionFactory and start over again (simulated via a custom H2 service)
 *
 * @author Sanne Grinovero &lt;sanne@hibernate.org&gt; (C) 2012 Red Hat Inc.
 */
public class StoredIndexTest {

   private FullTextSessionBuilder node;

   @ClassRule
   public static TemporaryFolder temporaryFolderFolder = new TemporaryFolder();

   @Test
   public void testRestartingNode() {
      // Run 1 of application:
      startNode(true);
      try {
         storeEmail("there are some problems on this planet!");
         assertEmailsFound("some", 1);
      } finally {
         //shutdown
         stopNode();
      }

      // Restart same application:
      startNode(false);
      try {
         assertEmailsFound("some", 1);
         storeEmail("stored stuff should not vanish on this planet");
         assertEmailsFound("stuff", 1);
         assertEmailsFound("some", 1);
         assertEmailsFound("planet", 2);
      } finally {
         cleanupStoredIndex();
         stopNode();
      }
   }

   /**
    * Verifies a query on a specific term returns an expected amount of results. We do actually load entities from
    * database, so both database and index are tested.
    *
    * @param termMatch
    * @param expectedMatches
    */
   private void assertEmailsFound(String termMatch, int expectedMatches) {
      FullTextSession fullTextSession = node.openFullTextSession();
      try {
         TermQuery termQuery = new TermQuery(new Term("message", termMatch));
         FullTextQuery fullTextQuery = fullTextSession.createFullTextQuery(termQuery, SimpleEmail.class);
         List<SimpleEmail> list = fullTextQuery.getResultList();
         Assert.assertEquals(expectedMatches, list.size());
         if (expectedMatches != 0) {
            Assert.assertEquals("complaints-office@world.com", list.get(0).to);
         }
      } finally {
         fullTextSession.close();
      }
   }

   /**
    * Saves a new test email
    */
   private void storeEmail(String content) {
      SimpleEmail email = new SimpleEmail();
      email.to = "complaints-office@world.com";
      email.message = content;
      FullTextSession fullTextSession = node.openFullTextSession();
      Transaction transaction = fullTextSession.beginTransaction();
      fullTextSession.save(email);
      transaction.commit();
      fullTextSession.close();
   }

   /**
    * Creates a new SessionFactory using a shared H2 connection pool, and running an Infinispan Directory storing the
    * index in memory and write-through filesystem.
    *
    * @param createSchema set to false to not drop an existing schema
    */
   private void startNode(boolean createSchema) {
      node = new FullTextSessionBuilder()
            .setProperty("hibernate.search.default.directory_provider", "infinispan")
            .setProperty(DefaultCacheManagerService.INFINISPAN_CONFIGURATION_RESOURCENAME, "filesystem-loading-infinispan.xml")
                  // avoid killing the schema when you still have to run the second node:
            .setProperty(Environment.HBM2DDL_AUTO, createSchema ? "create" : "validate")
                  // share the same in-memory database connection pool
            .setProperty(
                  Environment.CONNECTION_PROVIDER,
                  org.infinispan.hibernate.search.ClusterSharedConnectionProvider.class.getName()
            )
            .addAnnotatedClass(SimpleEmail.class)
            .build();
   }

   /**
    * Closes the SessionFactory, SearchFactory and Infinispan CacheManagers. Only service to survive is the H2 in memory
    * database, and the data stored by the CacheLoader enabled in the Infinispan configuration.
    */
   public void stopNode() {
      if (node != null) {
         node.close();
         node = null;
      }
   }

   /**
    * This test uses and Infinispan CacheLoader writing in $tmp directory. Make sure we at least clear the index so that
    * subsequent runs of the same test won't fail.
    */
   private void cleanupStoredIndex() {
      FullTextSession fullTextSession = node.openFullTextSession();
      try {
         Transaction transaction = fullTextSession.beginTransaction();
         fullTextSession.purgeAll(SimpleEmail.class);
         transaction.commit();
      } finally {
         fullTextSession.close();
      }
   }

   /**
    * We need to use the custom H2 connector pool to make sure that when shutting down the first node we don't kill the
    * database with all data we need for the second phase of the test.
    */
   @BeforeClass
   public static void prepareConnectionPool() throws IOException {
      File tmpFolder = temporaryFolderFolder.newFolder();
      System.setProperty("tempTestDataDir", new File(tmpFolder, "data").getAbsolutePath());
      System.setProperty("tempTestGlobalStateDir", tmpFolder.getAbsolutePath());
      ClusterSharedConnectionProvider.realStart();
   }

   /**
    * Kills the static connection pool of H2 started by {@link #prepareConnectionPool()}
    */
   @AfterClass
   public static void shutdownConnectionPool() {
      ClusterSharedConnectionProvider.realStop();
   }

}
