package org.jivesoftware.smack;

import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.security.auth.callback.CallbackHandler;
import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jxmpp.jid.DomainBareJid;

public abstract class ConnectionConfiguration {
   protected final DomainBareJid serviceName;
   protected final String host;
   protected final int port;
   private final String keystorePath;
   private final String keystoreType;
   private final String pkcs11Library;
   private final SSLContext customSSLContext;
   private final CallbackHandler callbackHandler;
   private final boolean debuggerEnabled;
   private final SocketFactory socketFactory;
   private final CharSequence username;
   private final String password;
   private final String resource;
   private final boolean sendPresence;
   private final boolean legacySessionDisabled;
   private final ConnectionConfiguration.SecurityMode securityMode;
   private final String[] enabledSSLProtocols;
   private final String[] enabledSSLCiphers;
   private final HostnameVerifier hostnameVerifier;
   protected final ProxyInfo proxy;
   protected final boolean allowNullOrEmptyUsername;

   protected ConnectionConfiguration(ConnectionConfiguration.Builder<?, ?> builder) {
      this.username = builder.username;
      this.password = builder.password;
      this.callbackHandler = builder.callbackHandler;
      this.resource = builder.resource;
      this.serviceName = builder.serviceName;
      if (this.serviceName == null) {
         throw new IllegalArgumentException("Must provide XMPP service name");
      } else {
         this.host = builder.host;
         this.port = builder.port;
         this.proxy = builder.proxy;
         if (this.proxy != null) {
            if (builder.socketFactory != null) {
               throw new IllegalArgumentException("Can not use proxy together with custom socket factory");
            }

            this.socketFactory = this.proxy.getSocketFactory();
         } else {
            this.socketFactory = builder.socketFactory;
         }

         this.securityMode = builder.securityMode;
         this.keystoreType = builder.keystoreType;
         this.keystorePath = builder.keystorePath;
         this.pkcs11Library = builder.pkcs11Library;
         this.customSSLContext = builder.customSSLContext;
         this.enabledSSLProtocols = builder.enabledSSLProtocols;
         this.enabledSSLCiphers = builder.enabledSSLCiphers;
         this.hostnameVerifier = builder.hostnameVerifier;
         this.sendPresence = builder.sendPresence;
         this.legacySessionDisabled = builder.legacySessionDisabled;
         this.debuggerEnabled = builder.debuggerEnabled;
         this.allowNullOrEmptyUsername = builder.allowEmptyOrNullUsername;
      }
   }

   public DomainBareJid getServiceName() {
      return this.serviceName;
   }

   public ConnectionConfiguration.SecurityMode getSecurityMode() {
      return this.securityMode;
   }

   public String getKeystorePath() {
      return this.keystorePath;
   }

   public String getKeystoreType() {
      return this.keystoreType;
   }

   public String getPKCS11Library() {
      return this.pkcs11Library;
   }

   public SSLContext getCustomSSLContext() {
      return this.customSSLContext;
   }

   public String[] getEnabledSSLProtocols() {
      return this.enabledSSLProtocols;
   }

   public String[] getEnabledSSLCiphers() {
      return this.enabledSSLCiphers;
   }

   public HostnameVerifier getHostnameVerifier() {
      return this.hostnameVerifier != null ? this.hostnameVerifier : SmackConfiguration.getDefaultHostnameVerifier();
   }

   public boolean isDebuggerEnabled() {
      return this.debuggerEnabled;
   }

   /** @deprecated */
   @Deprecated
   public boolean isLegacySessionDisabled() {
      return this.legacySessionDisabled;
   }

   public CallbackHandler getCallbackHandler() {
      return this.callbackHandler;
   }

   public SocketFactory getSocketFactory() {
      return this.socketFactory;
   }

   public CharSequence getUsername() {
      return this.username;
   }

   public String getPassword() {
      return this.password;
   }

   public String getResource() {
      return this.resource;
   }

   public boolean isSendPresence() {
      return this.sendPresence;
   }

   public boolean isCompressionEnabled() {
      return false;
   }

   static {
      SmackConfiguration.getVersion();
   }

