package org.infinispan.configuration.global;

import static java.util.Arrays.asList;
import static org.infinispan.configuration.global.TransportConfiguration.CLUSTER_NAME;
import static org.infinispan.configuration.global.TransportConfiguration.DISTRIBUTED_SYNC_TIMEOUT;
import static org.infinispan.configuration.global.TransportConfiguration.INITIAL_CLUSTER_SIZE;
import static org.infinispan.configuration.global.TransportConfiguration.INITIAL_CLUSTER_TIMEOUT;
import static org.infinispan.configuration.global.TransportConfiguration.MACHINE_ID;
import static org.infinispan.configuration.global.TransportConfiguration.NODE_NAME;
import static org.infinispan.configuration.global.TransportConfiguration.PROPERTIES;
import static org.infinispan.configuration.global.TransportConfiguration.RACK_ID;
import static org.infinispan.configuration.global.TransportConfiguration.SITE_ID;
import static org.infinispan.configuration.global.TransportConfiguration.TRANSPORT;

import java.util.Properties;
import java.util.concurrent.TimeUnit;

import org.infinispan.commons.CacheConfigurationException;
import org.infinispan.commons.configuration.Builder;
import org.infinispan.commons.configuration.attributes.AttributeSet;
import org.infinispan.commons.util.TypedProperties;
import org.infinispan.commons.util.Util;
import org.infinispan.remoting.transport.Transport;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;

/**
 * Configures the transport used for network communications across the cluster.
 */
public class TransportConfigurationBuilder extends AbstractGlobalConfigurationBuilder implements Builder<TransportConfiguration> {
   private static final Log log = LogFactory.getLog(TransportConfigurationBuilder.class);

   // Lazily instantiate this if the user doesn't request an alternate to avoid a hard dep on jgroups library
   public static final String DEFAULT_TRANSPORT = "org.infinispan.remoting.transport.jgroups.JGroupsTransport";

   private final ThreadPoolConfigurationBuilder transportThreadPool;
   private final ThreadPoolConfigurationBuilder remoteCommandThreadPool;
   private final AttributeSet attributes;

   TransportConfigurationBuilder(GlobalConfigurationBuilder globalConfig) {
      super(globalConfig);
      attributes = TransportConfiguration.attributeSet();
      transportThreadPool = new ThreadPoolConfigurationBuilder(globalConfig);
      remoteCommandThreadPool = new ThreadPoolConfigurationBuilder(globalConfig);
   }

   /**
    * Defines the name of the cluster. Nodes only connect to clusters sharing the same name.
    *
    * @param clusterName
    */
   public TransportConfigurationBuilder clusterName(String clusterName) {
      attributes.attribute(CLUSTER_NAME).set(clusterName);
      return this;
   }

   /**
    * The id of the machine where this node runs. Used for <a
    * href="http://community.jboss.org/wiki/DesigningServerHinting">server hinting</a> .
    */
   public TransportConfigurationBuilder machineId(String machineId) {
      attributes.attribute(MACHINE_ID).set(machineId);
      return this;
   }

   /**
    * The id of the rack where this node runs. Used for <a
    * href="http://community.jboss.org/wiki/DesigningServerHinting">server hinting</a> .
    */
   public TransportConfigurationBuilder rackId(String rackId) {
      attributes.attribute(RACK_ID).set(rackId);
      return this;
   }

   /**
    * The id of the site where this node runs. Used for <a
    * href="http://community.jboss.org/wiki/DesigningServerHinting">server hinting</a> .
    */
   public TransportConfigurationBuilder siteId(String siteId) {
      attributes.attribute(SITE_ID).set(siteId);
      return this;
   }

   /**
    * Timeout for coordinating cluster formation when nodes join or leave the cluster.
    *
    * @param distributedSyncTimeout
    * @return
    */
   public TransportConfigurationBuilder distributedSyncTimeout(long distributedSyncTimeout) {
      attributes.attribute(DISTRIBUTED_SYNC_TIMEOUT).set(distributedSyncTimeout);
      return this;
   }

   /**
    * Timeout for coordinating cluster formation when nodes join or leave the cluster.
    *
    * @param distributedSyncTimeout
    * @return
    */
   public TransportConfigurationBuilder distributedSyncTimeout(long distributedSyncTimeout, TimeUnit unit) {
      return distributedSyncTimeout(unit.toMillis(distributedSyncTimeout));
   }

