#include <stdio.h>
#include <windows.h>
#define WIN32_LEAN_AND_MEAN

#include "../external/cusolver_common.h"
#include "../external/cusolverDn.h"

#ifdef __cplusplus
# define no_name_mangle extern "C"
#else
# define no_name_mangle 
#endif

#define function static

#define GLOBAL_LOG_BUFFER_SIZE 4096*4096

__device__ int last_index;
__device__ char *device_log_buffer;
char *log_buffer;

#define D_LOG(msg) d_debug_printf((msg))
//#define D_LOGF(msg, ...) d_debug_printfv((msg), __VA_ARGS__)
#define H_LOG(msg) h_debug_printf((msg))
#define H_LOGF(msg, ...) h_debug_printfv((msg), __VA_ARGS__)

__host__ function void h_debug_printf(const char *msg); 
__host__ function void h_debug_printfv(const char *fmt, ...); 
__device__ function void d_debug_printf(const char *msg);
//__device__ function void d_debug_printfv(const char *fmt, ...);

__global__ void init_device_logging()
{
  last_index = 0;
}


__host__ function void
h_debug_printf(const char* msg) 
{
  OutputDebugStringA(msg);
}

__host__ function void
h_debug_printfv(const char *fmt, ...)
{
  char buffer[2048] = {0};
  va_list args;
  va_start(args, fmt);
  vsnprintf(&buffer[0], sizeof(buffer), fmt, args);
  va_end(args);
  OutputDebugStringA(buffer);
}

__device__ function void
d_debug_printf(const char *msg) {
  int i = 0;
  while(msg[i] != '\0')
  {
    device_log_buffer[last_index + i] = msg[i];
    i += 1;
  }
  last_index = last_index + 1;

}



__global__ void hello_from_GPU()
{

  D_LOG("HELLO FROM GPUUUU\n");
}

void
check_cusolver_version() 
{

  int major=-1, minor=-1, patch=-1;
  cusolverGetProperty(MAJOR_VERSION, &major);
  cusolverGetProperty(MINOR_VERSION, &minor);
  cusolverGetProperty(PATCH_LEVEL, &patch);
  H_LOGF("CUSOLVER Version (Major,Minor,PatchLevel): %d.%d.%d\n", major,minor,patch);

}



no_name_mangle void 
cuda_entry_point()
{
  H_LOG("\n\n-- Entered CUDA code:\n\n");
  char *temp_device_log_buffer;
  cudaMalloc((void**)&temp_device_log_buffer, GLOBAL_LOG_BUFFER_SIZE * sizeof(char));
  cudaMemcpyToSymbol(device_log_buffer, &temp_device_log_buffer, sizeof(char *));
  check_cusolver_version();

  init_device_logging<<<1, 1>>>();
  cudaDeviceSynchronize();
  hello_from_GPU<<<1, 1>>>();
  cudaDeviceSynchronize();



  log_buffer = (char *)malloc(GLOBAL_LOG_BUFFER_SIZE * sizeof(char));
  cudaMemcpy(log_buffer, temp_device_log_buffer, GLOBAL_LOG_BUFFER_SIZE * sizeof(char), cudaMemcpyDeviceToHost);

  // Print all messages from cuda.
  H_LOG(log_buffer);

  H_LOG("\n\n Testing cuSolver Eigenvalue problem \n\n");
  
  const int size = 5;
  const int mat_size = size*size;
  double A[mat_size] = {
    6.39,   0.13,  -8.23,   5.71,  -3.18,
   0.13,   8.37,  -4.46,  -6.10,   7.21,
   -8.23,  -4.46,  -9.58,  -9.25,  -7.42,
   5.71,  -6.10,  -9.25,   3.72,   8.54,
   -3.18,   7.21,  -7.42,   8.54,   2.51};

  double *device_mat;
  cudaMalloc((void**)&device_mat, mat_size * sizeof(double));
  cudaMemcpy(device_mat, A, mat_size * sizeof(double), cudaMemcpyHostToDevice);

  cusolverDnHandle_t cusolverH = 0;
  cusolverDnCreate(&cusolverH);

  double W[size];
  double *d_W;
  cudaMalloc((void**)&d_W, size * sizeof(double));

  int lwork;
  cusolverDnDsyevd_bufferSize(
      cusolverH, CUSOLVER_EIG_MODE_VECTOR, CUBLAS_FILL_MODE_LOWER,
      size, device_mat, size, d_W, &lwork
      );

  double *d_work;
  cudaMalloc((void**)&d_work, lwork * sizeof(double));

  int *device_info;
  cudaMalloc((void**)&device_info, sizeof(int));

  cusolverDnDsyevd(
      cusolverH, CUSOLVER_EIG_MODE_VECTOR, CUBLAS_FILL_MODE_LOWER,
      size, device_mat, size, d_W, d_work, lwork, device_info
      );
  
  double out_mat[mat_size];
  cudaMemcpy(W, d_W, size * sizeof(double), cudaMemcpyDeviceToHost);
  cudaMemcpy(out_mat, device_mat, mat_size * sizeof(double), cudaMemcpyDeviceToHost);

  H_LOG("Results on GPU dsyevd:\n");
  H_LOG("Eigenvalues:\n");
  for(int i = 0; i < size; i++)
  {
    H_LOGF("%6.2f\n", W[i]);
  }
  H_LOG("\n");
  H_LOG("Eigenvectors (columnwise):\n");
  for(int i = 0; i < size; i++)
  {
    for(int j = 0; j < size; j++)
    {
      H_LOGF("%6.2f ", out_mat[j * size + i]); 
    }
    H_LOG("\n");
  }



  cudaFree(temp_device_log_buffer);
}
