#include <amscppnarray/amscppnarray.hpp>

namespace ams
{
namespace narray
{
namespace rand
{

    int32_t dpr32_nextseed(int32_t seed)
    {
        int32_t sret = seed;
        sret = ams::mod(sret*dpr32_mult1+1,dpr32_mod);
        return sret;
    }
    
    double dpr32_randd(int32_t *seed)
    {
        double ret;
        *seed = ams::narray::rand::dpr32_nextseed(*seed);
        ret = (double)*seed/(double)(dpr32_mod-1);
        return ret;
    }
    
    float dpr32_randf(int32_t *seed)
    {
        float ret;
        *seed = ams::narray::rand::dpr32_nextseed(*seed);
        ret = (float)*seed/(float)(dpr32_mod-1);
        return ret;
    }
    
    double dpr32_gaussian(int32_t *seed)
    {
        double ret = 0.0;
        double u1,u2;
        u1 = ams::narray::rand::dpr32_randd(seed);
        u2 = ams::narray::rand::dpr32_randd(seed);
        
        if(u1>0.0)
        {
            ret = ::sqrt(-2.0*::log(u1))*::cos(2.0*pi*u2);
        }
    
        return ret;
    }
    
    
    float dpr32_gaussianf(int32_t *seed)
    {
        float ret = 0.0f;
        float u1,u2;
        u1 = ams::narray::rand::dpr32_randf(seed);
        u2 = ams::narray::rand::dpr32_randf(seed);
        
        if(u1>0.0)
        {
            ret = ::sqrt(-2.0f*::log(u1))*::cos(2.0f*pif*u2);
        }
    
        return ret;
    }
    
    vect2 dpr32_randuvect2(int32_t *seed)
    {
        vect2 ret = vect2(0,0);
        double th = ams::narray::rand::dpr32_randd(seed)*2.0*pi;
        ret = vect2(cos(th),sin(th));
        return ret;
    }
    
    vect3 dpr32_randuvect3(int32_t *seed)
    {
        vect3 ret;
        double az,el;
        el = ::acos(2.0*dpr32_randd(seed)-1.0)-pi/2.0;
        az = ams::narray::rand::dpr32_randd(seed)*2.0*pi;
    
        ret = vect3(cos(az)*cos(el),sin(az)*cos(el),sin(el));
    
        return ret;
    }
    
    vect4 dpr32_randuvect4(int32_t *seed)
    {
        vect4 ret;
        double x,y,z,w;
    
        x = ams::narray::rand::dpr32_gaussian(seed);
        y = ams::narray::rand::dpr32_gaussian(seed);
        z = ams::narray::rand::dpr32_gaussian(seed);
        w = ams::narray::rand::dpr32_gaussian(seed);
        ret = vect4(x,y,z,w);
        ret = vnormalize(ret);
    
        return ret;
    }
    
    vect2f dpr32_randuvect2f(int32_t *seed)
    {
        vect2f ret = vect2f(0,0);
        float th = ams::narray::rand::dpr32_randf(seed)*2.0*pi;
        ret = vect2f(cos(th),sin(th));
        return ret;
    }
    
    vect3f dpr32_randuvect3f(int32_t *seed)
    {
        vect3f ret;
        float az,el;
        el = ::acos(2.0f*dpr32_randf(seed)-1.0)-pif/2.0;
        az = ams::narray::rand::dpr32_randf(seed)*2.0f*pif;
    
        ret = vect3f(cos(az)*cos(el),sin(az)*cos(el),sin(el));
    
        return ret;
    }
    
    vect4f dpr32_randuvect4f(int32_t *seed)
    {
        vect4f ret;
        float x,y,z,w;
    
        x = ams::narray::rand::dpr32_gaussianf(seed);
        y = ams::narray::rand::dpr32_gaussianf(seed);
        z = ams::narray::rand::dpr32_gaussianf(seed);
        w = ams::narray::rand::dpr32_gaussianf(seed);
        ret = vect4f(x,y,z,w);
        ret = vnormalize(ret);
    
        return ret;
    }

    int32_t get_rseed()
    {
        return ams::narray::rand::dpr32_rseed;
    }

    void set_rseed(int32_t seed)
    {
        ams::narray::rand::dpr32_rseed = seed;
    }

    void set_rseed_withtimer()
    {
        int32_t t1 = (int32_t)time(NULL);
        int32_t t2 = (int32_t)(::fmod((double)clock()/((double)CLOCKS_PER_SEC)*1000.0f,36000.0f));
        ams::narray::rand::dpr32_rseed = (int32_t)t1 + (int32_t)t2;
    }

