/**
   * Check whether that caller can access or not ,based on current state and pre-defined policy
   *
   * @param throttleContext -The Context for this caller - runtime state
   * @param configuration -The Configuration for this caller - data from policy
   * @param currentTime - The current system time
   * @return boolean -The boolean value which say access will allow or not
   * @throws ThrottleException throws for invalid throttle configuration
   */
  public boolean canAccess(
      ThrottleContext throttleContext, CallerConfiguration configuration, long currentTime)
      throws ThrottleException {
    boolean canAccess;
    if (configuration == null) {
      if (log.isDebugEnabled()) {
        log.debug("Couldn't find the configuration .");
      }
      return true;
    }
    if (configuration.getMaximumRequestPerUnitTime() < 0
        || configuration.getUnitTime() <= 0
        || configuration.getProhibitTimePeriod() < 0) {
      throw new ThrottleException("Invalid Throttle Configuration");
    }

    // if caller access first time in his new session
    if (this.firstAccessTime == 0) {
      initAccess(configuration, throttleContext, currentTime);
    }
    // if unit time period (session time) is not over
    if (this.nextTimeWindow > currentTime) {
      canAccess = canAccessIfUnitTimeNotOver(configuration, throttleContext, currentTime);
    } else {
      canAccess = canAccessIfUnitTimeOver(configuration, throttleContext, currentTime);
    }

    return canAccess;
  }
 /**
  * Init the access for a particular caller , caller will registered with context
  *
  * @param configuration -The Configuration for this caller
  * @param throttleContext -The Throttle Context
  * @param currentTime -The system current time in milliseconds
  */
 private void initAccess(
     CallerConfiguration configuration, ThrottleContext throttleContext, long currentTime) {
   // This block should be synchronize by key as multiple threads can access this.
   this.unitTime = configuration.getUnitTime();
   this.firstAccessTime = currentTime;
   this.nextTimeWindow = this.firstAccessTime + this.unitTime;
   // Also we need to pick counter value associated with time window.
   throttleContext.addCallerContext(this, this.id);
   throttleContext.replicateTimeWindow(this.id);
 }
  /**
   * Clean up the callers - remove all callers that have expired their time window
   *
   * @param configuration
   * @param throttleContext
   * @param currentTime
   */
  public void cleanUpCallers(
      CallerConfiguration configuration, ThrottleContext throttleContext, long currentTime) {

    if (log.isDebugEnabled()) {
      log.debug("Cleaning up the inactive caller's states ... ");
    }
    if (configuration == null) {
      if (log.isDebugEnabled()) {
        log.debug("Couldn't find the configuration .");
      }
      return;
    }
    // if number of access for a unit time is less than MAX and
    // if the unit time period (session time) has over

    int maxRequest = configuration.getMaximumRequestPerUnitTime();
    if (!(maxRequest == 0)) {
      if ((this.globalCount.get() + this.localCount.get()) <= (maxRequest - 1)) {
        if (this.nextTimeWindow != 0 && this.nextTimeWindow < (currentTime - this.unitTime)) {
          if (log.isDebugEnabled()) {
            log.debug("Removing caller with id " + this.id);
          }
          // Removes the previous callercontext and Sends the current state to
          //  others (clustered env)
          throttleContext.removeAndDestroyShareParamsOfCaller(id);
        }
      } else {
        // if number of access for a unit time has just been greater than MAX
        // now same as a new session
        // OR
        //  if caller in prohibit session  and prohibit period has just over and only
        if ((this.nextAccessTime == 0) || this.nextAccessTime < (currentTime - this.unitTime)) {
          if (this.nextTimeWindow != 0 && this.nextTimeWindow < (currentTime - this.unitTime)) {
            if (log.isDebugEnabled()) {
              log.debug("Removing caller with id " + this.id);
            }
            // Removes the previous callercontext and Sends
            //  the current state to others (clustered env)
            throttleContext.removeAndDestroyShareParamsOfCaller(id);
          }
        }
      }
    }
  }
  /**
   * To verify access if unit time has already over
   *
   * @param configuration -The Configuration for this caller
   * @param throttleContext -The Throttle that caller having pass
   * @param currentTime -The system current time
   * @return boolean -The boolean value which say access will allow or not
   */
  private boolean canAccessIfUnitTimeOver(
      CallerConfiguration configuration, ThrottleContext throttleContext, long currentTime) {

    boolean canAccess = false;
    // if number of access for a unit time is less than MAX and
    // if the unit time period (session time) has just over
    int maxRequest = configuration.getMaximumRequestPerUnitTime();
    if (maxRequest != 0) {
      if ((this.globalCount.get() + this.localCount.get()) < maxRequest) {
        if (this.nextTimeWindow != 0) {
          // Removes and sends the current state to others  (clustered env)
          throttleContext.removeAndFlushCaller(this.id);
        }
        if (log.isDebugEnabled()) {
          log.debug(
              "CallerContext Checking access if unit time over next time window>> Access allowed="
                  + maxRequest
                  + " available="
                  + (maxRequest - (this.globalCount.get() + this.localCount.get()))
                  + " key="
                  + this.getId()
                  + " currentGlobalCount="
                  + globalCount
                  + " currentTime="
                  + currentTime
                  + " nextTimeWindow="
                  + this.nextTimeWindow
                  + " currentLocalCount="
                  + localCount
                  + " Tier="
                  + configuration.getID()
                  + " nextAccessTime="
                  + this.nextAccessTime);
        }
        canAccess = true; // this is bonus access
        // next time callers can access as a new one
      } else {
        // if number of access for a unit time has just been greater than MAX now same as a new
        // session
        // OR if caller in prohibit session  and prohibit period has just over
        if ((this.nextAccessTime == 0) || (this.nextAccessTime <= currentTime)) {
          if (log.isDebugEnabled()) {
            log.debug(
                "CallerContext Checking access if unit time over>> Access allowed="
                    + maxRequest
                    + " available="
                    + (maxRequest - (this.globalCount.get() + this.localCount.get()))
                    + " key="
                    + this.getId()
                    + " currentGlobalCount="
                    + globalCount
                    + " currentTime="
                    + currentTime
                    + " nextTimeWindow="
                    + this.nextTimeWindow
                    + " currentLocalCount="
                    + localCount
                    + " Tier="
                    + configuration.getID()
                    + " nextAccessTime="
                    + this.nextAccessTime);
          }
          // remove previous callercontext instance
          if (this.nextTimeWindow != 0) {
            throttleContext.removeCallerContext(id);
          }
          // reset the states so that, this is the first access
          this.nextAccessTime = 0;
          canAccess = true;

          this.globalCount.set(0); // can access the system   and this is same as first access
          this.localCount.set(1);
          this.firstAccessTime = currentTime;
          this.nextTimeWindow = currentTime + configuration.getUnitTime();
          // registers caller and send the current state to others (clustered env)
          throttleContext.addAndFlushCallerContext(this, id);
          throttleContext.replicateTimeWindow(this.id);

          if (log.isDebugEnabled()) {
            log.debug(
                "Caller="
                    + this.getId()
                    + " has reset counters and added for replication when unit "
                    + "time is over");
          }
        } else {
          // if  caller in prohibit session  and prohibit period has not  over
          if (log.isDebugEnabled()) {
            String type =
                ThrottleConstants.IP_BASE == configuration.getType() ? "IP address" : "domain";
            log.debug(
                "Even unit time has over , CallerContext in prohibit state :"
                    + type
                    + " - "
                    + this.id);
          }
        }
      }
    }
    return canAccess;
  }
  /**
   * To verify access if the unit time has already not over
   *
   * @param configuration - -The Configuration for this caller
   * @param throttleContext -The Throttle Context
   * @param currentTime -The system current time
   * @return boolean -The boolean value which say access will allow or not
   */
  private boolean canAccessIfUnitTimeNotOver(
      CallerConfiguration configuration, ThrottleContext throttleContext, long currentTime) {
    boolean canAccess = false;
    int maxRequest = configuration.getMaximumRequestPerUnitTime();
    if (maxRequest != 0) {
      if ((this.globalCount.get() + this.localCount.get())
          < maxRequest) { // If the globalCount is less than max request
        if (log.isDebugEnabled()) {
          log.debug(
              "CallerContext Checking access if unit time is not over and less than max count>> Access "
                  + "allowed="
                  + maxRequest
                  + " available="
                  + (maxRequest - (this.globalCount.get() + this.localCount.get()))
                  + " key="
                  + this.getId()
                  + " currentGlobalCount="
                  + globalCount
                  + " currentTime="
                  + currentTime
                  + " "
                  + "nextTimeWindow="
                  + this.nextTimeWindow
                  + " currentLocalCount="
                  + localCount
                  + " Tier="
                  + configuration.getID()
                  + " nextAccessTime="
                  + this.nextAccessTime);
        }
        canAccess = true; // can continue access
        this.localCount.incrementAndGet();
        // Send the current state to others (clustered env)
        throttleContext.flushCallerContext(this, id);
        // can complete access

      } else {
        // else , if caller has not already prohibit
        if (this.nextAccessTime == 0) {
          // and if there is no prohibit time  period in configuration
          long prohibitTime = configuration.getProhibitTimePeriod();
          if (prohibitTime == 0) {
            // prohibit access until unit time period is over
            this.nextAccessTime = this.firstAccessTime + configuration.getUnitTime();
          } else {
            // if there is a prohibit time period in configuration ,then
            // set it as prohibit period
            this.nextAccessTime = currentTime + prohibitTime;
          }
          if (log.isDebugEnabled()) {
            String type =
                ThrottleConstants.IP_BASE == configuration.getType() ? "IP address" : "domain";
            log.debug(
                "Maximum Number of requests are reached for caller with " + type + " - " + this.id);
          }
          // Send the current state to others (clustered env)
          throttleContext.flushCallerContext(this, id);
        } else {
          // else , if the caller has already prohibit and prohibit
          // time period has already over
          if (this.nextAccessTime <= currentTime) {
            if (log.isDebugEnabled()) {
              log.debug(
                  "CallerContext Checking access if unit time is not over before time window exceed >> "
                      + "Access allowed="
                      + maxRequest
                      + " available="
                      + (maxRequest - (this.globalCount.get() + this.localCount.get()))
                      + " key="
                      + this.getId()
                      + " currentGlobalCount="
                      + globalCount
                      + " currentTime="
                      + currentTime
                      + " "
                      + "nextTimeWindow="
                      + this.nextTimeWindow
                      + " currentLocalCount="
                      + localCount
                      + " "
                      + "Tier="
                      + configuration.getID()
                      + " nextAccessTime="
                      + this.nextAccessTime);
            }
            // remove previous caller context
            if (this.nextTimeWindow != 0) {
              throttleContext.removeCallerContext(id);
            }
            // reset the states so that, this is the first access
            this.nextAccessTime = 0;
            canAccess = true;

            this.globalCount.set(0); // can access the system   and this is same as first access
            this.localCount.set(1);
            this.firstAccessTime = currentTime;
            this.nextTimeWindow = currentTime + configuration.getUnitTime();
            throttleContext.addAndFlushCallerContext(this, this.id);
            throttleContext.replicateTimeWindow(this.id);

            if (log.isDebugEnabled()) {
              log.debug(
                  "Caller="
                      + this.getId()
                      + " has reset counters and added for replication when unit "
                      + "time is not over");
            }
          } else {
            if (log.isDebugEnabled()) {
              String type =
                  ThrottleConstants.IP_BASE == configuration.getType() ? "IP address" : "domain";
              log.debug(
                  "Prohibit period is not yet over for caller with " + type + " - " + this.id);
            }
          }
        }
      }
    }
    return canAccess;
  }