/* (non-Javadoc)
   * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(org.eclipse.paho.client.mqttv3.MqttConnectOptions, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
   */
  public IMqttToken connect(
      MqttConnectOptions options, Object userContext, IMqttActionListener callback)
      throws MqttException, MqttSecurityException {
    final String methodName = "connect";
    if (comms.isConnected()) {
      throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_CONNECTED);
    }
    if (comms.isConnecting()) {
      throw new MqttException(MqttException.REASON_CODE_CONNECT_IN_PROGRESS);
    }
    if (comms.isDisconnecting()) {
      throw new MqttException(MqttException.REASON_CODE_CLIENT_DISCONNECTING);
    }
    if (comms.isClosed()) {
      throw new MqttException(MqttException.REASON_CODE_CLIENT_CLOSED);
    }

    this.connOpts = options;
    this.userContext = userContext;
    final boolean automaticReconnect = options.isAutomaticReconnect();

    // @TRACE 103=cleanSession={0} connectionTimeout={1} TimekeepAlive={2} userName={3} password={4}
    // will={5} userContext={6} callback={7}
    log.fine(
        CLASS_NAME,
        methodName,
        "103",
        new Object[] {
          Boolean.valueOf(options.isCleanSession()),
          new Integer(options.getConnectionTimeout()),
          new Integer(options.getKeepAliveInterval()),
          options.getUserName(),
          ((null == options.getPassword()) ? "[null]" : "[notnull]"),
          ((null == options.getWillMessage()) ? "[null]" : "[notnull]"),
          userContext,
          callback
        });
    comms.setNetworkModules(createNetworkModules(serverURI, options));
    comms.setReconnectCallback(
        new MqttCallbackExtended() {

          public void messageArrived(String topic, MqttMessage message) throws Exception {}

          public void deliveryComplete(IMqttDeliveryToken token) {}

          public void connectComplete(boolean reconnect, String serverURI) {}

          public void connectionLost(Throwable cause) {
            if (automaticReconnect) {
              // Automatic reconnect is set so make sure comms is in resting state
              comms.setRestingState(true);
              reconnecting = true;
              startReconnectCycle();
            }
          }
        });

    // Insert our own callback to iterate through the URIs till the connect succeeds
    MqttToken userToken = new MqttToken(getClientId());
    ConnectActionListener connectActionListener =
        new ConnectActionListener(
            this, persistence, comms, options, userToken, userContext, callback, reconnecting);
    userToken.setActionCallback(connectActionListener);
    userToken.setUserContext(this);

    // If we are using the MqttCallbackExtended, set it on the connectActionListener
    if (this.mqttCallback instanceof MqttCallbackExtended) {
      connectActionListener.setMqttCallbackExtended((MqttCallbackExtended) this.mqttCallback);
    }

    comms.setNetworkModuleIndex(0);
    connectActionListener.connect();

    return userToken;
  }
  /**
   * Factory method to create the correct network module, based on the supplied address URI.
   *
   * @param address the URI for the server.
   * @param Connect options
   * @return a network module appropriate to the specified address.
   */
  private NetworkModule createNetworkModule(String address, MqttConnectOptions options)
      throws MqttException, MqttSecurityException {
    final String methodName = "createNetworkModule";
    // @TRACE 115=URI={0}
    log.fine(CLASS_NAME, methodName, "115", new Object[] {address});

    NetworkModule netModule;
    String shortAddress;
    String host;
    int port;
    SocketFactory factory = options.getSocketFactory();

    int serverURIType = MqttConnectOptions.validateURI(address);

    switch (serverURIType) {
      case MqttConnectOptions.URI_TYPE_TCP:
        shortAddress = address.substring(6);
        host = getHostName(shortAddress);
        port = getPort(shortAddress, 1883);
        if (factory == null) {
          factory = SocketFactory.getDefault();
        } else if (factory instanceof SSLSocketFactory) {
          throw ExceptionHelper.createMqttException(
              MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
        }
        netModule = new TCPNetworkModule(factory, host, port, clientId);
        ((TCPNetworkModule) netModule).setConnectTimeout(options.getConnectionTimeout());
        break;
      case MqttConnectOptions.URI_TYPE_SSL:
        shortAddress = address.substring(6);
        host = getHostName(shortAddress);
        port = getPort(shortAddress, 8883);
        SSLSocketFactoryFactory factoryFactory = null;
        if (factory == null) {
          //				try {
          factoryFactory = new SSLSocketFactoryFactory();
          Properties sslClientProps = options.getSSLProperties();
          if (null != sslClientProps) factoryFactory.initialize(sslClientProps, null);
          factory = factoryFactory.createSocketFactory(null);
          //				}
          //				catch (MqttDirectException ex) {
          //					throw ExceptionHelper.createMqttException(ex.getCause());
          //				}
        } else if ((factory instanceof SSLSocketFactory) == false) {
          throw ExceptionHelper.createMqttException(
              MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
        }

        // Create the network module...
        netModule = new SSLNetworkModule((SSLSocketFactory) factory, host, port, clientId);
        ((SSLNetworkModule) netModule).setSSLhandshakeTimeout(options.getConnectionTimeout());
        // Ciphers suites need to be set, if they are available
        if (factoryFactory != null) {
          String[] enabledCiphers = factoryFactory.getEnabledCipherSuites(null);
          if (enabledCiphers != null) {
            ((SSLNetworkModule) netModule).setEnabledCiphers(enabledCiphers);
          }
        }
        break;
      case MqttConnectOptions.URI_TYPE_WS:
        shortAddress = address.substring(5);
        host = getHostName(shortAddress);
        port = getPort(shortAddress, 80);
        if (factory == null) {
          factory = SocketFactory.getDefault();
        } else if (factory instanceof SSLSocketFactory) {
          throw ExceptionHelper.createMqttException(
              MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
        }
        netModule = new WebSocketNetworkModule(factory, address, host, port, clientId);
        ((WebSocketNetworkModule) netModule).setConnectTimeout(options.getConnectionTimeout());
        break;
      case MqttConnectOptions.URI_TYPE_WSS:
        shortAddress = address.substring(6);
        host = getHostName(shortAddress);
        port = getPort(shortAddress, 443);
        SSLSocketFactoryFactory wSSFactoryFactory = null;
        if (factory == null) {
          wSSFactoryFactory = new SSLSocketFactoryFactory();
          Properties sslClientProps = options.getSSLProperties();
          if (null != sslClientProps) wSSFactoryFactory.initialize(sslClientProps, null);
          factory = wSSFactoryFactory.createSocketFactory(null);

        } else if ((factory instanceof SSLSocketFactory) == false) {
          throw ExceptionHelper.createMqttException(
              MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
        }

        // Create the network module...
        netModule =
            new WebSocketSecureNetworkModule(
                (SSLSocketFactory) factory, address, host, port, clientId);
        ((WebSocketSecureNetworkModule) netModule)
            .setSSLhandshakeTimeout(options.getConnectionTimeout());
        // Ciphers suites need to be set, if they are available
        if (wSSFactoryFactory != null) {
          String[] enabledCiphers = wSSFactoryFactory.getEnabledCipherSuites(null);
          if (enabledCiphers != null) {
            ((SSLNetworkModule) netModule).setEnabledCiphers(enabledCiphers);
          }
        }
        break;
      case MqttConnectOptions.URI_TYPE_LOCAL:
        netModule = new LocalNetworkModule(address.substring(8));
        break;
      default:
        // This shouldn't happen, as long as validateURI() has been called.
        netModule = null;
    }
    return netModule;
  }