#include <amsculib2/amsculib2.hpp>

namespace amscuda
{

//template instantiations
template float dbuff_sum(float *devbuffer, int N);
template void dbuff_minmax(float *devbuffer, int N, float *min, float *max);


template void dbuff_setall(float *devbuffer, int N, float setto, int nblocks, int nthreads);




//fill devbuffer with random uniform numbers between 0 and 1 using int32_t based generator
__global__ void dbuff_rand_dpr32_kf(float *devbuffer, int N, int32_t *seeds)
{
    int I0 = threadIdx.x + blockIdx.x*blockDim.x;
    int Is = blockDim.x*gridDim.x;
    int I;

    int32_t lseed;
    float f;

    lseed = seeds[I0];
    for(I=I0;I<N;I=I+Is)
    {
        f = dpr32_randf(&lseed);
        devbuffer[I] = f;
    }

    return;
}

void dbuff_rand_dpr32(float *devbuffer, int N, int32_t *rseedinout,  int nblocks, int nthreads)
{
    cudaError_t err = cudaSuccess;
    int I;
    int32_t *seeds = NULL;
    int32_t *devseeds = NULL;
    int32_t lseed;

    if(devbuffer==NULL || N<=0)
    {
        return;
    }

    seeds = new(std::nothrow) int32_t[nblocks*nthreads];
    cudaMalloc(&devseeds,sizeof(int32_t)*nblocks*nthreads);

    if(rseedinout!=NULL) lseed = *rseedinout; else lseed = 1;
    for(I=0;I<nblocks*nthreads;I++)
    {
        lseed = lseed + I + 1;
        dpr32_nextseed(&lseed);
        seeds[I] = lseed;
    }

    cudaMemcpy(devseeds,seeds,sizeof(int32_t)*nblocks*nthreads,cudaMemcpyHostToDevice);
    if(rseedinout!=NULL) *rseedinout = lseed;

    dbuff_rand_dpr32_kf<<<nblocks,nthreads>>>(devbuffer,N,devseeds);
    cudaDeviceSynchronize();
    err = cudaGetLastError();
    if(err!=cudaSuccess)
    {
        printf("amscu::dbuff_rand_dpr32 error: %s\n",cudaGetErrorString(err));
    }

    cudaFree(devseeds); devseeds = NULL;
    delete[] seeds; seeds = NULL;

    return;
}

__global__ void dbuff_rand_dpr32n_kf(float *devbuffer, int N, int32_t *seeds)
{
    int I0 = threadIdx.x + blockIdx.x*blockDim.x;
    int Is = blockDim.x*gridDim.x;
    int I;

    int32_t lseed;
    float f;

    lseed = seeds[I0];
    for(I=I0;I<N;I=I+Is)
    {
        f = dpr32_randnf(&lseed);
        devbuffer[I] = f;
    }

    return;
}

void dbuff_rand_dpr32n(float *devbuffer, int N, int32_t *rseedinout,  int nblocks, int nthreads)
{
    cudaError_t err = cudaSuccess;
    int I;
    int32_t *seeds = NULL;
    int32_t *devseeds = NULL;
    int32_t lseed;

    if(devbuffer==NULL || N<=0)
    {
        return;
    }

    seeds = new(std::nothrow) int32_t[nblocks*nthreads];
    cudaMalloc(&devseeds,sizeof(int32_t)*nblocks*nthreads);

    if(rseedinout!=NULL) lseed = *rseedinout; else lseed = 1;
    for(I=0;I<nblocks*nthreads;I++)
    {
        lseed = lseed + I + 1;
        dpr32_nextseed(&lseed);
        seeds[I] = lseed;
    }

    cudaMemcpy(devseeds,seeds,sizeof(int32_t)*nblocks*nthreads,cudaMemcpyHostToDevice);
    if(rseedinout!=NULL) *rseedinout = lseed;

    dbuff_rand_dpr32n_kf<<<nblocks,nthreads>>>(devbuffer,N,devseeds);
    cudaDeviceSynchronize();
    err = cudaGetLastError();
    if(err!=cudaSuccess)
    {
        printf("amscu::dbuff_rand_dpr32 error: %s\n",cudaGetErrorString(err));
    }

    cudaFree(devseeds); devseeds = NULL;
    delete[] seeds; seeds = NULL;

    return;
}



void dbuff_rand_dpr64(float *devbuffer, int N, int64_t *rseedinout,  int nblocks, int nthreads)
{
    int I;

    return;
}



///////////
// Tests //
///////////

void test_dbuff_rand_dpr32()
{
    cuarray<float> data;
    float *dev_data = NULL;
    int Nx = 5000;
    int Ny = 5000;
    cuarray<int> dims;
    int32_t rseed = 15;
    FILE *fp = NULL;
    const char *fname = "./test_scripts/test_dbuff_rand_dpr32.bin";

    clock_t t0,t1,t2;
    double dt0,dt1;

    printf("Tests of dbuff_rand_dpr32...\n");

    fp = fopen(fname,"w+");
    if(fp==NULL)
    {
        printf("Error: Could not open %s for writing.\n",fname);
        return;
    }

    data.resize(Nx*Ny);
    dims.resize(2);
    dims[0] = Nx; dims[1] = Ny;
    cudaMalloc(&dev_data,Nx*Ny*sizeof(float));

    t0 = clock();
    dbuff_rand_dpr32(dev_data,Nx*Ny,&rseed,256,512);
    t1 = clock();
    cudaMemcpy(data.data,dev_data,Nx*Ny*sizeof(float),cudaMemcpyDeviceToHost);
    t2 = clock();

    dt0 = (double)(t1-t0)/(double)CLOCKS_PER_SEC*1000.0;
    dt1 = (double)(t2-t1)/(double)CLOCKS_PER_SEC*1000.0;
    printf("dbuff_rand_dpr32 exec time: %1.3f msec\n",dt0);
    printf("copy (%d,%d) to host time: %1.3f msec\n",Nx,Ny,dt1);
    
    fwrite_ndarray(fp,&dims,&data);

    t0 = clock();
    dbuff_rand_dpr32n(dev_data,Nx*Ny,&rseed,256,512);
    t1 = clock();
    cudaMemcpy(data.data,dev_data,Nx*Ny*sizeof(float),cudaMemcpyDeviceToHost);
    t2 = clock();

    dt0 = (double)(t1-t0)/(double)CLOCKS_PER_SEC*1000.0;
    dt1 = (double)(t2-t1)/(double)CLOCKS_PER_SEC*1000.0;
    printf("dbuff_rand_dpr32n exec time: %1.3f msec\n",dt0);
    printf("copy (%d,%d) to host time: %1.3f msec\n",Nx,Ny,dt1);

    fwrite_ndarray(fp,&dims,&data);

    fclose(fp);

    cudaFree(dev_data); dev_data = NULL;

}

};

