/*
 *  Copyright (c) 2016, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 */

#ifndef FATAL_INCLUDE_fatal_math_compile_time_rng_h
#define FATAL_INCLUDE_fatal_math_compile_time_rng_h

#include <type_traits>

namespace fatal {
namespace detail {
namespace compile_time_rng_impl {

template <typename T>
using default_compile_time_rng_prime = std::integral_constant<T, 31>;

template <
  typename T, T EntropySeed, T Hash,
  template <std::size_t> class TEntropy, std::size_t Index = 0,
  unsigned char Next = TEntropy<Index>::value
>
struct hash_impl {
  static_assert(EntropySeed > 1, "expected EntropySeed > 1");

  using result = typename hash_impl<
    T, EntropySeed,
    static_cast<T>((Hash * EntropySeed) ^ Hash ^ TEntropy<Index>::value),
    TEntropy, Index + 1
  >::result;
};

template <
  typename T, T EntropySeed, T Hash,
  template <std::size_t> class TEntropy, std::size_t Index
>
struct hash_impl<T, EntropySeed, Hash, TEntropy, Index, 0> {
  static_assert(Index > 0, "expected a non-empty source");

  using result = std::integral_constant<T, Hash>;
};

template <
  typename T, template <std::size_t> class TEntropy, T EntropySeed, T Seed
>
using hash = typename hash_impl<T, EntropySeed, Seed, TEntropy>::result;

template <std::size_t Index>
using date_entropy = std::integral_constant<unsigned char, __DATE__[Index]>;

template <std::size_t Index>
using time_entropy = std::integral_constant<unsigned char, __TIME__[Index]>;

template <std::size_t Index>
using file_entropy = std::integral_constant<unsigned char, __FILE__[Index]>;

template <typename T>
using entropy = std::integral_constant<
  T,
  static_cast<T>(
     __cplusplus ^ __LINE__
# ifdef __STDC_VERSION__
      ^ __STDC_VERSION__
# endif // __STDC_VERSION__
  )
>;

template <typename T, T EntropySeed, T Seed>
using mix = std::integral_constant<
  T,
  Seed ^ entropy<T>::value ^ __LINE__
    ^ hash<T, date_entropy, EntropySeed, static_cast<T>(Seed ^ __LINE__)>::value
    ^ hash<T, time_entropy, EntropySeed, static_cast<T>(Seed ^ __LINE__)>::value
    ^ hash<T, file_entropy, EntropySeed, static_cast<T>(Seed ^ __LINE__)>::value
>;

} // namespace compile_time_rng_impl {
} // namespace detail {

//////////////////////
// compile_time_rng //
//////////////////////

/**
 * A compile-time pseudo-random number generator.
 *
 * NOTE: this is a compile-time facility. Given the same parameters,
 * the same number will be generated regardless of how many times
 * the generator is called.
 *
 * NOTE: the current implementation is a very low quality pseudo-random number
 * generator, for proof-of-concept purposes only. To be replaced by a decent
 * PRNG.
 *
 * Parameters:
 *
 *  - T: the type of the numbers generated by the pseudo-random number
 *    generator. Must be an unsigned integral. Defaults to `std::size_t`.
 *
 *  - PreSeed: a seed common to all calls to this generator.
 *    It exists for convenience reasons.
 *
 *  - EntropySeed: a number used during the computation of compile-time entropy
 *    for the the pseudo-random number generator. Doesn't necessarily have to be
 *    a prime, but primes should yield better results.
 *
 * Example:
 *
 *  // yields `compile_time_rng<unsigned, *implementation defined*>`
 *  using rng = FATAL_COMPILE_TIME_RNG<unsigned>;
 *
 *  // yields a pseudo-random `unsigned` generated at compile time.
 *  using result = rng::get<>;
 *
 * @author: Marcelo Juchem <marcelo@fb.com>
 */
template <
  typename T = std::size_t,
  T PreSeed = 0,
  T EntropySeed = detail::compile_time_rng_impl::default_compile_time_rng_prime<
    T
  >::value
>
struct compile_time_rng {
  /**
   * The type of the numbers generated.
   *
   * @author: Marcelo Juchem <marcelo@fb.com>
   */
  using type = T;

