/* (non-Javadoc)
   * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(long, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
   */
  public IMqttToken disconnect(
      long quiesceTimeout, Object userContext, IMqttActionListener callback) throws MqttException {
    final String methodName = "disconnect";
    // @TRACE 104=> quiesceTimeout={0} userContext={1} callback={2}
    log.fine(
        CLASS_NAME,
        methodName,
        "104",
        new Object[] {new Long(quiesceTimeout), userContext, callback});

    MqttToken token = new MqttToken(getClientId());
    token.setActionCallback(callback);
    token.setUserContext(userContext);

    MqttDisconnect disconnect = new MqttDisconnect();
    try {
      comms.disconnect(disconnect, quiesceTimeout, token);
    } catch (MqttException ex) {
      // @TRACE 105=< exception
      log.fine(CLASS_NAME, methodName, "105", null, ex);
      throw ex;
    }
    // @TRACE 108=<
    log.fine(CLASS_NAME, methodName, "108");

    return token;
  }
  /* (non-Javadoc)
   * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String[], int[], java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
   */
  public IMqttToken subscribe(
      String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback)
      throws MqttException {
    final String methodName = "subscribe";

    if (topicFilters.length != qos.length) {
      throw new IllegalArgumentException();
    }

    // remove any message handlers for individual topics
    for (int i = 0; i < topicFilters.length; ++i) {
      this.comms.removeMessageListener(topicFilters[i]);
    }

    String subs = "";
    for (int i = 0; i < topicFilters.length; i++) {
      if (i > 0) {
        subs += ", ";
      }
      subs += "topic=" + topicFilters[i] + " qos=" + qos[i];

      // Check if the topic filter is valid before subscribing
      MqttTopic.validate(topicFilters[i], true /*allow wildcards*/);
    }
    // @TRACE 106=Subscribe topicFilter={0} userContext={1} callback={2}
    log.fine(CLASS_NAME, methodName, "106", new Object[] {subs, userContext, callback});

    MqttToken token = new MqttToken(getClientId());
    token.setActionCallback(callback);
    token.setUserContext(userContext);
    token.internalTok.setTopics(topicFilters);

    MqttSubscribe register = new MqttSubscribe(topicFilters, qos);

    comms.sendNoWait(register, token);
    // @TRACE 109=<
    log.fine(CLASS_NAME, methodName, "109");

    return token;
  }
  /* (non-Javadoc)
   * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String[], java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
   */
  public IMqttToken unsubscribe(
      String[] topicFilters, Object userContext, IMqttActionListener callback)
      throws MqttException {
    final String methodName = "unsubscribe";
    String subs = "";
    for (int i = 0; i < topicFilters.length; i++) {
      if (i > 0) {
        subs += ", ";
      }
      subs += topicFilters[i];

      // Check if the topic filter is valid before unsubscribing
      // Although we already checked when subscribing, but invalid
      // topic filter is meanless for unsubscribing, just prohibit it
      // to reduce unnecessary control packet send to broker.
      MqttTopic.validate(topicFilters[i], true /*allow wildcards*/);
    }

    // @TRACE 107=Unsubscribe topic={0} userContext={1} callback={2}
    log.fine(CLASS_NAME, methodName, "107", new Object[] {subs, userContext, callback});

    // remove message handlers from the list for this client
    for (int i = 0; i < topicFilters.length; ++i) {
      this.comms.removeMessageListener(topicFilters[i]);
    }

    MqttToken token = new MqttToken(getClientId());
    token.setActionCallback(callback);
    token.setUserContext(userContext);
    token.internalTok.setTopics(topicFilters);

    MqttUnsubscribe unregister = new MqttUnsubscribe(topicFilters);

    comms.sendNoWait(unregister, token);
    // @TRACE 110=<
    log.fine(CLASS_NAME, methodName, "110");

    return token;
  }
  /* (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;
  }