// ------------------------------------------------------------------------------------------------
#include "Library/Numeric/Random.hpp"
#include "Core/Common.hpp"
#include "Core/Buffer.hpp"

// ------------------------------------------------------------------------------------------------
#include <ctime>
#include <memory>
#include <random>

// ------------------------------------------------------------------------------------------------
#ifdef SQMOD_OS_WINDOWS
    #include <process.h>
#else
    #include <sys/types.h>
    #include <unistd.h>
#endif

// ------------------------------------------------------------------------------------------------
namespace SqMod {

// ------------------------------------------------------------------------------------------------
static std::unique_ptr< std::mt19937 >          RG32_MT19937 = // NOLINT(cert-err58-cpp)
                            std::make_unique< std::mt19937 >(GenerateSeed());

static std::unique_ptr< std::mt19937_64 >       RG64_MT19937 = // NOLINT(cert-err58-cpp)
                            std::make_unique< std::mt19937_64 >(GenerateSeed());

// ------------------------------------------------------------------------------------------------
static std::uniform_int_distribution< int8_t >    Int8_Dist(std::numeric_limits< int8_t >::min(), // NOLINT(cert-err58-cpp)
                                                            std::numeric_limits< int8_t >::max());
static std::uniform_int_distribution< uint8_t >   uint8_t_Dist(std::numeric_limits< uint8_t >::min(), // NOLINT(cert-err58-cpp)
                                                            std::numeric_limits< uint8_t >::max());

static std::uniform_int_distribution< int16_t >   Int16_Dist(std::numeric_limits< int16_t >::min(), // NOLINT(cert-err58-cpp)
                                                            std::numeric_limits< int16_t >::max());
static std::uniform_int_distribution< uint16_t >  Uint16_Dist(std::numeric_limits< uint16_t >::min(), // NOLINT(cert-err58-cpp)
                                                            std::numeric_limits< uint16_t >::max());

static std::uniform_int_distribution< int32_t >   Int32_Dist(std::numeric_limits< int32_t >::min(), // NOLINT(cert-err58-cpp)
                                                            std::numeric_limits< int32_t >::max());
static std::uniform_int_distribution< uint32_t >  Uint32_Dist(std::numeric_limits< uint32_t >::min(), // NOLINT(cert-err58-cpp)
                                                            std::numeric_limits< uint32_t >::max());

static std::uniform_int_distribution< int64_t >   Int64_Dist(std::numeric_limits< int64_t >::min(), // NOLINT(cert-err58-cpp)
                                                            std::numeric_limits< int64_t >::max());
static std::uniform_int_distribution< uint64_t >  Uint64_Dist(std::numeric_limits< uint64_t >::min(), // NOLINT(cert-err58-cpp)
                                                            std::numeric_limits< uint64_t >::max());

static std::uniform_real_distribution<float>  Float32_Dist(std::numeric_limits< float >::min(), // NOLINT(cert-err58-cpp)
                                                            std::numeric_limits< float >::max());
static std::uniform_real_distribution<double>  Float64_Dist(std::numeric_limits< double >::min(), // NOLINT(cert-err58-cpp)
                                                            std::numeric_limits< double >::max());

// ------------------------------------------------------------------------------------------------
static std::uniform_int_distribution< String::value_type >
                                    String_Dist(std::numeric_limits< String::value_type >::min(), // NOLINT(cert-err58-cpp)
                                                std::numeric_limits< String::value_type >::max());

// ------------------------------------------------------------------------------------------------
uint32_t GenerateSeed()
{
    unsigned long a = clock();
    unsigned long b = time(nullptr);
#ifdef SQMOD_OS_WINDOWS
    unsigned long c = _getpid();
#else
    unsigned long c = getpid();
#endif
    // Mangle
    a=a-b;  a=a-c;  a=a^(c >> 13);
    b=b-c;  b=b-a;  b=b^(a << 8);
    c=c-a;  c=c-b;  c=c^(b >> 13);
    a=a-b;  a=a-c;  a=a^(c >> 12);
    b=b-c;  b=b-a;  b=b^(a << 16);
    c=c-a;  c=c-b;  c=c^(b >> 5);
    a=a-b;  a=a-c;  a=a^(c >> 3);
    b=b-c;  b=b-a;  b=b^(a << 10);
    c=c-a;  c=c-b;  c=c^(b >> 15);
    // Return result
    return c;
}

// ------------------------------------------------------------------------------------------------
size_t GenerateSeed2()
{
    struct { // NOLINT(cppcoreguidelines-pro-type-member-init)
        std::clock_t    c;
        std::time_t     t;
#ifdef SQMOD_OS_WINDOWS
        int             p;
#else
        pid_t           p;
#endif
    } data;
    data.c = std::clock();
    data.t = std::time(nullptr);
#ifdef SQMOD_OS_WINDOWS
    data.p = _getpid();
#else
    data.p = getpid();
#endif
    // Mangle and return result
    return FnvHash(reinterpret_cast< const uint8_t * >(&data), sizeof(data));
}

// ------------------------------------------------------------------------------------------------
void ReseedRandom()
{
    RG32_MT19937 = std::make_unique<std::mt19937>(GenerateSeed());
    RG64_MT19937 = std::make_unique<std::mt19937_64>(GenerateSeed());
}

void ReseedRandom(uint32_t n)
{
    RG32_MT19937 = std::make_unique<std::mt19937>(n);
    RG64_MT19937 = std::make_unique<std::mt19937_64>(n);
}

// ------------------------------------------------------------------------------------------------
void ReseedRandom32()
{
    RG32_MT19937 = std::make_unique<std::mt19937>(GenerateSeed());
}

void ReseedRandom32(uint32_t n)
{
    RG32_MT19937 = std::make_unique<std::mt19937>(n);
}

// ------------------------------------------------------------------------------------------------
void ReseedRandom64()
{
    RG64_MT19937 = std::make_unique<std::mt19937_64>(GenerateSeed());
}

void ReseedRandom64(uint32_t n)
{
    RG64_MT19937 = std::make_unique<std::mt19937_64>(n);
}

// ------------------------------------------------------------------------------------------------
int8_t GetRandomInt8()
{
    return Int8_Dist(*RG32_MT19937);
}

int8_t GetRandomInt8(int8_t n)
{
    return std::uniform_int_distribution< int8_t >(0, n)(*RG32_MT19937);
}

int8_t GetRandomInt8(int8_t m, int8_t n)
{
    return std::uniform_int_distribution< int8_t >(m, n)(*RG32_MT19937);
}

// ------------------------------------------------------------------------------------------------
uint8_t GetRandomUint8()
{
    return uint8_t_Dist(*RG32_MT19937);
}

uint8_t GetRandomUint8(uint8_t n)
{
    return std::uniform_int_distribution< uint8_t >(0, n)(*RG32_MT19937);
}

uint8_t GetRandomUint8(uint8_t m, uint8_t n)
{
    return std::uniform_int_distribution< uint8_t >(m, n)(*RG32_MT19937);
}

// ------------------------------------------------------------------------------------------------
int16_t GetRandomInt16()
{
    return Int16_Dist(*RG32_MT19937);
}

int16_t GetRandomInt16(int16_t n)
{
    return std::uniform_int_distribution< int16_t >(0, n)(*RG32_MT19937);
}

int16_t GetRandomInt16(int16_t m, int16_t n)
{
    return std::uniform_int_distribution< int16_t >(m, n)(*RG32_MT19937);
}

// ------------------------------------------------------------------------------------------------
uint16_t GetRandomUint16()
{
    return Uint16_Dist(*RG32_MT19937);
}

uint16_t GetRandomUint16(uint16_t n)
{
    return std::uniform_int_distribution< uint16_t >(0, n)(*RG32_MT19937);
}

uint16_t GetRandomUint16(uint16_t m, uint16_t n)
{
    return std::uniform_int_distribution< uint16_t >(m, n)(*RG32_MT19937);
}

// ------------------------------------------------------------------------------------------------
int32_t GetRandomInt32()
{
    return Int32_Dist(*RG32_MT19937);
}

int32_t GetRandomInt32(int32_t n)
{
    return std::uniform_int_distribution< int32_t >(0, n)(*RG32_MT19937);
}

int32_t GetRandomInt32(int32_t m, int32_t n)
{
    return std::uniform_int_distribution< int32_t >(m, n)(*RG32_MT19937);
}


// ------------------------------------------------------------------------------------------------
uint32_t GetRandomUint32()
{
    return Uint32_Dist(*RG32_MT19937);
}

uint32_t GetRandomUint32(uint32_t n)
{
    return std::uniform_int_distribution< uint32_t >(0, n)(*RG32_MT19937);
}

uint32_t GetRandomUint32(uint32_t m, uint32_t n)
{
    return std::uniform_int_distribution< uint32_t >(m, n)(*RG32_MT19937);
}


// ------------------------------------------------------------------------------------------------
int64_t GetRandomInt64()
{
    return Int64_Dist(*RG64_MT19937);
}

int64_t GetRandomInt64(int64_t n)
{
    return std::uniform_int_distribution< int64_t >(0, n)(*RG64_MT19937);
}

int64_t GetRandomInt64(int64_t m, int64_t n)
{
    return std::uniform_int_distribution< int64_t >(m, n)(*RG64_MT19937);
}


// ------------------------------------------------------------------------------------------------
uint64_t GetRandomUint64()
{
    return Uint64_Dist(*RG64_MT19937);
}

uint64_t GetRandomUint64(uint64_t n)
{
    return std::uniform_int_distribution< uint64_t >(0, n)(*RG64_MT19937);
}

uint64_t GetRandomUint64(uint64_t m, uint64_t n)
{
    return std::uniform_int_distribution< uint64_t >(m, n)(*RG64_MT19937);
}

// ------------------------------------------------------------------------------------------------
float GetRandomFloat32()
{
    return Float32_Dist(*RG32_MT19937);
}

float GetRandomFloat32(float n)
{
    return std::uniform_real_distribution< float >(0, n)(*RG32_MT19937);
}

float GetRandomFloat32(float m, float n)
{
    return std::uniform_real_distribution< float >(m, n)(*RG32_MT19937);
}

// ------------------------------------------------------------------------------------------------
double GetRandomFloat64()
{
    return Float64_Dist(*RG64_MT19937);
}

double GetRandomFloat64(double n)
{
    return std::uniform_real_distribution< double >(0, n)(*RG64_MT19937);
}

double GetRandomFloat64(double m, double n)
{
    return std::uniform_real_distribution< double >(m, n)(*RG64_MT19937);
}

// ------------------------------------------------------------------------------------------------
void GetRandomString(String & str, String::size_type len)
{
    // Resize to the requested size and fill with 0
    str.resize(len);
    // Generate the requested amount of characters
    for (auto & c : str)
    {
        c = String_Dist(*RG32_MT19937);
    }
}

void GetRandomString(String & str, String::size_type len, String::value_type n)
{
    // Resize to the requested size and fill with 0
    str.resize(len);
    // Create the custom distribution
    std::uniform_int_distribution< String::value_type > dist(1, n);
    // Generate the requested amount of characters
    for (auto & c : str)
    {
        c = dist(*RG32_MT19937);
    }
}

void GetRandomString(String & str, String::size_type len, String::value_type m, String::value_type n)
{
    // Resize to the requested size and fill with 0
    str.resize(len);
    // Create the custom distribution
    std::uniform_int_distribution< String::value_type > dist(m, n);
    // Generate the requested amount of characters
    for (auto & c : str)
    {
        c = dist(*RG32_MT19937);
    }
}

// ------------------------------------------------------------------------------------------------
bool GetRandomBool()
{
    return std::bernoulli_distribution()(*RG32_MT19937);
}

bool GetRandomBool(SQFloat p)
{
    return std::bernoulli_distribution(p)(*RG32_MT19937);
}

// ------------------------------------------------------------------------------------------------
static String RandomString(int32_t len)
{
    // Is there anything to generate?
    if (len <= 0)
        return _SC("");
    // Prepare the string buffer
    String str;
    // Request the random fill
    GetRandomString(str, len);
    // Return ownership of the string
    return str;
}

// ------------------------------------------------------------------------------------------------
static String RandomString(int32_t len, SQChar n)
{
    // Is there anything to generate?
    if (len <= 0)
        return _SC("");
    // Prepare the string buffer
    String str;
    // Request the random fill
    GetRandomString(str, len, n);
    // Return ownership of the string
    return str;
}

// ------------------------------------------------------------------------------------------------
static String RandomString(int32_t len, SQChar m, SQChar n)
{
    // Is there anything to generate?
    if (len <= 0)
        return _SC("");
    // Prepare the string buffer
    String str;
    // Request the random fill
    GetRandomString(str, len, m, n);
    // Return ownership of the string
    return str;
}

// ------------------------------------------------------------------------------------------------
void Register_Random(HSQUIRRELVM vm)
{
    RootTable(vm).Bind(_SC("SqRand"), Table(vm)
        .Func(_SC("GenSeed"), &GenerateSeed)
        .Func(_SC("GenSeed2"), &GenerateSeed2)
        .Overload< void (*)(void) >(_SC("Reseed"), &ReseedRandom)
        .Overload< void (*)(uint32_t) >(_SC("Reseed"), &ReseedRandom)
        .Overload< void (*)(void) >(_SC("Reseed32"), &ReseedRandom32)
        .Overload< void (*)(uint32_t) >(_SC("Reseed32"), &ReseedRandom32)
        .Overload< void (*)(void) >(_SC("Reseed64"), &ReseedRandom64)
        .Overload< void (*)(uint32_t) >(_SC("Reseed64"), &ReseedRandom64)

#ifdef _SQ64
        .Overload< SQInteger (*)(void) >(_SC("Integer"), &GetRandomInt64)
        .Overload< SQInteger (*)(SQInteger) >(_SC("Integer"), &GetRandomInt64)
        .Overload< SQInteger (*)(SQInteger, SQInteger) >(_SC("Integer"), &GetRandomInt64)
#else
        .Overload< SQInteger (*)(void) >(_SC("Integer"), &GetRandomInt32)
        .Overload< SQInteger (*)(SQInteger) >(_SC("Integer"), &GetRandomInt32)
        .Overload< SQInteger (*)(SQInteger, SQInteger) >(_SC("Integer"), &GetRandomInt32)
#endif // _SQ64

#ifdef SQUSEDOUBLE
        .Overload< SQFloat (*)(void) >(_SC("Float"), &GetRandomFloat64)
        .Overload< SQFloat (*)(SQFloat) >(_SC("Float"), &GetRandomFloat64)
        .Overload< SQFloat (*)(SQFloat, SQFloat) >(_SC("Float"), &GetRandomFloat64)
#else
        .Overload< SQFloat (*)(void) >(_SC("Float"), &GetRandomFloat32)
        .Overload< SQFloat (*)(SQFloat) >(_SC("Float"), &GetRandomFloat32)
        .Overload< SQFloat (*)(SQFloat, SQFloat) >(_SC("Float"), &GetRandomFloat32)
#endif // SQUSEDOUBLE

        .Overload< String (*)(int32_t) >(_SC("String"), &RandomString)
        .Overload< String (*)(int32_t, SQChar) >(_SC("String"), &RandomString)
        .Overload< String (*)(int32_t, SQChar, SQChar) >(_SC("String"), &RandomString)
        .Overload< bool (*)(void) >(_SC("Bool"), &GetRandomBool)
        .Overload< bool (*)(SQFloat) >(_SC("Bool"), &GetRandomBool)
    );
}

} // Namespace:: SqMod