   public abstract static class Builder<B extends ConnectionConfiguration.Builder<B, C>, C extends ConnectionConfiguration> {
      private ConnectionConfiguration.SecurityMode securityMode;
      private String keystorePath;
      private String keystoreType;
      private String pkcs11Library;
      private SSLContext customSSLContext;
      private String[] enabledSSLProtocols;
      private String[] enabledSSLCiphers;
      private HostnameVerifier hostnameVerifier;
      private CharSequence username;
      private String password;
      private String resource;
      private boolean sendPresence;
      private boolean legacySessionDisabled;
      private ProxyInfo proxy;
      private CallbackHandler callbackHandler;
      private boolean debuggerEnabled;
      private SocketFactory socketFactory;
      private DomainBareJid serviceName;
      private String host;
      private int port;
      private boolean allowEmptyOrNullUsername;

      protected Builder() {
         this.securityMode = ConnectionConfiguration.SecurityMode.ifpossible;
         this.keystorePath = System.getProperty("javax.net.ssl.keyStore");
         this.keystoreType = "jks";
         this.pkcs11Library = "pkcs11.config";
         this.resource = "Smack";
         this.sendPresence = true;
         this.legacySessionDisabled = false;
         this.debuggerEnabled = SmackConfiguration.DEBUG;
         this.port = 5222;
         this.allowEmptyOrNullUsername = false;
      }

      public B setUsernameAndPassword(CharSequence username, String password) {
         this.username = username;
         this.password = password;
         return this.getThis();
      }

      public B setServiceName(DomainBareJid serviceName) {
         this.serviceName = serviceName;
         return this.getThis();
      }

      public B setResource(String resource) {
         this.resource = resource;
         return this.getThis();
      }

      public B setHost(String host) {
         this.host = host;
         return this.getThis();
      }

      public B setPort(int port) {
         this.port = port;
         return this.getThis();
      }

      public B setCallbackHandler(CallbackHandler callbackHandler) {
         this.callbackHandler = callbackHandler;
         return this.getThis();
      }

      public B setSecurityMode(ConnectionConfiguration.SecurityMode securityMode) {
         this.securityMode = securityMode;
         return this.getThis();
      }

      public B setKeystorePath(String keystorePath) {
         this.keystorePath = keystorePath;
         return this.getThis();
      }

      public B setKeystoreType(String keystoreType) {
         this.keystoreType = keystoreType;
         return this.getThis();
      }

      public B setPKCS11Library(String pkcs11Library) {
         this.pkcs11Library = pkcs11Library;
         return this.getThis();
      }

      public B setCustomSSLContext(SSLContext context) {
         this.customSSLContext = context;
         return this.getThis();
      }

      public B setEnabledSSLProtocols(String[] enabledSSLProtocols) {
         this.enabledSSLProtocols = enabledSSLProtocols;
         return this.getThis();
      }

      public B setEnabledSSLCiphers(String[] enabledSSLCiphers) {
         this.enabledSSLCiphers = enabledSSLCiphers;
         return this.getThis();
      }

      public B setHostnameVerifier(HostnameVerifier verifier) {
         this.hostnameVerifier = verifier;
         return this.getThis();
      }

      /** @deprecated */
      @Deprecated
      public B setLegacySessionDisabled(boolean legacySessionDisabled) {
         this.legacySessionDisabled = legacySessionDisabled;
         return this.getThis();
      }

      public B setSendPresence(boolean sendPresence) {
         this.sendPresence = sendPresence;
         return this.getThis();
      }

      public B setDebuggerEnabled(boolean debuggerEnabled) {
         this.debuggerEnabled = debuggerEnabled;
         return this.getThis();
      }

      public B setSocketFactory(SocketFactory socketFactory) {
         this.socketFactory = socketFactory;
         return this.getThis();
      }

      public B allowEmptyOrNullUsernames() {
         this.allowEmptyOrNullUsername = true;
         return this.getThis();
      }

      public abstract C build();

      protected abstract B getThis();
   }

   public static enum SecurityMode {
      required,
      ifpossible,
      disabled;
   }
}
