package net.jodah.lyra.internal;

import net.jodah.lyra.util.Duration;

/**
 * Statistics to track the usage of a RecurringPolicy.
 * 
 * @author Jonathan Halterman
 */
public final class RecurringStats {
  // Retry stats
  private final int maxAttempts;
  private final long maxDuration;
  private final long interval;
  private long startTime;

  // Backoff stats
  private double intervalMultiplier = -1;
  private long maxInterval;

  // Mutable state
  private int attemptCount;
  private long waitTime;
  private long maxWaitTime;

  public RecurringStats(RecurringPolicy<?> retryPolicy) {
    maxAttempts = retryPolicy.getMaxAttempts();
    interval = retryPolicy.getInterval() == null ? 0 : retryPolicy.getInterval().toNanos();
    if (retryPolicy.getMaxDuration() == null) {
      maxDuration = -1;
      waitTime = interval;
      maxWaitTime = -1;
    } else {
      maxDuration = retryPolicy.getMaxDuration().toNanos();
      waitTime = Math.min(interval, maxDuration);
      maxWaitTime = maxDuration;
    }

    if (retryPolicy.getMaxInterval() != null) {
      intervalMultiplier = retryPolicy.getIntervalMultiplier();
      maxInterval = retryPolicy.getMaxInterval().toNanos();
    }
  }

  /**
   * Returns the max amount of time that an external caller should wait before the retry policy is
   * exceeded. The max wait time is calculated each time {@link #incrementAttempts()} or
   * {@link #incrementTime()} is called.
   */
  public Duration getMaxWaitTime() {
    return maxWaitTime == -1 ? Duration.inf() : Duration.nanos(maxWaitTime);
  }

  /**
   * Returns the amount of time that an external caller should wait, based on the retry policy,
   * before performing a retry. The wait time is calculated each time {@link #incrementAttempts()}
   * or {@link #incrementTime()} is called.
   */
  public Duration getWaitTime() {
    return Duration.nanos(waitTime);
  }

  /**
   * Increments the retry/recovery attempts and time.
   */
  public void incrementAttempts() {
    attemptCount++;
    incrementTime();
  }

  public void incrementTime() {
    long now = System.nanoTime();

    // First time
    if (startTime == 0)
      startTime = now;
    else if (intervalMultiplier != -1)
      waitTime = Math.min(maxInterval, (long) (waitTime * intervalMultiplier));

    if (maxDuration != -1) {
      long elapsedNanos = now - startTime;
      waitTime = Math.min(waitTime, maxDuration - elapsedNanos);
      maxWaitTime = maxDuration - elapsedNanos;
    }
  }

  /**
   * Returns true if the max attempts or max duration for the recurring policy have been exceeded
   * else false.
   */
  public boolean isPolicyExceeded() {
    boolean withinMaxRetries = maxAttempts == -1 || attemptCount < maxAttempts;
    boolean withinMaxDuration = maxDuration == -1 || System.nanoTime() - startTime < maxDuration;
    return !withinMaxRetries || !withinMaxDuration;
  }
}