#include <amsculib3/amsculib3.hpp>

namespace amscuda
{
namespace random
{
namespace lcg
{

//////////////////////////////////////////////////
// Deterministic Pseudorandom int32_t Generator //
//////////////////////////////////////////////////

//Simple 32 bit integer deterministic pseudo-random generator
// *not* for cryptography
// Frequency of generated floats should be uniform [0,1)

AMSCU_CONST static const int32_t dpr32_mod = 1<<30-1;
AMSCU_CONST static const int32_t dpr32_mult = 25137;

//Next seed in simple 32 bit integer deterministic psuedo-rand generator
__host__ __device__ void dpr32_nextseed(int32_t *rseed_inout)
{
    int32_t lseed;
    if(rseed_inout!=NULL) lseed = *rseed_inout;

    lseed = (lseed*dpr32_mult + 1)%dpr32_mod;
    lseed = (lseed>=0)*(lseed)+(lseed<0)*(lseed+dpr32_mod); //ensure mod is positive
    
    if(rseed_inout!=NULL) *rseed_inout = lseed;

    return;
}

//Simple 32 bit integer deterministic pseudo-random generator
// *not* for cryptography
// Frequency of generated floats should be uniform [0,1)
__host__ __device__ float dpr32_randf(int32_t *rseed_inout)
{
    int32_t lseed = 1;
    float ret = 0.0f;

    if(rseed_inout!=NULL) lseed = *rseed_inout;

    dpr32_nextseed(&lseed);
    ret = ((float)(lseed))/((float)dpr32_mod);

    if(rseed_inout!=NULL) *rseed_inout = lseed;

    return ret;
}

//box muller standard normal variable
__host__ __device__ float dpr32_randnf(int32_t *rseed_inout)
{
    int32_t lseed = 1;
    float ret = 0.0f;
    float u1,u2;

    if(rseed_inout!=NULL) lseed = *rseed_inout;

    u1 = dpr32_randf(&lseed);
    u2 = dpr32_randf(&lseed);

    ret = ::sqrtf(-2.0f*::logf(u1))*::cosf(2.0f*pif*u2);

    if(rseed_inout!=NULL) *rseed_inout = lseed;
    
    return ret;
}

//////////////////////////////////////////////////
// Deterministic Pseudorandom int64_t Generator //
//////////////////////////////////////////////////

//"goodenough" deterministic pseudo-random number generator
//random enough for procedural applications, deterministic, 
//operates without side-effects for thread safety

AMSCU_CONST const int64_t random_dpr64_mod = (2LL<<31LL)-1LL;
AMSCU_CONST const int64_t random_dpr64_mult = 1201633LL;

__host__ __device__ void dpr64_nextseed(int64_t *seedinout)
{
    int64_t lseed = 0LL;
    if(seedinout!=NULL) lseed = *seedinout;

    lseed = (random_dpr64_mult*lseed+1LL)%random_dpr64_mod;
    lseed = (lseed>=0)*(lseed)+(lseed<0)*(lseed+random_dpr64_mod);

    if(seedinout!=NULL) *seedinout = lseed;

    return;
}

__host__ __device__ double dpr64_randd(int64_t *seedinout)
{
    double ret = 0.0;
    int64_t lseed = 0LL;

    if(seedinout!=NULL) lseed = *seedinout;

    dpr64_nextseed(&lseed);
    ret = ((double)lseed)/((double)(random_dpr64_mod-1LL));

    if(seedinout!=NULL) *seedinout = lseed;

    return ret;
}

__host__ __device__ float dpr64_randf(int64_t *seedinout)
{
    float ret = 0.0f;
    int64_t lseed = 0LL;

    if(seedinout!=NULL) lseed = *seedinout;

    dpr64_nextseed(&lseed);
    ret = ((float)lseed)/((float)(random_dpr64_mod-1LL));

    if(seedinout!=NULL) *seedinout = lseed;

    return ret;
}

///////////
// Tests //
///////////

void test_dprg64()
{
    // printf("Tests for dprg:\n");
    // long I;
    // int64_t seed = 133LL;
    // double d;
    // float f;
    // cuvec3 qv;

    // printf("dpr64_randd test\n");
    // seed = 133LL;
    // for(I=0;I<10;I++)
    // {
    //     d = dpr64_randd(&seed);
    //     printf("seed: %lld rand: %1.4f\n",(long long)seed,d);
    // }

    // printf("\n\n");
    // printf("dpr64_randf test\n");
    // seed = 133LL;
    // for(I=0;I<10;I++)
    // {
    //     f = dpr64_randf(&seed);
    //     printf("seed: %lld rand: %1.4f\n",(long long)seed,f);
    // }

    // return;
}

void test_dprg32()
{
    // printf("Tests for dprg:\n");
    // long I;
    // int32_t seed = 133;
    // double d;
    // float f;
    // cuvec3 qv;

    // printf("dpr32_randf test\n");
    // seed = 133;
    // for(I=0;I<10;I++)
    // {
    //     f = dpr32_randf(&seed);
    //     printf("seed: %lld rand: %1.4f\n",(long long)seed,f);
    // }

    // printf("\n\ndpr32_randnf test\n");
    // seed = 133;
    // for(I=0;I<10;I++)
    // {
    //     f = dpr32_randnf(&seed);
    //     printf("seed: %lld rand: %1.4f\n",(long long)seed,f);
    // }

    // return;
}

};
};
}; //end namespaces