/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.buddyreplication;

import org.jboss.cache.Cache;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.config.BuddyReplicationConfig;
import org.jboss.cache.config.CacheLoaderConfig;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import org.jboss.cache.factories.XmlConfigurationParser;
import org.jboss.cache.loader.CacheLoader;
import org.jboss.cache.loader.CacheLoaderManager;
import org.jboss.cache.loader.DummyInMemoryCacheLoader;
import org.jboss.cache.util.TestingUtil;
import org.jboss.cache.xml.XmlHelper;
import org.jgroups.Address;
import static org.testng.AssertJUnit.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import org.w3c.dom.Element;

import javax.transaction.TransactionManager;
import java.util.ArrayList;
import java.util.List;

/**
 * Base class for BR tests
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
@Test(groups = {"functional", "jgroups"})
public abstract class BuddyReplicationTestsBase
{
   protected List<CacheSPI<Object, Object>> caches;
   protected BuddyFqnTransformer fqnTransformer = new BuddyFqnTransformer();

   @AfterMethod(alwaysRun = true)
   public void tearDown() throws Exception
   {
      System.out.println("***** TEARING DOWN *****");
      System.setProperty("org.jboss.cache.shutdown.force", "true");
      if (caches != null)
      {
         // an optimisation to aid the progress of unit tests, especially in the case of TCP connections.  Note that this
         // is NOT necessary in live systems since each cache would typically be in a separate JVM.
         for (CacheSPI c : caches)
         {
            if (c != null && c.getBuddyManager() != null) c.getBuddyManager().stop();
         }

         for (CacheSPI c : caches)
         {
            if (c != null)
            {
               TransactionManager tm = c.getTransactionManager();
               if (tm != null)
               {
                  try
                  {
                     if (tm.getTransaction() != null) tm.rollback();
                  }
                  catch (Exception e)
                  {
                     // error rolling back gtx2EntryMap
                     e.printStackTrace();
                  }
               }

               CacheLoaderManager clm = c.getCacheLoaderManager();
               if (clm != null)
               {
                  CacheLoader cl = c.getCacheLoaderManager().getCacheLoader();
                  try
                  {
                     if (cl != null) cl.remove(Fqn.ROOT);
                  }
                  catch (Exception e)
                  {
                     // unable to clean cache loader
                     e.printStackTrace();
                  }
               }

               c.stop();
               c = null;
            }
         }
      }
      caches = null;
      System.gc();
   }

   protected final static int VIEW_BLOCK_TIMEOUT = 5000;

   protected CacheSPI<Object, Object> createCache(int numBuddies, String buddyPoolName) throws Exception
   {
      return createCache(numBuddies, buddyPoolName, false, true);
   }

   protected CacheSPI<?, ?> createCache(int numBuddies, String buddyPoolName, boolean useDataGravitation) throws Exception
   {
      return createCache(numBuddies, buddyPoolName, useDataGravitation, true);
   }

   protected CacheSPI<Object, Object> createCache(int numBuddies, String buddyPoolName, boolean useDataGravitation, boolean start) throws Exception
   {
      return createCache(false, numBuddies, buddyPoolName, useDataGravitation, true, start);
   }

   protected CacheSPI<Object, Object> createCache(boolean optimisticLocks, int numBuddies, String buddyPoolName, boolean useDataGravitation, boolean start) throws Exception
   {
      return createCache(optimisticLocks, numBuddies, buddyPoolName, useDataGravitation, true, start);
   }

   protected CacheSPI<?, ?> createCache(int numBuddies, String buddyPoolName, boolean useDataGravitation, boolean removeOnFind, boolean start) throws Exception
   {
      return createCache(false, numBuddies, buddyPoolName, useDataGravitation, removeOnFind, start);
   }

   protected CacheSPI<Object, Object> createCache(boolean optimisticLocks, int numBuddies, String buddyPoolName, boolean useDataGravitation, boolean removeOnFind, boolean start) throws Exception
   {
      CacheSPI<Object, Object> c = (CacheSPI<Object, Object>) new DefaultCacheFactory<Object, Object>().createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC, false, false, true), false);
      c.getConfiguration().setClusterName("BuddyReplicationTest");
      // basic config
      String xmlString = "<config><buddyReplicationEnabled>true</buddyReplicationEnabled>\n" +
            "<buddyCommunicationTimeout>500000</buddyCommunicationTimeout>\n" +
            "          <buddyLocatorClass>org.jboss.cache.buddyreplication.NextMemberBuddyLocator</buddyLocatorClass>\n" +
            "          <autoDataGravitation>" + useDataGravitation + "</autoDataGravitation>\n" +
            "          <dataGravitationRemoveOnFind>" + removeOnFind + "</dataGravitationRemoveOnFind>\n" +
            "          <buddyLocatorProperties>numBuddies = " + numBuddies + "</buddyLocatorProperties>\n";

      if (buddyPoolName != null) xmlString += "<buddyPoolName>" + buddyPoolName + "</buddyPoolName>";
      xmlString += "</config>";
      Element element = XmlHelper.stringToElement(xmlString);
      BuddyReplicationConfig config = XmlConfigurationParser.parseBuddyReplicationConfig(element);
      c.getConfiguration().setBuddyReplicationConfig(config);

      c.getConfiguration().setFetchInMemoryState(true);
      if (optimisticLocks)
      {
         c.getConfiguration().setNodeLockingScheme(Configuration.NodeLockingScheme.OPTIMISTIC);
      }
      c.getConfiguration().setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
      c.getConfiguration().setSyncCommitPhase(true);// helps track down breakages

      // Call the hook that allows mux integration if that's what the test wants
      configureMultiplexer(c);

      if (start)
      {
         c.start();
         validateMultiplexer(c);
      }
      return c;
   }

   /**
    * Provides a hook for multiplexer integration. This default implementation
    * is a no-op; subclasses that test mux integration would override
    * to integrate the given cache with a multiplexer.
    * <p/>
    * param cache a cache that has been configured but not yet created.
    */
   protected void configureMultiplexer(Cache cache) throws Exception
   {
      // default does nothing
   }

   /**
    * Provides a hook to check that the cache's channel came from the
    * multiplexer, or not, as expected.  This default impl asserts that
    * the channel did not come from the multiplexer.
    *
    * @param cache a cache that has already been started
    */
   protected void validateMultiplexer(Cache cache)
   {
      assertFalse("Cache is not using multiplexer", cache.getConfiguration().isUsingMultiplexer());
   }

   protected List<CacheSPI<Object, Object>> createCaches(int numCaches, boolean useBuddyPool) throws Exception
   {
      return createCaches(1, numCaches, useBuddyPool, false);
   }

   protected List<CacheSPI<Object, Object>> createCaches(int numCaches, boolean useBuddyPool, boolean useDataGravitation, boolean optimisticLocks) throws Exception
   {
      return createCaches(1, numCaches, useBuddyPool, useDataGravitation, optimisticLocks);
   }

   protected List<CacheSPI<Object, Object>> createCaches(int numCaches, boolean useBuddyPool, boolean useDataGravitation) throws Exception
   {
      return createCaches(1, numCaches, useBuddyPool, useDataGravitation);
   }

   protected List<CacheSPI<Object, Object>> createCachesWithCacheLoader(int numCaches, boolean useDataGravitation, boolean removeOnFind, boolean passivation) throws Exception
   {
      return this.createCachesWithCacheLoader(numCaches, useDataGravitation, removeOnFind, passivation, false);
   }

   protected List<CacheSPI<Object, Object>> createCachesWithCacheLoader(int numCaches, boolean useDataGravitation, boolean removeOnFind, boolean passivation, boolean fetchPersistent) throws Exception
   {
      List<CacheSPI<Object, Object>> caches = new ArrayList<CacheSPI<Object, Object>>();
      for (int i = 0; i < numCaches; i++)
      {
         caches.add(createCacheWithCacheLoader(useDataGravitation, removeOnFind, passivation, fetchPersistent, true));
      }

      // allow some time for the caches to start up and discover each other
      TestingUtil.blockUntilViewsReceived(caches.toArray(new Cache[0]), VIEW_BLOCK_TIMEOUT);
      TestingUtil.sleepThread(getSleepTimeout());
      return caches;
   }

   protected CacheSPI createCacheWithCacheLoader(boolean useDataGravitation, boolean removeOnFind, boolean passivation, boolean fetchPersistent, boolean start) throws Exception
   {
      CacheSPI cache = createCache(1, null, useDataGravitation, removeOnFind, false);

      String cloader = "<config>\n" +
            "<passivation>" + passivation + "</passivation>\n" +
            "<preload></preload>\n" +

            "<cacheloader>\n" +
            "<class>" + DummyInMemoryCacheLoader.class.getName() + "</class>\n" +
            "<async>false</async>\n" +
            "<shared>false</shared>\n" +
            "<fetchPersistentState>" + fetchPersistent + "</fetchPersistentState>\n" +
            "</cacheloader>\n" +
            "</config>";

      Element element = XmlHelper.stringToElement(cloader);
      CacheLoaderConfig cfg = XmlConfigurationParser.parseCacheLoaderConfig(element);
      cache.getConfiguration().setCacheLoaderConfig(cfg);
      if (start)
      {
         cache.start();
      }

      return cache;
   }

   protected List<CacheSPI<Object, Object>> createCaches(int numBuddies, int numCaches, boolean useBuddyPool) throws Exception
   {
      return createCaches(numBuddies, numCaches, useBuddyPool, false);
   }

   protected List<CacheSPI<Object, Object>> createCaches(int numBuddies, int numCaches, boolean useBuddyPool, boolean useDataGravitation) throws Exception
   {
      return createCaches(numBuddies, numCaches, useBuddyPool, useDataGravitation, false);
   }

   protected List<CacheSPI<Object, Object>> createCaches(int numBuddies, int numCaches, boolean useBuddyPool, boolean useDataGravitation, boolean optimisticLocks) throws Exception
   {
      return createCaches(numBuddies, numCaches, useBuddyPool, useDataGravitation, optimisticLocks, true);
   }

   protected List<CacheSPI<Object, Object>> createCaches(int numBuddies, int numCaches, boolean useBuddyPool, boolean useDataGravitation, boolean optimisticLocks, boolean start) throws Exception
   {
      List<CacheSPI<Object, Object>> caches = new ArrayList<CacheSPI<Object, Object>>(numCaches);
      for (int i = 0; i < numCaches; i++)
         caches.add(createCache(optimisticLocks, numBuddies, useBuddyPool ? Character.toString((char) ('A' + i)) : null, useDataGravitation, start));

      if (start)
      {
         // allow some time for the caches to start up and discover each other
         TestingUtil.blockUntilViewsReceived(caches.toArray(new Cache[0]), VIEW_BLOCK_TIMEOUT);
         TestingUtil.sleepThread(getSleepTimeout());
      }

      return caches;
   }

   protected void printBuddyGroup(Cache cache)
   {
      BuddyManager bm = ((CacheSPI) cache).getBuddyManager();
      BuddyGroup bg = bm.buddyGroup;
      System.out.println("*** " + bg);
      System.out.println("    Groups I participate in: " + bm.buddyGroupsIParticipateIn.keySet());
   }

   /**
    * This is to allow for any state transfers involved (when assigning a buddy) to complete
    */
   protected int getSleepTimeout()
   {
      return 1000;
   }

   protected void assertIsBuddy(Cache dataOwner, Cache buddy, boolean onlyBuddy)
   {
      Address dataOwnerLocalAddress = dataOwner.getLocalAddress();
      Address buddyLocalAddress = buddy.getLocalAddress();

      System.out.println("*** assert with groups.  Testing that " + buddyLocalAddress + " is a buddy for owner " + dataOwnerLocalAddress + " only buddy? " + onlyBuddy);
      printBuddyGroup(dataOwner);

      BuddyManager dataOwnerBuddyManager = ((CacheSPI) dataOwner).getBuddyManager();
      BuddyManager buddyBuddyManager = ((CacheSPI) buddy).getBuddyManager();

      // lets test things on the data owner's side of things
      if (onlyBuddy) assertEquals("Should only have one buddy", 1, dataOwnerBuddyManager.getBuddyAddresses().size());

      assertTrue(buddyLocalAddress + " should be a buddy to " + dataOwnerLocalAddress, dataOwnerBuddyManager.getBuddyAddresses().contains(buddyLocalAddress));

      // and now on the buddy end
      BuddyGroup group = buddyBuddyManager.buddyGroupsIParticipateIn.get(dataOwnerLocalAddress);
      System.out.println("*** Groups I participate in: " + buddyBuddyManager.buddyGroupsIParticipateIn);
      System.out.println("*** Buddy's version of dataOwner's group " + group);

      assertTrue("buddy's list of groups it participates in should contain data owner's group name", buddyBuddyManager.buddyGroupsIParticipateIn.containsKey(dataOwnerLocalAddress));
      if (onlyBuddy) assertEquals(1, group.getBuddies().size());
      assertTrue(buddyLocalAddress + " should be a buddy to " + group.getGroupName(), group.getBuddies().contains(buddyLocalAddress));
   }

   protected void assertNoLocks(List<CacheSPI<Object, Object>> caches)
   {
      for (Cache cache : caches)
      {
         if (cache != null) assertEquals(0, ((CacheSPI) cache).getNumberOfLocksHeld());
      }
   }

}
