/*! \file 
 * \brief main file.
 * 
 * 
 */

// Utilities and system includes
#include <cuda_runtime.h>

#include <helper_functions.h>
#include <helper_cuda.h>
#include <algorithm>

#include "exo03func.hpp"

void InitData(int argc, char** argv,
              char *&typeChoice,ReduceType &datatype,
              int &size, int &maxThreads,
	      int &maxBlocks, int &cpuFinalThreshold
	     );

void InitDevice(int argc, char** argv, ReduceType datatype);

/*! \brief main function.
 * 
 * 
*/
int main( int argc, char** argv) {
  int size = 1<<28;    // number of elements to reduce
  int maxThreads = 256;  // number of threads per block
  int maxBlocks = 64;
  int cpuFinalThreshold = 1;
  char *typeChoice;
  ReduceType datatype = REDUCE_INT;

  printf("%s Starting...\n\n", argv[0]);
    
  InitData(argc, argv, typeChoice, datatype, size, maxThreads,
	   maxBlocks, cpuFinalThreshold);  

  InitDevice( argc, argv, datatype);

  printf("typeChoice=%s, size=%d, maxThreads=%d\n",typeChoice, size, maxThreads);
  printf("maxBlocks=%d, cpuFinalThreshold=%d\n",maxBlocks, cpuFinalThreshold);
  printf("Reducing array of type %s, dimension %d\n\n", typeChoice,size);

  switch (datatype){
    default:
    case REDUCE_INT:
        runExo03<int>(REDUCE_INT,size,maxThreads,maxBlocks,cpuFinalThreshold);
        break;
    case REDUCE_FLOAT:
        runExo03<float>(REDUCE_FLOAT,size,maxThreads,maxBlocks,cpuFinalThreshold);
        break;
    case REDUCE_DOUBLE:
        runExo03<double>(REDUCE_DOUBLE,size,maxThreads,maxBlocks,cpuFinalThreshold);
        break;
  }
    
  //cudaThreadExit();
  //shrEXIT(argc, (const char**)argv);
  cudaDeviceReset();
}

/*! \brief Initialisation à partir de la ligne de commande.
 * 
 * 
*/
void InitData(int argc, char** argv,
              char *&typeChoice,ReduceType &datatype,
              int &size, int &maxThreads,
	      int &maxBlocks, int &cpuFinalThreshold
	     ){
  int sizetmp,maxThreadstmp,maxBlockstmp,cpuFinalThresholdtmp;
  //cutGetCmdLineArgumentstr( argc, (const char**) argv, "type", &typeChoice);
  getCmdLineArgumentString( argc, (const char**) argv, "type", &typeChoice);
  if (0 == typeChoice){
    typeChoice = (char*)malloc(4 * sizeof(char));
    strcpy(typeChoice, "int");
  }

  if (!strcasecmp(typeChoice, "float"))
    datatype = REDUCE_FLOAT;
  else if (!strcasecmp(typeChoice, "double"))
    datatype = REDUCE_DOUBLE;
  else
    datatype = REDUCE_INT;  //default
  sizetmp=getCmdLineArgumentInt( argc, (const char**)argv, "n");
  if (sizetmp>0) size=sizetmp;
  maxThreadstmp=getCmdLineArgumentInt( argc, (const char**)argv, "threads");
  if (maxThreadstmp>0 ) maxThreads=maxThreadstmp;
  maxBlockstmp=getCmdLineArgumentInt( argc, (const char**)argv, "maxblocks");
  if (maxBlockstmp>0) maxBlocks=maxBlockstmp;
  cpuFinalThresholdtmp=getCmdLineArgumentInt( argc, (const char**)argv, "cputhresh");
  if (cpuFinalThresholdtmp>0) cpuFinalThreshold=cpuFinalThresholdtmp;
}

/*! \brief Initialisation du device GPU.
 * 
 * 
*/
void InitDevice(int argc, char** argv, ReduceType datatype){
  cudaDeviceProp deviceProp;
  deviceProp.major = 1;
  deviceProp.minor = 0;
  int minimumComputeVersion = 10;

  if (datatype == REDUCE_DOUBLE){
    deviceProp.minor = 3;
    minimumComputeVersion = 13;
  }

  int dev;

  dev = findCudaDevice(argc, (const char **)argv);

  checkCudaErrors(cudaGetDeviceProperties(&deviceProp, dev));

  if ((deviceProp.major * 10 + deviceProp.minor) >= minimumComputeVersion)
  {
    printf("Using Device %d: %s\n\n", dev, deviceProp.name);
    checkCudaErrors(cudaSetDevice(dev));
  }
  else
  {
    printf("Error: the selected device does not support the minimum compute capability of %d.%d.\n\n",
           minimumComputeVersion / 10, minimumComputeVersion % 10);

    cudaDeviceReset();
    exit(EXIT_FAILURE);
  }
}