/**
   * Constructor to set up connection parameters using the {@link DeviceClientConfig}.
   *
   * @param config The {@link DeviceClientConfig} corresponding to the device associated with this
   *     {@link com.microsoft.azure.iothub.DeviceClient}.
   */
  public AmqpsIotHubConnection(DeviceClientConfig config) {
    // Codes_SRS_AMQPSIOTHUBCONNECTION_14_002: [The constructor shall throw a new
    // IllegalArgumentException if any of the parameters of the configuration is null or empty.]
    if (config == null) {
      throw new IllegalArgumentException("The DeviceClientConfig cannot be null.");
    }
    if (config.getIotHubHostname() == null || config.getIotHubHostname().length() == 0) {
      throw new IllegalArgumentException("hostName cannot be null or empty.");
    }
    if (config.getDeviceId() == null || config.getDeviceId().length() == 0) {
      throw new IllegalArgumentException("deviceID cannot be null or empty.");
    }
    if (config.getIotHubName() == null || config.getIotHubName().length() == 0) {
      throw new IllegalArgumentException("hubName cannot be null or empty.");
    }
    if (config.getDeviceKey() == null || config.getDeviceKey().length() == 0) {
      throw new IllegalArgumentException("deviceKey cannot be null or empty.");
    }

    // Codes_SRS_AMQPSIOTHUBCONNECTION_14_001: [The constructor shall save the configuration.]
    this.config = config;

    String iotHubHostname = this.config.getIotHubHostname();
    String iotHubName = this.config.getIotHubName();
    String deviceId = this.config.getDeviceId();
    String iotHubUser = deviceId + "@sas." + iotHubName;

    // Codes_SRS_AMQPSIOTHUBCONNECTION_14_003: [The constructor shall create a new SAS token and
    // copy all input parameters to private member variables.]
    IotHubSasToken sasToken = new IotHubSasToken(this.config);

    // Codes_SRS_AMQPSIOTHUBCONNECTION_14_006: [The constructor shall initialize a new private map
    // for messages that are in progress.]
    // Codes_SRS_AMQPSIOTHUBCONNECTION_14_007: [The constructor shall initialize new private Futures
    // for the status of the Connection and Reactor.]
    this.hostName = iotHubHostname;
    this.userName = iotHubUser;
    this.deviceID = deviceId;
    this.sasToken = sasToken.toString();

    // Codes_SRS_AMQPSIOTHUBCONNECTION_14_004: [The constructor shall set it’s state to CLOSED.]
    this.state = ReactorState.CLOSED;

    // Codes_SRS_AMQPSIOTHUBCONNECTION_14_005: [The constructor shall initialize a new private queue
    // for received messages.]
    // Codes_SRS_AMQPSIOTHUBCONNECTION_14_006: [The constructor shall initialize a new private map
    // for messages that are in progress.]
    receivedMessageQueue = new LinkedBlockingQueue<>();
    inProgressMessageMap = new HashMap<>();

    this.maxQueueSize = -1;

    // Codes_SRS_AMQPSIOTHUBCONNECTION_14_007: [The constructor shall initialize new private Futures
    // for the status of the Connection and Reactor.]
    completionStatus = new CompletableFuture<>();
    reactorReady = new CompletableFuture<>();
  }
  /**
   * Opens the {@link AmqpsIotHubConnection} creating a new {@link
   * AmqpsIotHubConnectionBaseHandler}.
   *
   * <p>If the current {@link AmqpsIotHubConnection.ReactorState} is not OPEN, this method will
   * create a new {@link IotHubSasToken} and use it to create a new {@link
   * AmqpsIotHubConnectionBaseHandler}. This method will start the {@link Reactor}, set the current
   * {@link AmqpsIotHubConnection.ReactorState} to OPEN, and open the {@link AmqpsIotHubConnection}
   * for sending.
   *
   * @throws IOException if the {@link AmqpsIotHubConnectionBaseHandler} has not been initialized.
   * @throws InterruptedException if there is a problem acquiring the semaphore for the {@link
   *     Reactor}.
   * @throws ExecutionException If the {@link CompletableFuture} in {@link
   *     AmqpsIotHubConnection#reactorReady()} completed exceptionally
   */
  public void open() throws IOException, InterruptedException, ExecutionException {
    // Codes_SRS_AMQPSIOTHUBCONNECTION_14_011: [If the AMQPS connection is already open, the
    // function shall do nothing.]
    if (this.state != ReactorState.OPEN) {
      // Codes_SRS_AMQPSIOTHUBCONNECTION_14_008: [The function shall initialize it’s
      // AmqpsIotHubConnectionBaseHandler using the saved host name, user name, device ID and sas
      // token.]
      IotHubSasToken sasToken = new IotHubSasToken(this.config);
      this.amqpsHandler =
          new AmqpsIotHubConnectionBaseHandler(
              this.hostName, this.userName, sasToken.toString(), this.deviceID, this);

      // Codes_SRS_AMQPSIOTHUBCONNECTION_14_009: [The function shall open the Amqps connection and
      // trigger the Reactor (Proton) to begin running.]
      // Codes_SRS_AMQPSIOTHUBCONNECTION_14_012: [If the AmqpsIotHubConnectionBaseHandler becomes
      // invalidated before the Reactor (Proton) starts, the function shall throw an IOException.]
      this.startReactorAsync();

      // Codes_SRS_AMQPSIOTHUBCONNECTION_14_010: [Once the Reactor (Proton) is ready, the function
      // shall set its state to OPEN.]
      // Codes_SRS_AMQPSIOTHUBCONNECTION_14_031: [ The function shall get the link credit from it's
      // AmqpsIotHubConnectionBaseHandler and set the private maxQueueSize member variable. ]
      // Codes_SRS_AMQPSIOTHUBCONNECTION_14_032: [ The function shall successfully complete it’s
      // CompletableFuture status member variable. ]
      try {
        this.reactorReady();
        // This is a blocking call, will return once the link credit is available from the
        // AmqpsIotHubBaseHandler
        this.maxQueueSize = this.amqpsHandler.getLinkCredit();
      } catch (TimeoutException e) {
        this.amqpsHandler.shutdown();
        throw new ExecutionException(
            "The request to get the link credit from the AmqpsIotHubBaseHandler timed out.", e);
      } catch (Exception e) {
        this.amqpsHandler.shutdown();
        throw e;
      }
      this.completionStatus.complete(new Boolean(true));
      this.state = ReactorState.OPEN;
    }
    // TODO: Should this wrap all exceptions in an IOException and only throw that?
  }
  // Tests_SRS_AMQPSIOTHUBSESSION_11_022: [The constructor shall save the configuration.]
  // Tests_SRS_AMQPSIOTHUBSESSION_11_001: [The function shall establish an AMQPS session with an IoT
  // Hub with the resource URI
  // 'amqps://[urlEncodedDeviceId]@sas.[urlEncodedIotHubName]:[urlEncodedSasToken]@[urlEncodedIotHubHostname].]
  @Test
  public void openEstablishesSessionUsingCorrectUri() throws JMSException, IOException {
    final String iotHubHostname = "test.iothub.testhub";
    final String iotHubName = "test.iothub";
    final String deviceId = "('test-deviceid')";
    final String deviceKey = "test-devicekey?&test";
    final String resourceUri = "test-resource-uri";
    new NonStrictExpectations() {
      {
        mockConfig.getIotHubHostname();
        result = iotHubHostname;
        mockConfig.getIotHubName();
        result = iotHubName;
        mockConfig.getDeviceId();
        result = deviceId;
        mockConfig.getDeviceKey();
        result = deviceKey;
        IotHubUri.getResourceUri(iotHubHostname, deviceId);
        result = resourceUri;
        new IotHubSasToken(resourceUri, deviceId, deviceKey, anyLong);
        result = mockToken;
      }
    };

    AmqpsIotHubSession session = new AmqpsIotHubSession(mockConfig);
    session.open();

    final String expectedUsername = deviceId + "@sas." + iotHubName;
    final String expectedPassword = mockToken.toString();
    final String expectedHostname = "amqps://" + iotHubHostname;
    new Verifications() {
      {
        new JmsConnectionFactory(expectedUsername, expectedPassword, expectedHostname);
      }
    };
  }