#include <amsmathutil25/amsmathutil25.hpp>

namespace ams
{
namespace rand
{

amsmu_randt1 dpr32_rseed;
amsmu_randt2 dpr64_rseed;

amsmu_randt1 seed32_next(amsmu_randt1 seed)
{
    amsmu_randt1 sret = seed;
    sret = ams::mod(sret*dpr32_mult1+dpr32_add1,dpr32_mod);
    return sret;
}

amsmu_randt2 seed64_next(amsmu_randt2 seed)
{
    amsmu_randt2 sret = seed;
    sret = ams::mod(sret*dpr64_mult1+dpr64_add1,dpr64_mod);
    return sret;
}

double rand(amsmu_randt1 *seed)
{
    double ret = 0.0;
    *seed = seed32_next(*seed);
    ret = ((double) *seed) / ((double)(dpr32_mod-1));
    return ret;
}

float randf(amsmu_randt1 *seed)
{
    float ret = 0.0;
    *seed = seed32_next(*seed);
    ret = ((float) *seed) / ((float)(dpr32_mod-1));
    return ret;
}

double randgaussian(amsmu_randt1 *seed)
{
    double ret = 0.0;
    double u1,u2;
    u1 = rand(seed);
    u2 = rand(seed);
    if(u1>0.0)
    {
        ret = ::sqrt(-2.0*::log(u1))*::cos(2.0*ams::pi*u2);
    }
    return ret;
}

float randgaussianf(amsmu_randt1 *seed)
{
    float ret = 0.0;
    float u1,u2;
    u1 = randf(seed);
    u2 = randf(seed);
    if(u1>0.0f)
    {
        ret = ::sqrtf(-2.0*::logf(u1))*::cosf(2.0f*ams::pif*u2);
    }
    return ret;
}

int randint(int low, int highexcl, amsmu_randt1 *seed)
{
    int ret = 0;

    if(highexcl-low>0)
    {
        *seed = seed32_next(*seed);
        ret = low + (int)((*seed)%(highexcl-low));
    }

    return ret;
}

int64_t randintl(int64_t low, int64_t highexcl, amsmu_randt2 *seed)
{
    int64_t ret = 0;

    if(highexcl-low>0)
    {
        *seed = seed64_next(*seed);
        ret = low + (int)((*seed)%(highexcl-low));
    }

    return ret;
}

void seed32_set(amsmu_randt1 _seed)
{
    dpr32_rseed = _seed;
    return;
}

void seed64_set(amsmu_randt2 _seed)
{
    dpr64_rseed = _seed;
    return;
}

void seed_init_timer()
{
    amsmu_randt1 t1 = (amsmu_randt1)time(NULL);
    amsmu_randt1 t2 = (amsmu_randt1)(::fmod((double)clock()/((double)CLOCKS_PER_SEC)*1000.0f,36000.0f));
    dpr32_rseed = (amsmu_randt1)t1 + (amsmu_randt1)t2;

    amsmu_randt2 t3 = (amsmu_randt2)time(NULL);
    amsmu_randt2 t4 = (amsmu_randt2)(::fmod((double)clock()/((double)CLOCKS_PER_SEC)*1000.0f,36000.0f));
    dpr64_rseed = (amsmu_randt2)t3 + (amsmu_randt2)t4;

    return;
}

//Threaded generation of random amsarrays

template<typename T> void amsarray_rand_threadf1(
    amsarray<T> *out,
    amsarray<amsmu_randt1> *rseeds,
    T (*randfunc)(amsmu_randt1 *),
    int threadnum,
    int nthreads
)
{
    int I0,I1,Is,I;
    Is = out->length/nthreads;
    I0 = Is*threadnum;
    I1 = (threadnum>=(nthreads-1)) ? out->length : Is*(threadnum+1);
    for(I=I0;I<I1;I++)
    {
        out->data[I] = randfunc(&(rseeds->data[threadnum]));
    }
    return;
}

template<typename T> void amsarray_random_threadexec1(
    amsarray<T> *out,
    amsarray_size_t N,
    T (*randfunc)(amsmu_randt1 *),
    amsmu_randt1 *rseed
)
{
    amsarray_size_t I;
    int J;
    int nthreads;
    std::vector<std::thread*> threads;
    ams::amsarray<amsmu_randt1> rseeds;
    int res;

    res = out->resize(N);
    if(res!=amsarray_success)
    {
        out->resize(0);
        return;
    }

    if(N<amsmathutil25_threadpsz)
    {
        //single threaded
        for(I=0;I<N;I++)
        {
            out->data[I] = randfunc(rseed);
        }
    }
    else
    {
        //multi-threaded operation
        nthreads = std::thread::hardware_concurrency();
        nthreads = (nthreads<1) ? 1 : nthreads;
        nthreads = (nthreads>amsmathutil25_maxthreads) ? amsmathutil25_maxthreads : nthreads;
        threads.resize(nthreads);
        rseeds.resize(nthreads);
        
        for(J=0;J<nthreads;J++)
        {
            *rseed = seed32_next(*rseed);
            rseeds.data[J] = *rseed + 13*J;
        }
        for(J=0;J<nthreads;J++)
        {
            threads[J] = new(std::nothrow) std::thread(
                &amsarray_rand_threadf1<T>,
                out,
                &rseeds,
                randfunc,
                J,nthreads
            );
            if(threads[J]==NULL)
            {
                //handle thread allocation error
                printf("warning: amsarray_random_threadexec1:: thread %d failed to allocate.\n",J);
            }
        }
        for(J=0;J<nthreads;J++)
        {
            if(threads[J]!=NULL)
            { 
                threads[J]->join(); 
                delete threads[J]; 
                threads[J]= NULL;
            }
        }
    }

    return;
}


amsarray<double> amsarray_rand(amsarray_size_t N, amsmu_randt1 *seed)
{
    amsarray<double> ret;

    if(N<=0)
    {
        ret.resize(0);
        return ret;
    }

    amsarray_random_threadexec1(
        &ret,
        N,
        &(rand),
        seed
    );

    return ret;
}

amsarray<float> amsarray_randf(amsarray_size_t N, amsmu_randt1 *seed)
{
    amsarray<float> ret;

    if(N<=0)
    {
        ret.resize(0);
        return ret;
    }

    amsarray_random_threadexec1(
        &ret,
        N,
        &(randf),
        seed
    );

    return ret;
}

template<typename T, typename T2> void amsarray_rand_threadf2(
    amsarray<T> *out,
    amsarray<T2> *rseeds,
    T (*randfunc)(T, T, T2*),
    T low,
    T highexcl,
    int threadnum,
    int nthreads
)
{
    int I0,I1,Is,I;
    Is = out->length/nthreads;
    I0 = Is*threadnum;
    I1 = (threadnum>=(nthreads-1)) ? out->length : Is*(threadnum+1);
    for(I=I0;I<I1;I++)
    {
        out->data[I] = randfunc(low,highexcl,&(rseeds->data[threadnum]));
    }
    return;
}

template<typename T, typename T2> void amsarray_random_threadexec2(
    amsarray<T> *out,
    amsarray_size_t N,
    T (*randfunc)(T, T, T2*),
    T2 (*nextfunc)(T2),
    T low,
    T highexcl,
    T2 *rseed
)
{
    amsarray_size_t I;
    int J;
    int nthreads;
    std::vector<std::thread*> threads;
    amsarray<T2> rseeds;
    int res;

    res = out->resize(N);
    if(res!=amsarray_success)
    {
        out->resize(0);
        return;
    }

    if(N<amsmathutil25_threadpsz)
    {
        //single threaded
        for(I=0;I<N;I++)
        {
            out->data[I] = randfunc(low,highexcl,rseed);
        }
    }
    else
    {
        //multi-threaded operation
        nthreads = std::thread::hardware_concurrency();
        nthreads = (nthreads<1) ? 1 : nthreads;
        nthreads = (nthreads>amsmathutil25_maxthreads) ? amsmathutil25_maxthreads : nthreads;
        threads.resize(nthreads);
        rseeds.resize(nthreads);
        for(J=0;J<nthreads;J++)
        {
            *rseed = nextfunc(*rseed);
            rseeds.data[J] = *rseed + 13*J;
        }
        for(J=0;J<nthreads;J++)
        {
            threads[J] = new(std::nothrow) std::thread(
                &amsarray_rand_threadf2<T,T2>,
                out,
                &rseeds,
                randfunc,
                low,highexcl,
                J,nthreads
            );
            if(threads[J]==NULL)
            {
                //handle thread allocation error
                printf("warning: amsarray_random_threadexec2:: thread %d failed to allocate.\n",J);
            }
        }
        for(J=0;J<nthreads;J++)
        {
            if(threads[J]!=NULL)
            { 
                threads[J]->join(); 
                delete threads[J]; 
                threads[J]= NULL;
            }
        }
    }

    return;
}

amsarray<int> amsarray_randint(amsarray_size_t N, int low, int highexcl,
    amsmu_randt1 *rseed)
{
    amsarray<int> ret;

    if(N<=0)
    {
        ret.resize(0);
        return ret;
    }

    amsarray_random_threadexec2(
        &ret,
        N,
        &(randint),
        &(seed32_next),
        low,highexcl,
        rseed
    );

    return ret;
}

amsarray<int64_t> amsarray_randintl(amsarray_size_t N, int64_t low, int64_t highexcl, amsmu_randt2 *rseed)
{
    amsarray<int64_t> ret;

    if(N<=0)
    {
        ret.resize(0);
        return ret;
    }

    amsarray_random_threadexec2(
        &ret,
        N,
        &(randintl),
        &(seed64_next),
        low,highexcl,
        rseed
    );

    return ret;
}

}; //end namespace rand
}; //end namespace ams