#include <amsculib3/amsculib3.hpp>

using namespace amscuda::random::xoroshiro;

namespace amscuda
{
namespace random
{

    //Choosing xoroshiro64** as my default RNG due to 32 bit only operations 
    randstate_t global_randstate = xs64ss_state(0);
    //must intialize randstate to a value other than {0,0}. The single-param constructor calls splitmix32 to initialize



    __host__ void rand_seed(const uint32_t seed)
    {
        global_randstate = xs64ss_state(seed);
    }

    __host__ __device__ void rand_state_increment(const int32_t inc, randstate_t *state)
    {
        #ifdef __CUDA_ARCH__
            // GPU-specific code (device path)
        #else
            // CPU-specific code (host path)
            if(state==NULL) state = &global_randstate;
        #endif
        xoroshiro::xs64ss_state* s2 = (xoroshiro::xs64ss_state*)state;
        s2->low += inc;
        xs64ss_next(s2);
        return;
    }

    __host__ __device__ void rand_next(randstate_t *state)
    {
        #ifdef __CUDA_ARCH__
            // GPU-specific code (device path)
        #else
            // CPU-specific code (host path)
            if(state==NULL) state = &global_randstate;
        #endif
        xs64ss_next((xoroshiro::xs64ss_state*)state);
        return;
    }

    __host__ __device__ uint32_t randui32(randstate_t *state)
    {
        uint32_t ret;
        #ifdef __CUDA_ARCH__
            // GPU-specific code (device path)
        #else
            // CPU-specific code (host path)
            if(state==NULL) state = &global_randstate;
        #endif

        ret = xoroshiro::xs64ss_next((xoroshiro::xs64ss_state*)state);
        return ret;
    }

    __host__ __device__ int randint(int low, int high, randstate_t *state)
    {
        int ret;
        #ifdef __CUDA_ARCH__
            // GPU-specific code (device path)
        #else
            // CPU-specific code (host path)
            if(state==NULL) state = &global_randstate;
        #endif
        int32_t q = (int)((randui32(state)>>1U)%(1U<<16U));
        ret = (q%(high-low))+low;
        return ret;
    }

    __host__ __device__ float randf(randstate_t *state)
    {
        float ret;
        #ifdef __CUDA_ARCH__
            // GPU-specific code (device path)
        #else
            // CPU-specific code (host path)
            if(state==NULL) state = &global_randstate;
        #endif
        ret= ((float)randui32(state))/(4294967296.0f);
        return ret;
    }

    __host__ __device__ double rand(randstate_t *state)
    {
        double ret;
        #ifdef __CUDA_ARCH__
            // GPU-specific code (device path)
        #else
            // CPU-specific code (host path)
            if(state==NULL) state = &global_randstate;
        #endif
        ret= ((double)randui32(state))/(4294967296.0f);
        return ret;
    }

    __host__ __device__ float randnf(randstate_t *state)
    {
        float q1,q2;
        #ifdef __CUDA_ARCH__
            // GPU-specific code (device path)
        #else
            // CPU-specific code (host path)
            if(state==NULL) state = &global_randstate;
        #endif

        q1 = randf(state);
        q2 = randf(state);
        return (::sqrtf(-2.0f*::logf(q1))*cos(2.0f*pif*q2));
    }

    __host__ __device__ double randn(randstate_t *state)
    {
        double q1,q2;
        #ifdef __CUDA_ARCH__
            // GPU-specific code (device path)
        #else
            // CPU-specific code (host path)
            if(state==NULL) state = &global_randstate;
        #endif

        q1 = rand(state);
        q2 = rand(state);
        return (::sqrt(-2.0*::log(q1))*cos(2.0*pi*q2));
    }

}; //end namespaces
};