//------------------------------------------------------------------------------
/*
    This file is part of rippled: https://github.com/ripple/rippled
    Copyright (c) 2012, 2013 Ripple Labs Inc.

    Permission to use, copy, modify, and/or distribute this software for any
    purpose  with  or without fee is hereby granted, provided that the above
    copyright notice and this permission notice appear in all copies.

    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================

#ifndef RIPPLE_BASICS_RANDOM_H_INCLUDED
#define RIPPLE_BASICS_RANDOM_H_INCLUDED

#include <ripple/beast/xor_shift_engine.h>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <mutex>
#include <random>
#include <type_traits>

namespace ripple {

#ifndef __INTELLISENSE__
static_assert(
    std::is_integral<beast::xor_shift_engine::result_type>::value &&
        std::is_unsigned<beast::xor_shift_engine::result_type>::value,
    "The Ripple default PRNG engine must return an unsigned integral type.");

static_assert(
    std::numeric_limits<beast::xor_shift_engine::result_type>::max() >=
        std::numeric_limits<std::uint64_t>::max(),
    "The Ripple default PRNG engine return must be at least 64 bits wide.");
#endif

namespace detail {

// Determines if a type can be called like an Engine
template <class Engine, class Result = typename Engine::result_type>
using is_engine = std::is_invocable_r<Result, Engine>;
}  // namespace detail

/** Return the default random engine.

    This engine is guaranteed to be deterministic, but by
    default will be randomly seeded. It is NOT cryptographically
    secure and MUST NOT be used to generate randomness that
    will be used for keys, secure cookies, IVs, padding, etc.

    Each thread gets its own instance of the engine which
    will be randomly seeded.
*/
inline beast::xor_shift_engine&
default_prng()
{
    // This is used to seed the thread-specific PRNGs on demand
    static beast::xor_shift_engine seeder = [] {
        std::random_device rng;
        std::uniform_int_distribution<std::uint64_t> distribution{1};
        return beast::xor_shift_engine(distribution(rng));
    }();

    // This protects the seeder
    static std::mutex m;

    // The thread-specific PRNGs:
    thread_local beast::xor_shift_engine engine = [] {
        std::uint64_t seed;
        {
            std::lock_guard lk(m);
            std::uniform_int_distribution<std::uint64_t> distribution{1};
            seed = distribution(seeder);
        }
        return beast::xor_shift_engine{seed};
    }();

    return engine;
}

/** Return a uniformly distributed random integer.

    @param min The smallest value to return. If not specified
               the value defaults to 0.
    @param max The largest value to return. If not specified
               the value defaults to the largest value that
               can be represented.

    The randomness is generated by the specified engine (or
    the default engine if one is not specified). The result
    is cryptographically secure only when the engine passed
    into the function is cryptographically secure.

    @note The range is always a closed interval, so calling
          rand_int(-5, 15) can return any integer in the
          closed interval [-5, 15]; similarly, calling
          rand_int(7) can return any integer in the closed
          interval [0, 7].
*/
/** @{ */
template <class Engine, class Integral>
std::enable_if_t<
    std::is_integral<Integral>::value && detail::is_engine<Engine>::value,
    Integral>
rand_int(Engine& engine, Integral min, Integral max)
{
    assert(max > min);

    // This should have no state and constructing it should
    // be very cheap. If that turns out not to be the case
    // it could be hand-optimized.
    return std::uniform_int_distribution<Integral>(min, max)(engine);
}

template <class Integral>
std::enable_if_t<std::is_integral<Integral>::value, Integral>
rand_int(Integral min, Integral max)
{
    return rand_int(default_prng(), min, max);
}

template <class Engine, class Integral>
std::enable_if_t<
    std::is_integral<Integral>::value && detail::is_engine<Engine>::value,
    Integral>
rand_int(Engine& engine, Integral max)
{
    return rand_int(engine, Integral(0), max);
}

template <class Integral>
std::enable_if_t<std::is_integral<Integral>::value, Integral>
rand_int(Integral max)
{
    return rand_int(default_prng(), max);
}

template <class Integral, class Engine>
std::enable_if_t<
    std::is_integral<Integral>::value && detail::is_engine<Engine>::value,
    Integral>
rand_int(Engine& engine)
{
    return rand_int(engine, std::numeric_limits<Integral>::max());
}

template <class Integral = int>
std::enable_if_t<std::is_integral<Integral>::value, Integral>
rand_int()
{
    return rand_int(default_prng(), std::numeric_limits<Integral>::max());
}
/** @} */

/** Return a random byte */
/** @{ */
template <class Byte, class Engine>
std::enable_if_t<
    (std::is_same<Byte, unsigned char>::value ||
     std::is_same<Byte, std::uint8_t>::value) &&
        detail::is_engine<Engine>::value,
    Byte>
rand_byte(Engine& engine)
{
    return static_cast<Byte>(rand_int<Engine, std::uint32_t>(
        engine,
        std::numeric_limits<Byte>::min(),
        std::numeric_limits<Byte>::max()));
}

template <class Byte = std::uint8_t>
std::enable_if_t<
    (std::is_same<Byte, unsigned char>::value ||
     std::is_same<Byte, std::uint8_t>::value),
    Byte>
rand_byte()
{
    return rand_byte<Byte>(default_prng());
}
/** @} */

/** Return a random boolean value */
/** @{ */
template <class Engine>
inline bool
rand_bool(Engine& engine)
{
    return rand_int(engine, 1) == 1;
}

inline bool
rand_bool()
{
    return rand_bool(default_prng());
}
/** @} */

}  // namespace ripple

#endif  // RIPPLE_BASICS_RANDOM_H_INCLUDED