    template<typename T> void narray_rand_threadf1(
        narray<T> *out,
        narray<int32_t> *rseeds,
        T (*randfunc)(int32_t *),
        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 narray_random_threadexec1(
        narray<T> *out,
        narray_size_t N,
        T (*randfunc)(int32_t *),
        int32_t *rseed
    )
    {
        narray_size_t I;
        int J;
        int nthreads;
        std::vector<std::thread*> threads;
        narray<int32_t> rseeds;
        int res;

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

        if(N<narray_thread_sz)
        {
            //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>narray_max_threads) ? narray_max_threads : nthreads;
            threads.resize(nthreads);
            rseeds.resize(nthreads);
            for(J=0;J<nthreads;J++)
            {
                *rseed = dpr32_nextseed(*rseed);
                rseeds.data[J] = *rseed + 13*J;
            }
            for(J=0;J<nthreads;J++)
            {
                threads[J] = new(std::nothrow) std::thread(
                    &narray_rand_threadf1<T>,
                    out,
                    &rseeds,
                    randfunc,
                    J,nthreads
                );
                if(threads[J]==NULL)
                {
                    //handle thread allocation error
                    printf("warning: narray_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;
    }

    narray<double> narray_rand(narray_size_t N,
        int32_t *rseed)
    {
        narray<double> ret;

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

        narray_random_threadexec1(
            &ret,
            N,
            &(ams::narray::rand::dpr32_randd),
            rseed
        );

        return ret;
    }

    narray<float> narray_randf(narray_size_t N,
        int32_t *rseed)
    {
        narray<float> ret;

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

        narray_random_threadexec1(
            &ret,
            N,
            &(ams::narray::rand::dpr32_randf),
            rseed
        );

        return ret;
    }

    narray<double> narray_randgauss(narray_size_t N,
        int32_t *rseed)
    {
        narray<double> ret;

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

        narray_random_threadexec1(
            &ret,
            N,
            &(ams::narray::rand::dpr32_gaussian),
            rseed
        );

        return ret;
    }

    narray<float> narray_randgaussf(narray_size_t N,
        int32_t *rseed)
    {
        narray<float> ret;

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

        narray_random_threadexec1(
            &ret,
            N,
            &(ams::narray::rand::dpr32_gaussianf),
            rseed
        );

        return ret;
    }

    narray<vect2> narray_randuvect2(narray_size_t N,
        int32_t *rseed)
    {
        narray<vect2> ret;

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

        narray_random_threadexec1(
            &ret,
            N,
            &(ams::narray::rand::dpr32_randuvect2),
            rseed
        );

        return ret;
    }

    narray<vect3> narray_randuvect3(narray_size_t N,
        int32_t *rseed)
    {
        narray<vect3> ret;

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

        narray_random_threadexec1(
            &ret,
            N,
            &(ams::narray::rand::dpr32_randuvect3),
            rseed
        );

        return ret;
    }

    narray<vect4> narray_randuvect4(narray_size_t N,
        int32_t *rseed)
    {
        narray<vect4> ret;

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

        narray_random_threadexec1(
            &ret,
            N,
            &(ams::narray::rand::dpr32_randuvect4),
            rseed
        );

        return ret;
    }

    narray<vect2f> narray_randuvect2f(narray_size_t N,
        int32_t *rseed)
    {
        narray<vect2f> ret;

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

        narray_random_threadexec1(
            &ret,
            N,
            &(ams::narray::rand::dpr32_randuvect2f),
            rseed
        );

        return ret;
    }

    narray<vect3f> narray_randuvect3f(narray_size_t N,
        int32_t *rseed)
    {
        narray<vect3f> ret;

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

        narray_random_threadexec1(
            &ret,
            N,
            &(ams::narray::rand::dpr32_randuvect3f),
            rseed
        );

        return ret;
    }

    narray<vect4f> narray_randuvect4f(narray_size_t N,
        int32_t *rseed)
    {
        narray<vect4f> ret;

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

        narray_random_threadexec1(
            &ret,
            N,
            &(ams::narray::rand::dpr32_randuvect4f),
            rseed
        );

        return ret;
    }


    int dpr32_randint(int32_t *seed, int low, int high)
    {
        int ret = 0;
        int val;

        if(high-low>0)
        {
            *seed = dpr32_nextseed(*seed);
            val = low + (int)(*seed)%(high-low);
        }

        return ret;
    }

    template<typename T> void narray_rand_threadf2(
        narray<T> *out,
        narray<int32_t> *rseeds,
        T (*randfunc)(int32_t *, T, T),
        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(&(rseeds->data[threadnum]),low,highexcl);
        }
        return;
    }

    template<typename T> void narray_random_threadexec2(
        narray<T> *out,
        narray_size_t N,
        T (*randfunc)(int32_t *, T, T),
        T low,
        T highexcl,
        int32_t *rseed
    )
    {
        narray_size_t I;
        int J;
        int nthreads;
        std::vector<std::thread*> threads;
        narray<int32_t> rseeds;
        int res;

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

        if(N<narray_thread_sz)
        {
            //single threaded
            for(I=0;I<N;I++)
            {
                out->data[I] = randfunc(rseed,low,highexcl);
            }
        }
        else
        {
            //multi-threaded operation
            nthreads = std::thread::hardware_concurrency();
            nthreads = (nthreads<1) ? 1 : nthreads;
            nthreads = (nthreads>narray_max_threads) ? narray_max_threads : nthreads;
            threads.resize(nthreads);
            rseeds.resize(nthreads);
            for(J=0;J<nthreads;J++)
            {
                *rseed = dpr32_nextseed(*rseed);
                rseeds.data[J] = *rseed + 13*J;
            }
            for(J=0;J<nthreads;J++)
            {
                threads[J] = new(std::nothrow) std::thread(
                    &narray_rand_threadf2<T>,
                    out,
                    &rseeds,
                    randfunc,
                    low,highexcl,
                    J,nthreads
                );
                if(threads[J]==NULL)
                {
                    //handle thread allocation error
                    printf("warning: narray_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;
    }

    narray<int> narray_randint(narray_size_t N, int low, int highexcl,
        int32_t *rseed)
    {
        narray<int> ret;

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

        narray_random_threadexec2(
            &ret,
            N,
            &(ams::narray::rand::dpr32_randint),
            low,highexcl,
            rseed
        );

        return ret;
    }


}; //end namespace rand
}; //end namespaces
};