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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.util.TestingUtil;
import static org.testng.AssertJUnit.assertEquals;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

/**
 * Tests basic group membership semantics
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
@Test(groups = "functional")
public class BuddyPoolBroadcastTest extends BuddyReplicationTestsBase
{
   private Log log = LogFactory.getLog(BuddyPoolBroadcastTest.class);

   private void checkConsistentPoolState(List<CacheSPI<Object, Object>> caches)
   {
      for (int i = 0; i < caches.size(); i++)
      {
         Map groupMap = caches.get(i).getBuddyManager().buddyPool;
         for (int j = 0; j < caches.size(); j++)
         {
            if (i != j)
            {
               Map groupMap2 = caches.get(j).getBuddyManager().buddyPool;
               for (CacheSPI cache : caches)
               {
                  assertEquals("Comparing contents of cache " + (i + 1) + " pool map with cache " + (j + 1), groupMap.get(cache), groupMap2.get(cache));
               }
            }
         }
      }
   }

   @AfterMethod(alwaysRun = true)
   public void tearDown() throws Exception
   {
      long st = System.currentTimeMillis();
      super.tearDown();
      System.out.println("Teardown: " + (System.currentTimeMillis() - st));
   }

   public void test2CachesWithPoolNames() throws Exception
   {
      log.error("Running test2CachesWithPoolNames");
      caches = createCaches(2, true);
      log.error("Created 2 caches");

      BuddyManager m = caches.get(0).getBuddyManager();
      Map groupMap = m.buddyPool;

      assertEquals("A", groupMap.get(caches.get(0).getLocalAddress()));
      assertEquals("B", groupMap.get(caches.get(1).getLocalAddress()));
   }

   public void test3CachesWithPoolNames() throws Exception
   {
      log.debug("Running test3CachesWithPoolNames");
      long st = System.currentTimeMillis();
      caches = createCaches(3, true);
      System.out.println("Setup: " + (System.currentTimeMillis() - st));
      st = System.currentTimeMillis();

      BuddyManager m = caches.get(0).getBuddyManager();
      Map groupMap = m.buddyPool;

      assertEquals("A", groupMap.get(caches.get(0).getLocalAddress()));
      assertEquals("B", groupMap.get(caches.get(1).getLocalAddress()));
      assertEquals("C", groupMap.get(caches.get(2).getLocalAddress()));
      System.out.println("Test: " + (System.currentTimeMillis() - st));
   }

   public void testBuddyPoolSync() throws Exception
   {
      log.debug("Running testBuddyPoolSync");
      caches = createCaches(3, true);

      Map map = caches.get(0).getBuddyManager().buddyPool;

      // first test the values
      assertEquals("Failed on cache 1", "A", map.get(caches.get(0).getLocalAddress()));
      assertEquals("Failed on cache 1", "B", map.get(caches.get(1).getLocalAddress()));
      assertEquals("Failed on cache 1", "C", map.get(caches.get(2).getLocalAddress()));

      // now test against each other
      checkConsistentPoolState(caches);
   }

   public void testChangingBuddyPoolMembership() throws Exception
   {
      log.debug("Running testChangingBuddyPoolMembership");
      caches = createCaches(3, true);

      Map map = caches.get(0).getBuddyManager().buddyPool;

      // first test the values
      assertEquals("Failed on cache 1", "A", map.get(caches.get(0).getLocalAddress()));
      assertEquals("Failed on cache 1", "B", map.get(caches.get(1).getLocalAddress()));
      assertEquals("Failed on cache 1", "C", map.get(caches.get(2).getLocalAddress()));

      // now test against each other
      checkConsistentPoolState(caches);

      caches.get(1).stop();
      caches.set(1, createCache(1, "Z"));

      TestingUtil.blockUntilViewsReceived(caches.toArray(new CacheSPI[0]), VIEW_BLOCK_TIMEOUT);
      TestingUtil.sleepThread(getSleepTimeout());

      // first test the values
      assertEquals("Failed on cache 1", "A", map.get(caches.get(0).getLocalAddress()));
      assertEquals("Failed on cache 1", "Z", map.get(caches.get(1).getLocalAddress()));
      assertEquals("Failed on cache 1", "C", map.get(caches.get(2).getLocalAddress()));

      // now test against each other
      checkConsistentPoolState(caches);
   }

   public void testConcurrency() throws Exception
   {
      log.debug("Running testConcurrency");
      int numCaches = 4;
      caches = new ArrayList<CacheSPI<Object, Object>>(4);
      CountDownLatch latch = new CountDownLatch(1);
      CacheStarter[] starters = new CacheStarter[numCaches];

      for (int i = 0; i < numCaches; i++)
      {
         caches.add(createCache(1, new String(new char[]{(char) ('A' + i)}), false, false));
         starters[i] = new CacheStarter("CacheStarter-" + i, latch, caches.get(i));
         starters[i].start();
      }

      // now have the lot start simultaneously
//      TestingUtil.sleepThread(500);
      latch.countDown();

      // allow a generous sleep time
      TestingUtil.blockUntilViewsReceived(caches.toArray(new CacheSPI[0]), 240000);
      TestingUtil.sleepThread(1000 * numCaches); // the max timeout we can expect is 2500ms * 10 nodes

      // and now look at the state of things.
      Map map = caches.get(0).getBuddyManager().buddyPool;
      System.out.println(map);
      for (int i = 0; i < numCaches; i++)
      {
         if (caches.get(i) != null)
            assertEquals("Failed on cache " + i + "(" + caches.get(i).getLocalAddress() + ")", new String(new char[]{(char) ('A' + i)}), map.get(caches.get(i).getLocalAddress()));
         else
            System.out.println("Cache " + i + " is null!??");
      }

      checkConsistentPoolState(caches);
   }

   public class CacheStarter extends Thread
   {
      private CountDownLatch latch;
      private CacheSPI cache;

      public CacheStarter(String name, CountDownLatch latch, CacheSPI cache)
      {
         super(name);
         this.latch = latch;
         this.cache = cache;
      }

      public void run()
      {
         try
         {
            latch.await();
            cache.start();
         }
         catch (Exception e)
         {
            e.printStackTrace();
         }
      }
   }

}
