/*
 * Copyright 2016 Facebook, Inc.
 *
 * 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.
 */

#include <algorithm>
#include <atomic>
#include <exception>
#include <vector>
#include <folly/futures/Future.h>

#include <gtest/gtest.h>

using namespace std;
using namespace std::chrono;
using namespace folly;

// Runs func num_times in parallel, expects that all of them will take
// at least min_duration and at least 1 execution will take less than
// max_duration.
template <typename D, typename F>
void multiAttemptExpectDurationWithin(size_t num_tries,
                                      D min_duration,
                                      D max_duration,
                                      const F& func) {
  vector<thread> threads(num_tries);
  vector<D> durations(num_tries, D::min());
  for (size_t i = 0; i < num_tries; ++i) {
    threads[i] = thread([&,i]{
      auto start = steady_clock::now();
      func();
      durations[i] = duration_cast<D>(steady_clock::now() - start);
    });
  }
  for (auto& t : threads) {
    t.join();
  }
  sort(durations.begin(), durations.end());
  for (auto d : durations) {
    EXPECT_GE(d, min_duration);
  }
  EXPECT_LE(durations[0], max_duration);
}

TEST(RetryingTest, has_op_call) {
  using ew = exception_wrapper;
  auto policy_raw = [](size_t n, const ew&) { return n < 3; };
  auto policy_fut = [](size_t n, const ew&) { return makeFuture(n < 3); };
  using namespace futures::detail;
  EXPECT_TRUE(retrying_policy_traits<decltype(policy_raw)>::is_raw::value);
  EXPECT_TRUE(retrying_policy_traits<decltype(policy_fut)>::is_fut::value);
}

TEST(RetryingTest, basic) {
  auto r = futures::retrying(
      [](size_t n, const exception_wrapper&) { return n < 3; },
      [](size_t n) {
          return n < 2
            ? makeFuture<size_t>(runtime_error("ha"))
            : makeFuture(n);
      }
  ).wait();
  EXPECT_EQ(2, r.value());
}

TEST(RetryingTest, policy_future) {
  atomic<size_t> sleeps {0};
  auto r = futures::retrying(
      [&](size_t n, const exception_wrapper&) {
          return n < 3
            ? makeFuture(++sleeps).then([] { return true; })
            : makeFuture(false);
      },
      [](size_t n) {
          return n < 2
            ? makeFuture<size_t>(runtime_error("ha"))
            : makeFuture(n);
      }
  ).wait();
  EXPECT_EQ(2, r.value());
  EXPECT_EQ(2, sleeps);
}

TEST(RetryingTest, policy_basic) {
  auto r = futures::retrying(
      futures::retryingPolicyBasic(3),
      [](size_t n) {
          return n < 2
            ? makeFuture<size_t>(runtime_error("ha"))
            : makeFuture(n);
      }
  ).wait();
  EXPECT_EQ(2, r.value());
}

TEST(RetryingTest, policy_capped_jittered_exponential_backoff) {
  multiAttemptExpectDurationWithin(5, milliseconds(200), milliseconds(400), []{
    using ms = milliseconds;
    auto r = futures::retrying(
        futures::retryingPolicyCappedJitteredExponentialBackoff(
          3, ms(100), ms(1000), 0.1, mt19937_64(0),
          [](size_t, const exception_wrapper&) { return true; }),
        [](size_t n) {
            return n < 2
              ? makeFuture<size_t>(runtime_error("ha"))
              : makeFuture(n);
        }
    ).wait();
    EXPECT_EQ(2, r.value());
  });
}

TEST(RetryingTest, policy_sleep_defaults) {
  multiAttemptExpectDurationWithin(5, milliseconds(200), milliseconds(400), []{
    //  To ensure that this compiles with default params.
    using ms = milliseconds;
    auto r = futures::retrying(
        futures::retryingPolicyCappedJitteredExponentialBackoff(
          3, ms(100), ms(1000), 0.1),
        [](size_t n) {
            return n < 2
              ? makeFuture<size_t>(runtime_error("ha"))
              : makeFuture(n);
        }
    ).wait();
    EXPECT_EQ(2, r.value());
  });
}

/*
TEST(RetryingTest, policy_sleep_cancel) {
  multiAttemptExpectDurationWithin(5, milliseconds(0), milliseconds(10), []{
    mt19937_64 rng(0);
    using ms = milliseconds;
    auto r = futures::retrying(
        futures::retryingPolicyCappedJitteredExponentialBackoff(
          5, ms(100), ms(1000), 0.1, rng,
          [](size_t n, const exception_wrapper&) { return true; }),
        [](size_t n) {
            return n < 4
              ? makeFuture<size_t>(runtime_error("ha"))
              : makeFuture(n);
        }
    );
    r.cancel();
    r.wait();
    EXPECT_EQ(2, r.value());
  });
}
*/
