class Bucket extends Stat {
    long startTimeMillis = -1;

    void reset(long startTimeMillis) {
      this.startTimeMillis = startTimeMillis;
      reset();
    }

    void copyFrom(Bucket other) {
      startTimeMillis = other.startTimeMillis;
      successes = other.successes;
      failures = other.failures;
    }

    @Override
    public String toString() {
      return "[startTime=" + startTimeMillis + ", s=" + successes + ", f=" + failures + ']';
    }
  }

  /**
   * Copies the most recent stats from the {@code oldStats} into this in order from oldest to newest and orders buckets
   * from oldest to newest, with uninitialized buckets counting as oldest.
   */
  void copyStats(CircuitStats oldStats) {
    if (oldStats instanceof TimedCircuitStats) {
      TimedCircuitStats old = (TimedCircuitStats) oldStats;
      int bucketsToCopy = Math.min(old.buckets.length, buckets.length);

      // Get oldest index to start copying from
      int oldIndex = old.indexAfter(old.currentIndex);
      for (int i = 0; i < bucketsToCopy; i++)
        oldIndex = old.indexBefore(oldIndex);

      for (int i = 0; i < bucketsToCopy; i++) {
        if (i != 0) {
          oldIndex = old.indexAfter(oldIndex);
          currentIndex = nextIndex();
        }
        buckets[currentIndex].copyFrom(old.buckets[oldIndex]);
        summary.add(buckets[currentIndex]);
      }
    } else {
      buckets[0].startTimeMillis = clock.currentTimeMillis();
      copyExecutions(oldStats);
    }
  }

  @Override
  public synchronized void recordSuccess() {
    getCurrentBucket().successes++;
    summary.successes++;
  }

  @Override
  public synchronized void recordFailure() {
    getCurrentBucket().failures++;
    summary.failures++;
  }

  @Override
  public int getExecutionCount() {
    return summary.successes + summary.failures;
  }

  @Override
  public int getFailureCount() {
    return summary.failures;
  }

  @Override
  public synchronized int getFailureRate() {
    int executions = getExecutionCount();
    return (int) Math.round(executions == 0 ? 0 : (double) summary.failures / (double) executions * 100.0);
  }

  @Override
  public int getSuccessCount() {
    return summary.successes;
  }

  @Override
  public synchronized int getSuccessRate() {
    int executions = getExecutionCount();
    return (int) Math.round(executions == 0 ? 0 : (double) summary.successes / (double) executions * 100.0);
  }

  @Override
  public synchronized void reset() {
    long startTimeMillis = clock.currentTimeMillis();
    for (Bucket bucket : buckets) {
      bucket.reset(startTimeMillis);
      startTimeMillis += bucketSizeMillis;
    }
    summary.reset();
    currentIndex = 0;
  }

  /**
   * Returns the current bucket based on the current time, moving the internal storage to the current bucket if
   * necessary, resetting bucket stats along the way.
   */
  synchronized Bucket getCurrentBucket() {
    Bucket previousBucket, currentBucket = buckets[currentIndex];
    long currentTime = clock.currentTimeMillis();
    long timeDiff = currentTime - currentBucket.startTimeMillis;
    if (timeDiff >= bucketSizeMillis) {
      int bucketsToMove = (int) (timeDiff / bucketSizeMillis);
      if (bucketsToMove <= buckets.length) {
        // Reset some buckets
        do {
          currentIndex = nextIndex();
          previousBucket = currentBucket;
          currentBucket = buckets[currentIndex];
          long bucketStartTime = currentBucket.startTimeMillis == -1 ?
            previousBucket.startTimeMillis + bucketSizeMillis :
            currentBucket.startTimeMillis + windowSizeMillis;
          summary.remove(currentBucket);
          currentBucket.reset(bucketStartTime);
          bucketsToMove--;
        } while (bucketsToMove > 0);
      } else {
        // Reset all buckets
        reset();
      }
    }

    return currentBucket;
  }

  /**
   * Returns the next index.
   */
  private int nextIndex() {
    return (currentIndex + 1) % buckets.length;
  }

  /**
   * Returns the index after the {@code index}.
   */
  private int indexAfter(int index) {<FILL_FUNCTION_BODY>

    return index == buckets.length - 1 ? 0 : index + 1;
  
<no_super_class>