/*
 * Copyright 2015, gRPC Authors All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.grpc.internal;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.common.annotations.VisibleForTesting;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * Retry Policy for Transport reconnection.  Initial parameters from
 * https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
 *
 * <p>TODO(carl-mastrangelo): add unit tests for this class
 */
final class ExponentialBackoffPolicy implements BackoffPolicy {
  static final class Provider implements BackoffPolicy.Provider {
    @Override
    public BackoffPolicy get() {
      return new ExponentialBackoffPolicy();
    }
  }

  private Random random = new Random();
  private long initialBackoffNanos = TimeUnit.SECONDS.toNanos(1);
  private long maxBackoffNanos = TimeUnit.MINUTES.toNanos(2);
  private double multiplier = 1.6;
  private double jitter = .2;

  private long nextBackoffNanos = initialBackoffNanos;

  @Override
  public long nextBackoffNanos() {
    long currentBackoffNanos = nextBackoffNanos;
    nextBackoffNanos = Math.min((long) (currentBackoffNanos * multiplier), maxBackoffNanos);
    return currentBackoffNanos
        + uniformRandom(-jitter * currentBackoffNanos, jitter * currentBackoffNanos);
  }

  private long uniformRandom(double low, double high) {
    checkArgument(high >= low);
    double mag = high - low;
    return (long) (random.nextDouble() * mag + low);
  }

  /*
   * No guice and no flags means we get to implement these setters for testing ourselves.  Do not
   * call these from non-test code.
   */

  @VisibleForTesting
  ExponentialBackoffPolicy setRandom(Random random) {
    this.random = random;
    return this;
  }

  @VisibleForTesting
  ExponentialBackoffPolicy setInitialBackoffNanos(long initialBackoffNanos) {
    this.initialBackoffNanos = initialBackoffNanos;
    return this;
  }

  @VisibleForTesting
  ExponentialBackoffPolicy setMaxBackoffNanos(long maxBackoffNanos) {
    this.maxBackoffNanos = maxBackoffNanos;
    return this;
  }

  @VisibleForTesting
  ExponentialBackoffPolicy setMultiplier(double multiplier) {
    this.multiplier = multiplier;
    return this;
  }

  @VisibleForTesting
  ExponentialBackoffPolicy setJitter(double jitter) {
    this.jitter = jitter;
    return this;
  }
}

