package com.google.common.util.concurrent;

import java.util.concurrent.TimeUnit;

abstract class SmoothRateLimiter extends RateLimiter {
   double storedPermits;
   double maxPermits;
   double stableIntervalMicros;
   private long nextFreeTicketMicros;

   private SmoothRateLimiter(RateLimiter.SleepingStopwatch stopwatch) {
      super(stopwatch);
      this.nextFreeTicketMicros = 0L;
   }

   final void doSetRate(double permitsPerSecond, long nowMicros) {
      this.resync(nowMicros);
      double stableIntervalMicros = (double)TimeUnit.SECONDS.toMicros(1L) / permitsPerSecond;
      this.stableIntervalMicros = stableIntervalMicros;
      this.doSetRate(permitsPerSecond, stableIntervalMicros);
   }

   abstract void doSetRate(double var1, double var3);

   final double doGetRate() {
      return (double)TimeUnit.SECONDS.toMicros(1L) / this.stableIntervalMicros;
   }

   final long queryEarliestAvailable(long nowMicros) {
      return this.nextFreeTicketMicros;
   }

   final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
      this.resync(nowMicros);
      long returnValue = this.nextFreeTicketMicros;
      double storedPermitsToSpend = Math.min((double)requiredPermits, this.storedPermits);
      double freshPermits = (double)requiredPermits - storedPermitsToSpend;
      long waitMicros = this.storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + (long)(freshPermits * this.stableIntervalMicros);
      this.nextFreeTicketMicros += waitMicros;
      this.storedPermits -= storedPermitsToSpend;
      return returnValue;
   }

   abstract long storedPermitsToWaitTime(double var1, double var3);

   private void resync(long nowMicros) {
      if (nowMicros > this.nextFreeTicketMicros) {
         this.storedPermits = Math.min(this.maxPermits, this.storedPermits + (double)(nowMicros - this.nextFreeTicketMicros) / this.stableIntervalMicros);
         this.nextFreeTicketMicros = nowMicros;
      }

   }

   // $FF: synthetic method
   SmoothRateLimiter(RateLimiter.SleepingStopwatch x0, Object x1) {
      this(x0);
   }

   static final class SmoothBursty extends SmoothRateLimiter {
      final double maxBurstSeconds;

      SmoothBursty(RateLimiter.SleepingStopwatch stopwatch, double maxBurstSeconds) {
         super(stopwatch, null);
         this.maxBurstSeconds = maxBurstSeconds;
      }

      void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
         double oldMaxPermits = this.maxPermits;
         this.maxPermits = this.maxBurstSeconds * permitsPerSecond;
         if (oldMaxPermits == Double.POSITIVE_INFINITY) {
            this.storedPermits = this.maxPermits;
         } else {
            this.storedPermits = oldMaxPermits == 0.0D ? 0.0D : this.storedPermits * this.maxPermits / oldMaxPermits;
         }

      }

      long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
         return 0L;
      }
   }

   static final class SmoothWarmingUp extends SmoothRateLimiter {
      private final long warmupPeriodMicros;
      private double slope;
      private double halfPermits;

      SmoothWarmingUp(RateLimiter.SleepingStopwatch stopwatch, long warmupPeriod, TimeUnit timeUnit) {
         super(stopwatch, null);
         this.warmupPeriodMicros = timeUnit.toMicros(warmupPeriod);
      }

      void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
         double oldMaxPermits = this.maxPermits;
         this.maxPermits = (double)this.warmupPeriodMicros / stableIntervalMicros;
         this.halfPermits = this.maxPermits / 2.0D;
         double coldIntervalMicros = stableIntervalMicros * 3.0D;
         this.slope = (coldIntervalMicros - stableIntervalMicros) / this.halfPermits;
         if (oldMaxPermits == Double.POSITIVE_INFINITY) {
            this.storedPermits = 0.0D;
         } else {
            this.storedPermits = oldMaxPermits == 0.0D ? this.maxPermits : this.storedPermits * this.maxPermits / oldMaxPermits;
         }

      }

      long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
         double availablePermitsAboveHalf = storedPermits - this.halfPermits;
         long micros = 0L;
         if (availablePermitsAboveHalf > 0.0D) {
            double permitsAboveHalfToTake = Math.min(availablePermitsAboveHalf, permitsToTake);
            micros = (long)(permitsAboveHalfToTake * (this.permitsToTime(availablePermitsAboveHalf) + this.permitsToTime(availablePermitsAboveHalf - permitsAboveHalfToTake)) / 2.0D);
            permitsToTake -= permitsAboveHalfToTake;
         }

         micros = (long)((double)micros + this.stableIntervalMicros * permitsToTake);
         return micros;
      }

      private double permitsToTime(double permits) {
         return this.stableIntervalMicros + permits * this.slope;
      }
   }
}
