package org.infinispan.client.hotrod.event;

import static org.infinispan.server.hotrod.test.HotRodTestingUtil.hotRodCacheConfiguration;
import static org.infinispan.test.TestingUtil.extractField;
import static org.infinispan.test.fwk.TestCacheManagerFactory.createCacheManager;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.net.ServerSocket;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.client.hotrod.event.impl.ClientListenerNotifier;
import org.infinispan.client.hotrod.test.HotRodClientTestingUtil;
import org.infinispan.commons.util.Util;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.server.hotrod.HotRodServer;
import org.infinispan.server.hotrod.test.HotRodTestingUtil;
import org.infinispan.test.AbstractInfinispanTest;
import org.infinispan.test.TestingUtil;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * Test to verify that the listener threads are stopped.
 *
 * @author gustavonalle
 * @since 7.2
 */
@Test(groups = "functional", testName = "client.hotrod.event.ListenerCacheManagerStopTest")
public class ListenerCacheManagerStopTest extends AbstractInfinispanTest {

   HotRodServer hotRodServer;
   RemoteCacheManager remoteCacheManager;
   RemoteCache<Integer, String> cache;
   EmbeddedCacheManager cacheManager;

   @BeforeMethod
   public void setup() throws IOException {
      Integer port = getRandomFreePort();
      cacheManager = createCacheManager(hotRodCacheConfiguration());
      hotRodServer = HotRodTestingUtil.startHotRodServer(cacheManager, port);
      remoteCacheManager = new RemoteCacheManager(
            new ConfigurationBuilder()
                  .addServer()
                  .port(port)
                  .host(HotRodTestingUtil.host())
                  .connectionTimeout(3000)
                  .socketTimeout(3000)
                  .build()
      );
      cache = remoteCacheManager.getCache();
   }

   @AfterMethod
   public void tearDown() {
      HotRodClientTestingUtil.killServers(hotRodServer);
      HotRodClientTestingUtil.killRemoteCacheManager(remoteCacheManager);
      TestingUtil.killCacheManagers(cacheManager);
   }

   @Test
   public void testThreadsAreStopped() throws Exception {
      final EventLogListener listener = new EventLogListener<>(remoteCacheManager.getCache());
      cache.addClientListener(listener);

      final String listenerId = findListenerId(listener);
      assertListenerThreadRunning(listenerId);

      remoteCacheManager.stop();
      hotRodServer.stop();

      assertListenerThreadNotRunning(listenerId);
   }

   private String findListenerId(Object listener) {
      ClientListenerNotifier clientNotifier = extractField(remoteCacheManager, "listenerNotifier");
      return Util.toHexString(clientNotifier.findListenerId(listener), 8);

   }

   private void assertListenerThreadRunning(final String listenerId) {
      eventually(new Condition() {
         @Override
         public boolean isSatisfied() throws Exception {
            return isListenerThreadRunning(listenerId);
         }
      });
   }

   private void assertListenerThreadNotRunning(final String listenerId) {
      eventually(new Condition() {
         @Override
         public boolean isSatisfied() throws Exception {
            return !isListenerThreadRunning(listenerId);
         }
      });
   }

   private boolean isListenerThreadRunning(String listenerId) {
      ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
      ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
      for (ThreadInfo threadInfo : threadInfos) {
         if ((threadInfo.getThreadState().equals(Thread.State.RUNNABLE) ||
                    threadInfo.getThreadState().equals(Thread.State.BLOCKED)) &&
               threadInfo.getThreadName().contains(listenerId)) {
            return true;
         }
      }
      return false;
   }

   private Integer getRandomFreePort() throws IOException {
      try (ServerSocket socket = new ServerSocket(0)) {
         return socket.getLocalPort();
      }
   }
}
