#include <amsculib3/amsculib3.hpp>

namespace amscuda
{
namespace random
{
    template<typename dtype, typename randfunc> void rand_threadfunc1(
        int threadnum,
        int nthreads,
        randfunc &&rf,
        dtype *buffer,
        int64_t N,
        randstate_t *seed
    )
    {
        int64_t I,Is,I0,I1;

        Is = N/nthreads;
        if(Is<1) Is = 1;
        I0 = Is*(threadnum);
        I1 = Is*(threadnum+1);
        if(I1>N) I1 = N;

        for(I=I0;I<I1;I++)
        {
            buffer[I] = rf(seed);
        }
        return;
    }

    template<typename callable, typename randfunc, typename dtype> 
    int hbuff_rand_threadedexec(
        callable &&fptr, 
        randfunc &&rf,
        dtype *buffer, 
        int64_t N, 
        randstate_t *mainseed
    )
    {
        int ret = amscu_success;

        int I;
        std::vector<std::thread*> threads;
        std::vector<randstate_t> seeds;
        int nthreads = amscuda::util::amscpu_cputhreading_threadplan(N);

        if(nthreads<=1)
        {
            nthreads = 1;
            I = 0;
            fptr(I,nthreads,rf,buffer,N,mainseed);
        }
        else
        {
            threads.resize(nthreads);
            seeds.resize(nthreads);
            for(I=0;I<nthreads;I++)
            {
                seeds[I] = *mainseed;
                rand_state_increment(I,&seeds[I]);
                //printf("debug: %d %u %u\n",(int)I,seeds[I].high,seeds[I].low);
            }
            rand_state_increment(I,mainseed);

            for(I=0;I<nthreads;I++) threads[I] = NULL;
            for(I=0;I<nthreads;I++)
            {
                threads[I] = new(std::nothrow) std::thread
                (
                    std::forward<callable>(fptr),
                    I,
                    nthreads,
                    rf,
                    buffer,
                    N,
                    &seeds[I]
                );
            }
            for(I=0;I<nthreads;I++)
            {
                if(threads[I]==NULL)
                { //null thread creation failure check
                    ret = amscu_failure;
                }
            }
            for(I=0;I<nthreads;I++)
            {
                if(threads[I]!=NULL)
                {
                    threads[I]->join();
                    delete threads[I];
                    threads[I] = NULL;
                }
            }
        }

        return ret;
    }

    template<typename dtype, typename rngbnd, typename randfunc> void rand_threadfunc2(
        int threadnum,
        int nthreads,
        randfunc &&rf,
        dtype *buffer,
        int64_t N,
        rngbnd min,
        rngbnd max,
        randstate_t *seed
    )
    {
        int64_t I,Is,I0,I1;

        Is = N/nthreads;
        if(Is<1) Is = 1;
        I0 = Is*(threadnum);
        I1 = Is*(threadnum+1);
        if(I1>N) I1 = N;

        for(I=I0;I<I1;I++)
        {
            buffer[I] = rf(min,max,seed);
        }
        return;
    }

    template<typename callable, typename randfunc, typename rngbnd, typename dtype> 
    int hbuff_rand_threadedexec2(
        callable &&fptr, 
        randfunc &&rf,
        dtype *buffer, 
        int64_t N, 
        rngbnd min,
        rngbnd max,
        randstate_t *mainseed
    )
    {
        int ret = amscu_success;

        int I;
        std::vector<std::thread*> threads;
        std::vector<randstate_t> seeds;
        int nthreads = amscuda::util::amscpu_cputhreading_threadplan(N);

        if(nthreads<=1)
        {
            nthreads = 1;
            I = 0;
            fptr(I,nthreads,rf,buffer,N,min,max,mainseed);
        }
        else
        {
            threads.resize(nthreads);
            seeds.resize(nthreads);
            for(I=0;I<nthreads;I++)
            {
                seeds[I] = *mainseed;
                rand_state_increment(I,&seeds[I]);
            }
            rand_state_increment(I,mainseed);

            for(I=0;I<nthreads;I++) threads[I] = NULL;
            for(I=0;I<nthreads;I++)
            {
                threads[I] = new(std::nothrow) std::thread
                (
                    std::forward<callable>(fptr),
                    I,
                    nthreads,
                    rf,
                    buffer,
                    N,
                    min,max,
                    &seeds[I]
                );
            }
            for(I=0;I<nthreads;I++)
            {
                if(threads[I]==NULL)
                { //null thread creation failure check
                    ret = amscu_failure;
                }
            }
            for(I=0;I<nthreads;I++)
            {
                if(threads[I]!=NULL)
                {
                    threads[I]->join();
                    delete threads[I];
                    threads[I] = NULL;
                }
            }
        }

        return ret;
    }

    __host__ int hbuff_randf(float *hbuffer, int size, randstate_t *state)
    {
        int ret = amscu_success;
        int res;
        if(state==NULL) state = &global_randstate;

        res = hbuff_rand_threadedexec(
            rand_threadfunc1<float, float (*)(amscuda::random::randstate_t*)>,
            randf,
            hbuffer,
            (int64_t) size,
            state
        );

        if(res!=amscu_success) ret = amscu_failure;

        return ret;
    }

    __host__ int hbuff_rand(double *hbuffer, int size, randstate_t *state)
    {
        int ret = amscu_success;
        int res;
        if(state==NULL) state = &global_randstate;

        res = hbuff_rand_threadedexec(
            rand_threadfunc1<double, double (*)(amscuda::random::randstate_t*)>,
            rand,
            hbuffer,
            (int64_t) size,
            state
        );

        if(res!=amscu_success) ret = amscu_failure;

        return ret;
    }

    __host__ int hbuff_randnf(float *hbuffer, int size, randstate_t *state)
    {
        int ret = amscu_success;
        int res;
        if(state==NULL) state = &global_randstate;

        res = hbuff_rand_threadedexec(
            rand_threadfunc1<float, float (*)(amscuda::random::randstate_t*)>,
            randnf,
            hbuffer,
            (int64_t) size,
            state
        );

        if(res!=amscu_success) ret = amscu_failure;

        return ret;
    }

    __host__ int hbuff_randn(double *hbuffer, int size, randstate_t *state)
    {
        int ret = amscu_success;
        int res;
        if(state==NULL) state = &global_randstate;

        res = hbuff_rand_threadedexec(
            rand_threadfunc1<double, double (*)(amscuda::random::randstate_t*)>,
            randn,
            hbuffer,
            (int64_t) size,
            state
        );

        if(res!=amscu_success) ret = amscu_failure;

        return ret;
    }

    __host__ int hbuff_randint(int *hbuffer, int size, int low, int high, randstate_t *state)
    {
        int ret = amscu_success;
        int res;
        if(state==NULL) state = &global_randstate;

        res = hbuff_rand_threadedexec2(
            rand_threadfunc2<int, int, int (*)(int, int, amscuda::random::randstate_t*)>,
            randint,
            hbuffer,
            (int64_t) size,
            low,high,
            state
        );

        if(res!=amscu_success) ret = amscu_failure;

        return ret;
    }
    
}; //end namespaces
};