/*! \file 
 * \brief template definition of functions reduceCPU, sumCPU and reduceGPU
 * 
 * 
 */
#ifndef __MYREDUCTION_HPP__
#define __MYREDUCTION_HPP__

#include "MyReduction.h"
#include "MyReduction_kernel.hpp"

/*! \brief 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 array
 * \param size       size if data array
 * 
 * \tparam T      type `int`, `float` or `double`
 */
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;
}

/*! \brief Compute sum on CPU
 * 
 * Calcul classique de la somme des éléments d'un tableau.
 * 
 * \param data       pointer to input data array
 * \param size       size if data array
 * 
 * \tparam T      type `int`, `float` or `double`
 */
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;
}

/*! \brief Compute sum reduction on GPU
 * 
 * Calcul de la somme des éléments d'un tableau sur GPU
 * en utilisant le kernel reduce6 du SDK NVIDIA.
 * 
 * \param data       pointer to input data on GPU
 * \param size       number of input data elements
 * \param maxThreads maximum number of threads
 * \param maxBlocks  maximum number of blocks
 * \param cpuFinalThreshold ...
 * 
 * \tparam T      type `int`, `float` or `double`
 */
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;
  checkCudaErrors( cudaMalloc((void**) &d_odata, numBlocks*sizeof(T)) );
  
  cudaDeviceSynchronize();
  // execute the kernel* @tparam T      type `int`, `float` or `double`
  reduce<T>(n, numThreads, numBlocks, d_idata, d_odata);
  
  // sum partial block sums on* @tparam T      type `int`, `float` or `double` 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
    T* h_odata = (T*) malloc(numBlocks*sizeof(T));
    checkCudaErrors( 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{
    // copy final sum from device to host
    checkCudaErrors( cudaMemcpy( &gpu_result, d_odata, sizeof(T), 
                                     cudaMemcpyDeviceToHost) );
  }

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