/*! \file 
 * \brief template definition of functions runExo03
 * 
 * 
 */
#ifndef __EXO03FUNC_HPP__
#define __EXO03FUNC_HPP__
//
#include <cuda_runtime.h>

// Utilities and system includes
#include <helper_functions.h>
#include <helper_cuda.h>
#include <algorithm>

// includes, project

#include "MyReduction.hpp"
#include "InitArray.hpp"
#include "InitArrayGPU.hpp"

/*! \brief main code 
 * 
 * \param datatype    ReduceType value
 * \param size        size of arrays
 * \param maxThreads  maximum threads number
 * \param maxBlocks   maximum blocks number
 * \param cpuFinalThreshold  ...
 * 
 * \tparam T type `int`, `float` or `double`
*/
template <class T> void runExo03( 
         ReduceType datatype, int size, 
         int maxThreads, int maxBlocks, int cpuFinalThreshold
	 )
{
  unsigned int hTimer;
  double Times[7];
  T Const=10;
  StopWatchInterface *timer = 0;
  sdkCreateTimer(&timer);
  // create input data on CPU
  sdkStartTimer(&timer);
  T *h_A = (T *) malloc(size * sizeof(T));
  Times[0]= sdkGetTimerValue(&timer);
  
  if (h_A == NULL){
    fprintf (stderr, "Malloc error on CPU\n");
  }
  
  //cutilCheckError( cutStopTimer(hTimer) );
  printf("malloc CPU(ms)       : %g\n",Times[0]);
  
  // Initialisation du tableau sur le CPU
  sdkResetTimer(&timer);
  sdkStartTimer(&timer);
  CPU_InitTestArray<T>(h_A, size, Const);
  Times[1]= sdkGetTimerValue(&timer);
  printf("Init data on CPU(ms) : %g\n",Times[1]);
  
  // Calcul de la somme sur le CPU
  sdkResetTimer(&timer);
  sdkStartTimer(&timer);
  T cpu_result = reduceCPU<T>(h_A,size);
  Times[2]= sdkGetTimerValue(&timer);  
  
  printf("Sum on CPU(ms)       : %g\n-------\n",Times[2]);
  printf("Total on CPU(ms)     : %g\n",Times[0]+Times[1]+Times[2]);

  printf("******** GPU ********\n");
  // allocate device memory and data
  T* d_A = NULL;

  sdkResetTimer(&timer);	
  sdkStartTimer(&timer);
  checkCudaErrors(   cudaMalloc((void**) &d_A, size * sizeof(T)) );
  Times[3]= sdkGetTimerValue(&timer); 
  
  printf("malloc GPU Time(ms)  : %g\n",Times[3]);
  
  sdkResetTimer(&timer);	
  sdkStartTimer(&timer);
  // copy data directly to device memory
  checkCudaErrors( cudaMemcpy(d_A, h_A, size * sizeof(T), cudaMemcpyHostToDevice) );
  Times[4]= sdkGetTimerValue(&timer); 

  printf("Choices (1) or (2) \n");
  printf(" (1)- Copy data CPU->GPU Time(ms) : %g\n",Times[4]);
  
  sdkResetTimer(&timer);	
  // or initialize data directly to device memory
  sdkStartTimer(&timer);
  GPU_InitTestArray(d_A, size, Const, maxThreads, maxBlocks);
    
  Times[5]= sdkGetTimerValue(&timer); 
  printf(" (2)- Init data GPU Time(ms)      : %g\n",Times[5]);
  
  sdkResetTimer(&timer);
  sdkStartTimer(&timer);
  T gpu_result = reduceGPU<T>(d_A,size, maxThreads, maxBlocks, cpuFinalThreshold);
  Times[6]= sdkGetTimerValue(&timer); 
  
  printf("Sum on GPU(ms)       : %g\n-------\n",Times[6]);
  printf("Total(1) on GPU(ms)  : %g (real %g)\n",Times[3]+Times[4]+Times[6],
               Times[0]+Times[1]+Times[3]+Times[4]+Times[6]);
  printf("Total(2) on GPU(ms)  : %g\n",Times[3]+Times[5]+Times[6]);

  sdkDeleteTimer(&timer);
  
  free(h_A);
  checkCudaErrors(cudaFree(d_A));
}

#endif

