/**
   * Constructor for Status Poller All params are required
   *
   * @param request The request that initiated the event
   * @param responsePromise Handler - Promise used to pass response to client
   * @param interval Interval between polls in millisecs
   * @param retries Max number of times the timer is allowed to be called
   * @param desiredState The desired state of the monitored object
   * @param conn Azure connection object to be used for the polling API call
   * @param retryOn404 Flag that determines if the poller should retry or fail when the response is
   *     404.
   */
  public StatusPoller(
      Request request,
      CompletablePromise<T> responsePromise,
      long interval,
      long retries,
      String desiredState,
      AWSConnection conn,
      boolean retryOn404) {
    if (responsePromise != null) {
      this.responsePromise = responsePromise;
    } else {
      IllegalArgumentException e =
          new IllegalArgumentException(
              StatusPoller.MISSING_PARAM_MSG.format(new Object[] {"handler"}));
      throw e;
    }

    if (request != null) {
      this.request = request;
    } else {
      IllegalArgumentException e =
          new IllegalArgumentException(
              StatusPoller.MISSING_PARAM_MSG.format(new Object[] {"request"}));
      responsePromise.failure(e);
      throw e;
    }

    if (interval > -1) {
      this.interval = interval;
    } else {
      IllegalArgumentException e =
          new IllegalArgumentException(
              StatusPoller.NEGATIVE_PARAM_MSG.format(new Object[] {"interval"}));
      responsePromise.failure(e);
      throw e;
    }

    if (retries > -1) {
      this.retries = retries;
    } else {
      IllegalArgumentException e =
          new IllegalArgumentException(
              StatusPoller.NEGATIVE_PARAM_MSG.format(new Object[] {"retries"}));
      responsePromise.failure(e);
      throw e;
    }

    if (conn != null) {
      this.conn = conn;
    } else {
      IllegalArgumentException e =
          new IllegalArgumentException(
              StatusPoller.MISSING_PARAM_MSG.format(new Object[] {"connection"}));
      responsePromise.failure(e);
      throw e;
    }

    if (AWSUtil.isValued(desiredState)) {
      this.desiredState = desiredState;
    } else {
      IllegalArgumentException e =
          new IllegalArgumentException(
              StatusPoller.MISSING_PARAM_MSG.format(new Object[] {"desiredState"}));
      responsePromise.failure(e);
      throw e;
    }

    this.retryOn404 = retryOn404;

    this.promise = run();
  }
  /**
   * This is the method to call when the timer expires. The timer will continue to reset until the
   * verification context is verified or the verification context expires. If the context is
   * verified, the status will be COMPLETE. If the context expires, the status will be FAILURE.
   *
   * @param scheduledTime Time in milliseconds when the timer should fire
   * @param actualTime Time in milliseconds
   * @return long - next scheduled time for firing; 0 implies the timer is complete
   */
  @Override
  public long timerFire(long scheduledTime, long actualTime) {
    if (getLogger().isTraceEnabled()) {
      getLogger().trace("The timer has fired for request " + request.getReqId());
    }
    T response = getResponseObject();
    Status status = null;
    String msg = null;
    long timerInterval = 0; // initialize to assume completion
    long timerResetValue = System.currentTimeMillis() + interval;

    response.setReqId(request.getReqId());
    response.setTimestamp(System.currentTimeMillis());
    if (!(--retries > -1)) {
      status = Status.FAILURE;
      msg = "The timer call has exceeded the maximum number of retries.";
      getLogger().error(msg);
      response.setStatus(status);
      response.setMessage(msg);
      responsePromise.complete(response);
    } else if (promise != null && promise.isCompleted()) {
      try {
        IHttpResponse httpResponse = (IHttpResponse) promise.get();
        int respStatus = httpResponse.getStatusCode();

        if (respStatus == 404 && retryOn404) {
          timerInterval = timerResetValue; // reset timer
          promise = run(); // refresh the response
        } else if (respStatus == 200) {
          String azureStatus = getStatus(httpResponse);
          try {
            if (desiredState.equalsIgnoreCase(azureStatus)) { // azureStatus could be null
              response = updateResponseObject(request, response, getJaxbObject(httpResponse));
              status = Status.COMPLETE;
              msg = "Success";
              response.setStatus(status);
              response.setMessage(msg);
              responsePromise.complete(response);
            } else if (isFailedState(azureStatus)) { // allows for short-circuit of failed calls
              status = Status.FAILURE;
              msg = "The status of " + azureStatus + " was returned which implies the call failed.";
              getLogger().error(msg);
              response.setStatus(status);
              response.setMessage(msg);
              responsePromise.complete(response);
            } else {
              timerInterval = timerResetValue; // reset timer
              promise = run(); // refresh the response
            }
          } catch (Exception e) {
            status = Status.FAILURE;
            msg = "An exception occurred while polling status.  " + e.getMessage();
            getLogger().error(msg, e);
            response.setStatus(status);
            response.setMessage(msg);
            responsePromise.complete(response);
          }
        } else {
          status = Status.FAILURE;
          msg = "Unexpected response status was returned by the API call. Status: " + respStatus;
          getLogger().error(msg);
          response.setStatus(status);
          response.setMessage(msg);
          responsePromise.complete(response);
        }
      } catch (Throwable e) {
        status = Status.FAILURE;
        msg = "An exception occurred while polling status.  " + e.getMessage();
        getLogger().error(msg, e);
        response.setStatus(status);
        response.setMessage(msg);
        responsePromise.complete(response);
      }
    } else if (promise == null) {
      status = Status.FAILURE;
      msg = "The HTTP Promise object is null.";
      getLogger().error(msg);
      response.setStatus(status);
      response.setMessage(msg);
      responsePromise.complete(response);
    } else {
      timerInterval = timerResetValue; // reset timer
    }

    // make sure this is called at the end so a new JAXB object will be computed next time fired
    clearJaxbObject();

    return timerInterval;
  }