#ifndef __EXO03FUNC_HPP__
#define __EXO03FUNC_HPP__

// Utilities and system includes
#include <shrUtils.h>
#include "cutil_inline.h"
#include <algorithm>

// includes, project
#include "exo03func.h"

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

template <class T>
void
runExo03( ReduceType datatype, int size, 
         int maxThreads, int maxBlocks, int cpuFinalThreshold){
  unsigned int hTimer;
  float Times[7];
  T Const=10;
  
  shrLog("******** CPU ********\n");
  // create input data on CPU
  cutilCheckError( cutCreateTimer(&hTimer) );
  cutilCheckError( cutStartTimer(hTimer) );
  
  T *h_A = (T *) malloc(size * sizeof(T));
  if (h_A == NULL){
    fprintf (stderr, "Malloc error on CPU\n");
  }
  
  cutilCheckError( cutStopTimer(hTimer) );
  Times[0]=cutGetTimerValue(hTimer);
  shrLog("malloc CPU(ms)       : %f\n",Times[0]);
  
  // Initialisation du tableau sur le CPU
  cutilCheckError( cutResetTimer(hTimer) );
  cutilCheckError( cutStartTimer(hTimer) );
  
  CPU_InitTestArray<T>(h_A, size, Const);

  cutilCheckError( cutStopTimer(hTimer) );
  Times[1]=cutGetTimerValue(hTimer);
  shrLog("Init data on CPU(ms) : %f\n",Times[1]);
  
  // Calcul de la somme sur le CPU
  cutilCheckError( cutResetTimer(hTimer) );
  cutilCheckError( cutStartTimer(hTimer) );
  
  T cpu_result = reduceCPU<T>(h_A,size);
  //T cpu_result = sumCPU<T>(h_A,size);
    
  cutilCheckError( cutStopTimer(hTimer) );
  Times[2]=cutGetTimerValue(hTimer);
  
  shrLog("Sum on CPU(ms)       : %f\n-------\n",Times[2]);
  shrLog("Total on CPU(ms)     : %f\n",Times[0]+Times[1]+Times[2]);
  if (datatype == REDUCE_INT){
    shrLog("\nCPU result = %d\n", cpu_result);
  }else{
    shrLog("\nCPU result = %f\n", cpu_result);
  }
  
  
  shrLog("******** GPU ********\n");
  // allocate device memory and data
  T* d_A = NULL;
        
  cutilCheckError( cutResetTimer(hTimer) );
  cutilCheckError( cutStartTimer(hTimer) );
	
  cutilSafeCallNoSync(   cudaMalloc((void**) &d_A, size * sizeof(T)) );
  
  cutilCheckError( cutStopTimer(hTimer) );
  Times[3]=cutGetTimerValue(hTimer);
  
  shrLog("malloc GPU Time(ms)  : %f\n",Times[3]);
  
  cutilCheckError( cutResetTimer(hTimer) );
  cutilCheckError( cutStartTimer(hTimer) );
  
  // copy data directly to device memory
  cutilSafeCallNoSync( cudaMemcpy(d_A, h_A, size * sizeof(T), cudaMemcpyHostToDevice) );
  
  cutilCheckError( cutStopTimer(hTimer) );
  Times[4]=cutGetTimerValue(hTimer);
  shrLog("Choices (1) or (2) \n");
  shrLog(" (1)- Copy data CPU->GPU Time(ms) : %f\n",Times[4]);
  
  cutilCheckError( cutResetTimer(hTimer) );
  cutilCheckError( cutStartTimer(hTimer) );
  
  // or initialize data directly to device memory
  GPU_InitTestArray(d_A, size, Const, maxThreads, maxBlocks);
    
  cutilCheckError( cutStopTimer(hTimer) );
  Times[5]=cutGetTimerValue(hTimer);
  
  shrLog(" (2)- Init data GPU Time(ms)      : %f\n",Times[5]);
  
  cutilCheckError( cutResetTimer(hTimer) );
  cutilCheckError( cutStartTimer(hTimer) );

  T gpu_result = reduceGPU(d_A,size, maxThreads, maxBlocks, cpuFinalThreshold);

  cutilCheckError( cutStopTimer(hTimer) );
  Times[6]=cutGetTimerValue(hTimer);
  
  shrLog("Sum on GPU(ms)       : %f\n-------\n",Times[6]);
  shrLog("Total(1) on GPU(ms)  : %f (real %f)\n",Times[3]+Times[4]+Times[6],
               Times[0]+Times[1]+Times[3]+Times[4]+Times[6]);
  shrLog("Total(2) on GPU(ms)  : %f\n",Times[3]+Times[5]+Times[6]);
  if (datatype == REDUCE_INT){
    shrLog("\nGPU result = %d\n", gpu_result);
  }else{
    shrLog("\nGPU result = %f\n", gpu_result);
  }
  
  // cleanup
  cutilCheckError( cutDeleteTimer( hTimer) );
  
  free(h_A);
  cutilSafeCallNoSync(cudaFree(d_A));
}
#endif

