#include <amsculib3/amsculib3.hpp>

namespace amscuda
{
namespace random
{
namespace xoroshiro
{

    //////////////////
    //Xoroshiro 64**//
    //////////////////

    __device__ __host__ xs64ss_state::xs64ss_state()
    {
        low = 0; high = 0;
    }

    __device__ __host__ xs64ss_state::xs64ss_state(const uint32_t seed)
    {
        uint32_t sm32 = seed;
        splitmix32_next(&sm32);
        low = splitmix32_next(&sm32);
        high = splitmix32_next(&sm32);
        return;
    }

    __device__ __host__ xs64ss_state::xs64ss_state(const uint32_t _low, const uint32_t _high)
    {
        low = _low; high = _high;
    }

    xs64ss_state xs64ss_globalstate = xs64ss_state();

    __host__ void xs64ss_seed(const uint32_t seed)
    {
        xs64ss_globalstate = xs64ss_state(seed);
    }

    __device__ __host__ static inline uint32_t xs64ss_rotl(const uint32_t x, int k) {
        return (x << k) | (x >> (32 - k));
    }

    //implements the xoroshiro64** PRNG
    __host__ __device__ uint32_t xs64ss_next(xs64ss_state *state)
    {
        const uint32_t low = state->low;
        uint32_t high = state->high;
        const uint32_t ret = xs64ss_rotl(low*0x9E3779BBU,5)*5;
        high ^= low;
        state->low = xs64ss_rotl(low, 26) ^ high ^ (high << 9);
        state->high = xs64ss_rotl(high, 13);
        return ret;
    }


    //////////////////
    //Xoroshiro 128+//
    //////////////////
    __device__ __host__ xs128pp_state::xs128pp_state()
    {
        low = 0;
        high = 0;
        return;
    }

    __device__ __host__ xs128pp_state::xs128pp_state(const uint64_t seed)
    {
        uint64_t sm64s = seed;
        splitmix64_next(&sm64s);
        low = splitmix64_nextint(&sm64s);
        high = splitmix64_nextint(&sm64s);
        return;
    }

    __device__ __host__ xs128pp_state::xs128pp_state(const uint64_t _low, const uint64_t _high)
    {
        low = _low;
        high = _high;
        return;
    }

    xs128pp_state xs128pp_globalstate = xs128pp_state();

    __host__ __device__ uint64_t xs128pp_rotl(const uint64_t x, int k)
    {
        return (x << k) | (x >> (64 - k));
    }

    __host__ __device__ uint64_t xs128pp_next(xs128pp_state* state)
    {
        const uint64_t low = state->low;
        uint64_t high = state->high;
        const uint64_t result = low + high;
        high ^= low;
        state->low = xs128pp_rotl(low,24) ^ high ^ (high << 16);
        state->high = xs128pp_rotl(high,37);
        return result;
    }

    __host__ __device__  void xs128pp_jump(xs128pp_state* state)
    {
        AMSCU_CONST static const uint64_t JUMP[] = { 0xdf900294d8f554a5, 0x170865df4b3201fc };
        uint64_t low = 0;
        uint64_t high = 0;
        int I;
        int B;

        for(I=0;I<2;I++)
        {
            for(B=0;B<64;B++)
            {
                if(JUMP[I] & (1ULL<<B))
                {
                    low ^= state->low;
                    high ^= state->high;
                }
                xs128pp_next(state);
            }
        }

        state->low = low;
        state->high = high;
        return;
    }

    __host__ void xs128pp_seed(uint64_t seed)
    {
        xs128pp_globalstate = xs128pp_state(seed);
    }


//To have a different implementation for host and device functions
// __host__ __device__ void my_function() {
//     #ifdef __CUDA_ARCH__
//         // GPU-specific code (device path)
//     #else
//         // CPU-specific code (host path)
//     #endif
// }



}; //end namespaces
};
};