#ifndef __MYREDUCTION_HPP__
#define __MYREDUCTION_HPP__

#include "MyReduction.h"

//! Compute sum reduction on CPU
//! We use Kahan summation for an accurate sum of large arrays.
//! http://en.wikipedia.org/wiki/Kahan_summation_algorithm
//! 
//! @param data       pointer to input data
//! @param size       number of input data elements
//////////////////////////////////////////////////////////////////////////////
template <class T>
T reduceCPU(T *data, int size)
{
    T sum = data[0];
    T c = (T)0.0;              
    for (int i = 1; i < size; i++)
    {
        T y = data[i] - c;  
        T t = sum + y;      
        c = (t - sum) - y;  
        sum = t;            
    }
    return sum;
}

template <class T>
T sumCPU(T *data, int size)
{
    T sum = (T)0.0;              
    for (int i = 0; i < size; i++)
      sum+=data[i];
    return sum;
}

template <class T>
void reduce(int size, int threads, int blocks, T *d_idata, T *d_odata);

template <class T>
T reduceGPU(T* d_idata, int  n,
                int  maxThreads,
                int  maxBlocks, 
                int  cpuFinalThreshold
                )
{
  T* d_odata;
  T gpu_result = 0;
    
  int numBlocks = 0;
  int numThreads = 0;
  getNumBlocksAndThreads(n, maxBlocks, maxThreads, numBlocks, numThreads);

  if (numBlocks == 1) cpuFinalThreshold = 1;
  cutilSafeCallNoSync( cudaMalloc((void**) &d_odata, numBlocks*sizeof(T)) );
  
  // execute the kernel
  reduce<T>(n, numThreads, numBlocks, d_idata, d_odata);

    // check if kernel execution generated an error
  cutilCheckMsg("Kernel execution failed");
  
  // sum partial block sums on GPU
  int s=numBlocks;
  while(s > cpuFinalThreshold) 
  {
    int threads = 0, blocks = 0;
    getNumBlocksAndThreads(s, maxBlocks, maxThreads, blocks, threads);
      
    reduce<T>(s, threads, blocks, d_odata, d_odata);
    
    s = (s + (threads*2-1)) / (threads*2);
  }
  
  if (s > 1)
  {
    // copy result from device to host
    // allocate mem for the result on host side
    //cudaThreadSynchronize();
    T* h_odata = (T*) malloc(numBlocks*sizeof(T));
    cutilSafeCallNoSync( cudaMemcpy( h_odata, d_odata, (s) * sizeof(T), 
                                     cudaMemcpyDeviceToHost) );
    for(int i=0; i < s; i++) 
      gpu_result += h_odata[i];
    free(h_odata);
  }else{
    //cudaThreadSynchronize();
    // copy final sum from device to host
    cutilSafeCallNoSync( cudaMemcpy( &gpu_result, d_odata, sizeof(T), 
                                     cudaMemcpyDeviceToHost) );
  }

  cutilSafeCallNoSync(cudaFree(d_odata));
  return gpu_result;
}
	      
#endif
