package org.infinispan.server.test.util;

import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;

import javax.management.Attribute;
import javax.management.ObjectName;

import org.infinispan.arquillian.core.RemoteInfinispanServer;
import org.infinispan.arquillian.utils.MBeanServerConnectionProvider;
import org.infinispan.client.hotrod.ProtocolVersion;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.client.hotrod.impl.RemoteCacheImpl;
import org.infinispan.commons.marshall.Marshaller;
import org.infinispan.server.test.client.memcached.MemcachedClient;
import org.jboss.arquillian.container.test.api.Config;
import org.jboss.arquillian.container.test.api.ContainerController;

/**
 * Often repeated test code routines.
 *
 * @author Michal Linhard (mlinhard@redhat.com)
 */
public class ITestUtils {
   public static final String SERVER_DATA_DIR = System.getProperty("server1.dist") + File.separator + "standalone"
         + File.separator + "data";
   public static final String SERVER_CONFIG_DIR = System.getProperty("server1.dist") + File.separator + "standalone"
         + File.separator + "configuration";
   private static final String SERVER_CONFIG_PROPERTY = "serverConfig";
   private static final String DEFAULT_CLUSTERING_MODE = "dist";

   public static final int SERVER1_MGMT_PORT = 9990;
   public static final int SERVER2_MGMT_PORT = 10090;
   public static final int SERVER3_MGMT_PORT = 10190;

   /**
    * Create {@link RemoteCacheManager} for given server.
    *
    * @param server The server
    * @return New {@link RemoteCacheManager}
    */
   public static RemoteCacheManager createCacheManager(RemoteInfinispanServer server) {
      return createCacheManager(server, ProtocolVersion.DEFAULT_PROTOCOL_VERSION.toString());
   }

   public static RemoteCacheManager createCacheManager(RemoteInfinispanServer server, String protocolVersion) {
      return new RemoteCacheManager(createConfigBuilder(server.getHotrodEndpoint().getInetAddress().getHostName(),
            server.getHotrodEndpoint().getPort(), protocolVersion).build());
   }

   public static ConfigurationBuilder createConfigBuilder(String hostName, int port) {
      return createConfigBuilder(hostName, port, ProtocolVersion.DEFAULT_PROTOCOL_VERSION.toString()).addJavaSerialWhiteList(".*");
   }

   public static ConfigurationBuilder createConfigBuilder(String hostName, int port, String protocolVersion) {
      ConfigurationBuilder builder = new ConfigurationBuilder();
      builder.addServer().host(hostName).port(port).protocolVersion(protocolVersion).addJavaSerialWhiteList(".*");
      return builder;
   }

   /**
    * Create cache manager for given {@link RemoteInfinispanMBeans}.
    *
    * @param serverBeans The server MBeans.
    * @return New {@link RemoteCacheManager}
    */
   public static RemoteCacheManager createCacheManager(RemoteInfinispanMBeans serverBeans) {
      return createCacheManager(serverBeans.server);
   }

   public static String getAttribute(MBeanServerConnectionProvider provider, String mbean, String attr) throws Exception {
      Object jmxAttr = provider.getConnection().getAttribute(new ObjectName(mbean), attr);
      return jmxAttr != null ? jmxAttr.toString() : null;
   }

   public static void setAttribute(MBeanServerConnectionProvider provider, String mbean, String attrName, Object attrValue) throws Exception {
      provider.getConnection().setAttribute(new ObjectName(mbean), new Attribute(attrName, attrValue));
   }

   public static Object invokeOperation(MBeanServerConnectionProvider provider, String mbean, String operationName, Object[] params,
                                        String[] signature) throws Exception {
      return provider.getConnection().invoke(new ObjectName(mbean), operationName, params, signature);
   }

   public interface Condition {
      public boolean isSatisfied() throws Exception;
   }

   public static void eventually(Condition ec, long timeout) {
      eventually(ec, timeout, 10);
   }

   public static void eventually(Condition ec, long timeout, int loops) {
      if (loops <= 0) {
         throw new IllegalArgumentException("Number of loops must be positive");
      }
      long sleepDuration = timeout / loops;
      if (sleepDuration == 0) {
         sleepDuration = 1;
      }
      try {
         for (int i = 0; i < loops; i++) {

            if (ec.isSatisfied())
               return;
            Thread.sleep(sleepDuration);
         }
         assertTrue(ec.isSatisfied());
      } catch (Exception e) {
         throw new RuntimeException("Unexpected!", e);
      }
   }

   /*
    * HotRod client automatically creates a ByteArrayKey from each key regardless of its type. This
    * ByteArrayKey is then stored in a cache/store
    */
   public static byte[] getRealKeyStored(String key, RemoteCache rc) throws Exception {
      Marshaller m = getMarshallerField((RemoteCacheImpl) rc);
      return m.objectToByteBuffer(key, 64);
   }

   private static Marshaller getMarshallerField(RemoteCacheImpl rci) throws Exception {
      Field field;
      try {
         field = RemoteCacheImpl.class.getDeclaredField("marshaller");
      } catch (NoSuchFieldException e) {
         throw new Exception("Could not access marshaller field", e);
      }
      field.setAccessible(true);
      Marshaller fieldValue;
      try {
         fieldValue = (Marshaller) field.get(rci);
      } catch (IllegalAccessException e) {
         throw new Exception("Could not access marshaller field", e);
      }
      return fieldValue;
   }

   public static void startContainer(ContainerController controller, String containerName, String config) {
      controller.start(containerName, new Config().add(SERVER_CONFIG_PROPERTY, config).map());
   }

   public static void stopContainers(ContainerController controller, String... containerNames) {
      for (String name : containerNames) {
         controller.stop(name);
      }
   }

   public static RemoteInfinispanMBeans createMBeans(RemoteInfinispanServer server, String containerName, String cacheName, String managerName) {
      return RemoteInfinispanMBeans.create(server, containerName, cacheName, managerName);
   }

   public static MemcachedClient createMemcachedClient(RemoteInfinispanServer server) {
      MemcachedClient mc;
      try {
         mc = new MemcachedClient(server.getMemcachedEndpoint().getInetAddress().getHostName(), server.getMemcachedEndpoint()
               .getPort());
      } catch (IOException e) {
         throw new RuntimeException("Could not create Memcached Client");
      }
      return mc;
   }

   public static void sleepForSecs(double numSecs) {
      // give the elements time to be evicted
      try {
         Thread.sleep((long) (numSecs * 1000));
      } catch (InterruptedException e) {
      }
   }

   public static String getClusteringMode() {
      return System.getProperty("clustering.mode", DEFAULT_CLUSTERING_MODE);
   }

   public static boolean isDistributedMode() {
      return "dist".equals(getClusteringMode());
   }

   public static boolean isLocalMode() {
      return getClusteringMode().contains("local");
   }

   public static boolean isReplicatedMode() {
      return "repl".equals(getClusteringMode());
   }

}
