package org.infinispan.notifications;

import java.util.List;

import org.infinispan.Cache;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified;
import org.infinispan.notifications.cachelistener.event.CacheEntryEvent;
import org.infinispan.remoting.transport.Address;
import org.infinispan.test.MultipleCacheManagersTest;
import org.testng.annotations.Test;

/**
 * Used to verify which nodes are going to receive events in case it's configured
 * as DIST: all key owners and the node which is performing the operation will receive
 * a notification.
 * 
 * @author Sanne Grinovero <sanne@hibernate.org> (C) 2011 Red Hat Inc.
 * @since 5.0
 */
@Test(groups = "functional", testName = "notifications.DistListenerTest")
public class DistListenerTest extends MultipleCacheManagersTest {

   private TestListener listener;

   @Override
   protected void createCacheManagers() throws Throwable {
      createCluster(getDefaultClusteredCacheConfig(
            CacheMode.DIST_SYNC, true), 3);
      waitForClusterToForm();
   }
   
   public void testRemoteGet() {
      final String key1 = this.getClass().getName() + "K1";

      List<Address> owners = cache(0).getAdvancedCache().getDistributionManager().locate(key1);

      assert owners.size() == 2: "Key should have 2 owners";

      Cache<String, String> owner1 = getCacheForAddress(owners.get(0));
      Cache<String, String> owner2 = getCacheForAddress(owners.get(1));
      assert owner1 != owner2;
      Cache<String, String> nonOwner = null;
      for (int i=0; i<3; i++) {
         if (this.<String, String>cache(i) != owner1
               && this.<String, String>cache(i) != owner2) {
            nonOwner = cache(i);
            break;
         }
      }
      assert nonOwner != null;
      
      listener = new TestListener();
      
      // test owner puts and listens:
      assertCreated(false);
      assertModified(false);
      owner1.addListener(listener);
      owner1.put(key1, "hello");
      assertModified(true);
      assertCreated(true);
      assertCreated(false);
      assertModified(false);
      owner1.put(key1, "hello");
      assertModified(true);
      assertCreated(false);
      
      // now de-register:
      owner1.removeListener(listener);
      owner1.put(key1, "hello");
      assertModified(false);
      assertCreated(false);
      
      // put on non-owner and listens on owner:
      owner1.addListener(listener);
      nonOwner.put(key1, "hello");
      assertModified(true);
      owner1.removeListener(listener);
      assertModified(false);
      
      //listen on non-owner:
      nonOwner.addListener(listener);
      nonOwner.put(key1, "hello");
      assertModified(true);
      
      //listen on non-owner non-putting:
      owner1.put(key1, "hello");
      assertModified(false);
   }
   
   private void assertCreated(boolean b) {
      assert listener.created == b;
      listener.created = false;
   }
   
   private void assertModified(boolean b) {
      assert listener.modified == b;
      listener.modified = false;
   }

   private <K, V> Cache<K, V> getCacheForAddress(Address a) {
      for (Cache<K, V> c: this.<K, V>caches())
         if (c.getAdvancedCache().getRpcManager().getAddress().equals(a))
            return c;
      return null;
   }
   
   @Listener
   static public class TestListener {
      
      boolean created = false;
      boolean modified = false;
      
      @CacheEntryCreated
      @SuppressWarnings("unused")
      public void create(CacheEntryEvent e) {
         created = true;
      }
      
      @CacheEntryModified
      @SuppressWarnings("unused")
      public void modify(CacheEntryEvent e) {
         modified = true;
      }
   }

}