  static_assert(std::is_unsigned<T>::value, "T must be an unsigned integral");

  /**
   * A seeded compile-time pseudo-random number generator.
   *
   * Parameters:
   *
   *  - Seed: an initial seed that defines the sequence of numbers
   *    generated on all iterations of this generator.
   *
   * Example:
   *
   *  template <typename TRNG>
   *  std::vector<std::size_t> random_sequence() {
   *    return {
   *      TRNG::template get<0>::value,
   *      TRNG::template get<1>::value,
   *      TRNG::template get<2>::value,
   *      TRNG::template get<3>::value
   *    };
   *  }
   *
   *  using rng = compile_time_rng<>;
   *
   *  // yields a pseudo-randomly generated sequence
   *  auto sequence1 = random_sequence<rng::seed<123>>();
   *
   *  // yields the exact same pseudo-randomly generated sequence
   *  // from `sequence1` since we're using the same seed
   *  auto sequence2 = random_sequence<rng::seed<123>>();
   *
   *  // yields a pseudo-randomly generated sequence, possibly different
   *  // from `sequence1` since we're using a different seed
   *  auto sequence3 = random_sequence<rng::seed<456>>();
   *
   * @author: Marcelo Juchem <marcelo@fb.com>
   */
  template <T Seed = 0>
  struct seed {
    /**
     * A `std::integral_constant` representing the pseudo-randomly
     * generated number.
     *
     * NOTE: this is a compile-time facility. Given the same parameters,
     * the same number will be generated regardless of how many times
     * the generator is called.
     *
     * Parameters:
     *
     *  - Iteration: a convenient way to generate a new number without
     *    having to change the seed. The proper way to use this parameter
     *    is to pass 0 the first time it is called, 1 the second time,
     *    2 the third time and so forth. Defaults to 0.
     *
     * Example:
     *
     *  using rng = compile_time_rng<unsigned>::seed<>;
     *
     *  // yields a pseudo-random `unsigned` generated at compile time.
     *  using first = rng::get<0>;
     *
     *  // yields a pseudo-random `unsigned` generated at compile time,
     *  // possibly different from `first`.
     *  using second = rng::get<1>;
     *
     * @author: Marcelo Juchem <marcelo@fb.com>
     */
    template <std::size_t Iteration = 0>
    using get = detail::compile_time_rng_impl::mix<
      T, EntropySeed,
      static_cast<T>((PreSeed ^ Seed ^ __LINE__) * (Iteration + 1))
    >;
  };

  /**
   * A convenient shortcut to `seed<>::get`.
   *
   * @author: Marcelo Juchem <marcelo@fb.com>
   */
  template <std::size_t Iteration = 0>
  using get = typename seed<>::template get<Iteration>;

};

////////////////////////////
// FATAL_COMPILE_TIME_RNG //
////////////////////////////

namespace detail {
namespace compile_time_rng_impl {

template <std::size_t Line>
struct helper {
  template <typename T = std::size_t>
  using bind = compile_time_rng<T, static_cast<T>(Line)>;
};

} // namespace compile_time_rng_impl {
} // namespace detail {

/**
 * A pre-seeded `compile_time_rng`.
 *
 * Parameters:
 *
 *  - T: the type of the numbers generated by the pseudo-random number
 *      generator. Defaults to `std::size_t`.
 *
 * Example:
 *
 *  // yields `compile_time_rng<std::size_t, *implementation defined*>`
 *  using rng1 = FATAL_COMPILE_TIME_RNG<>;
 *
 *  auto const n = rng1::get<>::value;
 *
 *  // yields `compile_time_rng<unsigned, *implementation defined*>`
 *  using rng2 = FATAL_COMPILE_TIME_RNG<unsigned>;
 *
 *  auto const x = rng2::get<>::value;
 *
 * @author: Marcelo Juchem <marcelo@fb.com>
 */
#define FATAL_COMPILE_TIME_RNG \
  ::fatal::detail::compile_time_rng_impl::helper<__LINE__>::bind

} // namespace fatal {

#endif // FATAL_INCLUDE_fatal_math_compile_time_rng_h
