/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#pragma once

#include <algorithm>
#include <random>

namespace facebook {
namespace react {

/*
 * The source of pseudo-random numbers and some problem-oriented tools built on
 * top of that. We need this class to maintain a reproducible stream of random
 * numbers and abstract away complex math of and C++ STL API behind that.
 */
class Entropy final {
 public:
  using Generator = std::mt19937;

  /*
   * Creates an instance seeded with a real, not pseudo-random, number.
   */
  Entropy() {
    std::random_device device;
    seed_ = device();
    generator_ = std::mt19937(seed_);
  }

  /*
   * Creates an instance seeded with a given number.
   */
  Entropy(uint_fast32_t seed) {
    seed_ = seed;
    generator_ = std::mt19937(seed_);
  }

  uint_fast32_t getSeed() const {
    return seed_;
  }

  /*
   * Family of methods that return uniformly distributed instances of a type
   * within a specified range.
   */
  template <typename T>
  bool random() const {
    T result;
    generateRandomValue(generator_, result);
    return result;
  }

  template <typename T, typename Arg1>
  T random(Arg1 arg1) const {
    T result;
    generateRandomValue(generator_, result, arg1);
    return result;
  }

  template <typename T, typename Arg1, typename Arg2>
  T random(Arg1 arg1, Arg2 arg2) const {
    T result;
    generateRandomValue(generator_, result, arg1, arg2);
    return result;
  }

  void generateRandomValue(
      Generator &generator,
      bool &result,
      double ratio = 0.5) const {
    result = generator() % 10000 < 10000 * ratio;
  }

  void generateRandomValue(Generator &generator, int &result, int min, int max)
      const {
    std::uniform_int_distribution<int> distribution(min, max);
    result = distribution(generator);
  }

  /*
   * Shuffles `std::vector` in place.
   */
  template <typename T>
  void shuffle(T array) const {
    std::shuffle(array.begin(), array.end(), generator_);
  }

  /*
   * Distribute items from a given array into buckets using a normal
   * distribution and given `deviation`.
   */
  template <typename T>
  std::vector<std::vector<T>> distribute(std::vector<T> items, double deviation)
      const {
    std::normal_distribution<> distribution{0, deviation};

    auto deviationLimit = int(deviation * 10);
    auto spreadResult = std::vector<std::vector<T>>(deviationLimit * 2);
    std::fill(spreadResult.begin(), spreadResult.end(), std::vector<T>{});

    for (auto const &item : items) {
      auto position = int(distribution(generator_) + deviationLimit);
      position = std::max(0, std::min(position, deviationLimit * 2));

      if (position < spreadResult.size()) {
        spreadResult[position].push_back(item);
      }
    }

    auto result = std::vector<std::vector<T>>{};
    for (auto const &chunk : spreadResult) {
      if (chunk.size() == 0) {
        continue;
      }
      result.push_back(chunk);
    }

    return result;
  }

 private:
  mutable std::mt19937 generator_;
  uint_fast32_t seed_;
};

} // namespace react
} // namespace facebook