   /**
    * Sets the number of nodes that need to join before the cache container can start. The default is to start
    * immediately without waiting.
    */
   public TransportConfigurationBuilder initialClusterSize(int clusterSize) {
      attributes.attribute(INITIAL_CLUSTER_SIZE).set(clusterSize);
      return this;
   }

   /**
    * Sets the timeout for the initial cluster to form. Defaults to 1 minute
    */
   public TransportConfigurationBuilder initialClusterTimeout(long initialClusterTimeout, TimeUnit unit) {
      attributes.attribute(INITIAL_CLUSTER_TIMEOUT).set(unit.toMillis(initialClusterTimeout));
      return this;
   }

   /**
    * Class that represents a network transport. Must implement
    * org.infinispan.remoting.transport.Transport
    *
    * NOTE: Currently Infinispan will not use the object instance, but instead instantiate a new
    * instance of the class. Therefore, do not expect any state to survive, and provide a no-args
    * constructor to any instance. This will be resolved in Infinispan 5.2.0
    *
    * @param transport transport instance
    */
   public TransportConfigurationBuilder transport(Transport transport) {
      attributes.attribute(TRANSPORT).set(transport);
      return this;
   }

   /**
    * Name of the current node. This is a friendly name to make logs, etc. make more sense.
    * Defaults to a combination of host name and a random number (to differentiate multiple nodes
    * on the same host)
    *
    * @param nodeName
    */
   public TransportConfigurationBuilder nodeName(String nodeName) {
      attributes.attribute(NODE_NAME).set(nodeName);
      return this;
   }

   /**
    * Sets transport properties
    *
    * @param properties
    * @return this TransportConfig
    */
   public TransportConfigurationBuilder withProperties(Properties properties) {
      attributes.attribute(PROPERTIES).set(TypedProperties.toTypedProperties(properties));
      return this;
   }

   /**
    * Clears the transport properties
    *
    * @return this TransportConfig
    */
   public TransportConfigurationBuilder clearProperties() {
      attributes.attribute(PROPERTIES).set(new TypedProperties());
      return this;
   }

   public TransportConfigurationBuilder addProperty(String key, String value) {
      TypedProperties properties = attributes.attribute(PROPERTIES).get();
      properties.put(key, value);
      attributes.attribute(PROPERTIES).set(properties);
      return this;
   }

   public TransportConfigurationBuilder removeProperty(String key) {
      TypedProperties properties = attributes.attribute(PROPERTIES).get();
      properties.remove(key);
      attributes.attribute(PROPERTIES).set(properties);
      return this;
   }

   public String getProperty(String key) {
      return String.valueOf(attributes.attribute(PROPERTIES).get().get(key));
   }

   /**
    * @deprecated Since 6.0, strictPeerToPeer is ignored and asymmetric clusters are always allowed.
    */
   @Deprecated
   public TransportConfigurationBuilder strictPeerToPeer(Boolean ignored) {
      log.strictPeerToPeerDeprecated();
      return this;
   }

   public ThreadPoolConfigurationBuilder transportThreadPool() {
      return transportThreadPool;
   }

   public ThreadPoolConfigurationBuilder remoteCommandThreadPool() {
      return remoteCommandThreadPool;
   }

   @Override
   public
   void validate() {
      asList(transportThreadPool, remoteCommandThreadPool).forEach(Builder::validate);
      if(attributes.attribute(CLUSTER_NAME).get() == null){
          throw new CacheConfigurationException("Transport clusterName cannot be null");
      }
   }

   @Override
   public
   TransportConfiguration create() {
      return new TransportConfiguration(attributes.protect(), transportThreadPool.create(), remoteCommandThreadPool.create());
   }

   public TransportConfigurationBuilder defaultTransport() {
      Transport transport = Util.getInstance(DEFAULT_TRANSPORT, this.getGlobalConfig().getClassLoader());
      transport(transport);
      return this;
   }

   @Override
   public
   TransportConfigurationBuilder read(TransportConfiguration template) {
      attributes.read(template.attributes());
      this.remoteCommandThreadPool.read(template.remoteCommandThreadPool());
      this.transportThreadPool.read(template.transportThreadPool());
      if (template.transport() != null) {
         Transport transport = Util.getInstance(template.transport().getClass().getName(), template.transport().getClass().getClassLoader());
         transport(transport);
      }

      return this;
   }

   public Transport getTransport() {
      return attributes.attribute(TRANSPORT).get();
   }

   @Override
   public String toString() {
      return "TransportConfigurationBuilder [transportThreadPool=" + transportThreadPool + ", remoteCommandThreadPool="
            + remoteCommandThreadPool + ", attributes=" + attributes
            + "]";
   }

}
