/*
 * Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
 *
 * SPDX-License-Identifier: MIT
 */

#include "OCLPerfUncoalescedRead.h"

#include <string.h>

#include <iomanip>
#include <sstream>

#include "Timer.h"

const char* OCLPerfUncoalescedRead::kernel_str =
    "#define NUM_READS 32\n\
    __kernel void read_uncoalescing(__global float *input,__global float *output)\n\
    {\n\
        float val = (float)(0.0f);\n\
        size_t gid = get_global_id(0);\n\
        val = val + input[gid * NUM_READS + 0];\n\
        val = val + input[gid * NUM_READS + 1];\n\
        val = val + input[gid * NUM_READS + 2];\n\
        val = val + input[gid * NUM_READS + 3];\n\
        val = val + input[gid * NUM_READS + 4];\n\
        val = val + input[gid * NUM_READS + 5];\n\
        val = val + input[gid * NUM_READS + 6];\n\
        val = val + input[gid * NUM_READS + 7];\n\
        val = val + input[gid * NUM_READS + 8];\n\
        val = val + input[gid * NUM_READS + 9];\n\
        val = val + input[gid * NUM_READS + 10];\n\
        val = val + input[gid * NUM_READS + 11];\n\
        val = val + input[gid * NUM_READS + 12];\n\
        val = val + input[gid * NUM_READS + 13];\n\
        val = val + input[gid * NUM_READS + 14];\n\
        val = val + input[gid * NUM_READS + 15];\n\
        val = val + input[gid * NUM_READS + 16];\n\
        val = val + input[gid * NUM_READS + 17];\n\
        val = val + input[gid * NUM_READS + 18];\n\
        val = val + input[gid * NUM_READS + 19];\n\
        val = val + input[gid * NUM_READS + 20];\n\
        val = val + input[gid * NUM_READS + 21];\n\
        val = val + input[gid * NUM_READS + 22];\n\
        val = val + input[gid * NUM_READS + 23];\n\
        val = val + input[gid * NUM_READS + 24];\n\
        val = val + input[gid * NUM_READS + 25];\n\
        val = val + input[gid * NUM_READS + 26];\n\
        val = val + input[gid * NUM_READS + 27];\n\
        val = val + input[gid * NUM_READS + 28];\n\
        val = val + input[gid * NUM_READS + 29];\n\
        val = val + input[gid * NUM_READS + 30];\n\
        val = val + input[gid * NUM_READS + 31];\n\
        output[gid] = val;\n\
    }\n";

OCLPerfUncoalescedRead::OCLPerfUncoalescedRead() { _numSubTests = 3; }

OCLPerfUncoalescedRead::~OCLPerfUncoalescedRead() {}

void OCLPerfUncoalescedRead::open(unsigned int test, char* units, double& conversion,
                                  unsigned int deviceId) {
  OCLTestImp::open(test, units, conversion, deviceId);
  CHECK_RESULT((error_ != CL_SUCCESS), "error_ opening test");
  silentFailure = false;
  _openTest = test;
  program_ = 0;
  kernel_ = 0;
  input_buff = NULL;

  if (test > 0) {
    size_t param_size = 0;
    char* strVersion = 0;
    error_ = _wrapper->clGetDeviceInfo(devices_[_deviceId], CL_DEVICE_OPENCL_C_VERSION, 0, 0,
                                       &param_size);
    CHECK_RESULT(error_ != CL_SUCCESS, "clGetPlatformInfo failed");
    strVersion = (char*)malloc(param_size);
    error_ = _wrapper->clGetDeviceInfo(devices_[_deviceId], CL_DEVICE_OPENCL_C_VERSION, param_size,
                                       strVersion, 0);
    CHECK_RESULT(error_ != CL_SUCCESS, "clGetPlatformInfo failed");
    if (strVersion[9] < '2') {
      printf("\nOpenCL C 2.0 not supported\n");
      silentFailure = true;
    }
    free(strVersion);
    if (silentFailure) return;
  }

  cl_mem buffer = _wrapper->clCreateBuffer(context_, CL_MEM_READ_ONLY,
                                           SIZE * NUM_READS * sizeof(cl_float), 0, &error_);
  buffers_.push_back(buffer);
  buffer =
      _wrapper->clCreateBuffer(context_, CL_MEM_WRITE_ONLY, SIZE * sizeof(cl_float), 0, &error_);
  buffers_.push_back(buffer);

  srand(0x8956);
  input_buff = (float*)malloc(SIZE * NUM_READS * sizeof(float));
  for (unsigned int i = 0; i < SIZE * NUM_READS; ++i) {
    input_buff[i] = (float)rand();
  }

  error_ =
      _wrapper->clEnqueueWriteBuffer(cmdQueues_[_deviceId], buffers_[0], CL_TRUE, 0,
                                     SIZE * NUM_READS * sizeof(cl_float), input_buff, 0, 0, NULL);
  CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueReadBuffer failed");

  float* buff = (float*)_wrapper->clEnqueueMapBuffer(cmdQueues_[_deviceId], buffers_[1], CL_TRUE,
                                                     CL_MAP_WRITE, 0, SIZE * sizeof(cl_float), 0, 0,
                                                     0, &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueMapBuffer failed");
  memset(buff, 0, SIZE * sizeof(cl_float));
  error_ = _wrapper->clEnqueueUnmapMemObject(cmdQueues_[_deviceId], buffers_[1], buff, 0, 0, 0);
  CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueMapBuffer failed");

  program_ = _wrapper->clCreateProgramWithSource(context_, 1, &kernel_str, NULL, &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "clCreateProgramWithSource failed");
  std::string compileOptions = "";
  if (test > 0) {
    compileOptions = "-cl-std=CL2.0";
  }

  error_ = _wrapper->clBuildProgram(program_, 1, &devices_[_deviceId], compileOptions.c_str(), NULL,
                                    NULL);

  if (error_ != CL_SUCCESS) {
    char log[400];
    _wrapper->clGetProgramBuildInfo(program_, devices_[_deviceId], CL_PROGRAM_BUILD_LOG, 400, log,
                                    0);
    printf("\n\n%s\n\n", log);
  }

  CHECK_RESULT((error_ != CL_SUCCESS), "clBuildProgram failed");
  kernel_ = _wrapper->clCreateKernel(program_, "read_uncoalescing", &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "clCreateKernel failed");
  error_ = _wrapper->clSetKernelArg(kernel_, 0, sizeof(cl_mem), (void*)&buffers_[0]);
  CHECK_RESULT((error_ != CL_SUCCESS), "clSetKernelArg failed");
  error_ = _wrapper->clSetKernelArg(kernel_, 1, sizeof(cl_mem), (void*)&buffers_[1]);
  CHECK_RESULT((error_ != CL_SUCCESS), "clSetKernelArg failed");
}

void OCLPerfUncoalescedRead::validate(void) {
  bool success = true;
  float* buff =
      (float*)_wrapper->clEnqueueMapBuffer(cmdQueues_[_deviceId], buffers_[1], CL_TRUE, CL_MAP_READ,
                                           0, SIZE * sizeof(cl_float), 0, 0, 0, &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueMapBuffer failed");
  for (unsigned int i = 0; i < SIZE; ++i) {
    volatile float val = 0;
    for (int j = 0; j < NUM_READS; ++j) {
      val += input_buff[i * NUM_READS + j];
    }
    if (val != buff[i]) {
      success = false;
      std::string errorMsg = "Invalid result.  Expected: ";
      errorMsg += std::to_string(val);
      errorMsg += " Actual result: ";
      errorMsg += std::to_string(buff[i]);
      CHECK_RESULT(true, errorMsg.c_str());
      break;
    }
  }
  error_ = _wrapper->clEnqueueUnmapMemObject(cmdQueues_[_deviceId], buffers_[1], buff, 0, 0, 0);
  CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueMapBuffer failed");
}

void OCLPerfUncoalescedRead::run(void) {
  if (silentFailure) {
    return;
  }
  CPerfCounter timer;

  // Warm up
  size_t workGroupSize = SIZE;
  for (int i = 0; i < 50; ++i) {
    error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, 1, NULL,
                                              &workGroupSize, NULL, 0, NULL, NULL);
    CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueNDRangeKernel");
    _wrapper->clFinish(cmdQueues_[_deviceId]);
  }

  cl_event eventArr[NUM_ITER];
  timer.Reset();
  timer.Start();
  for (unsigned int i = 0; i < NUM_ITER; i++) {
    error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, 1, NULL,
                                              &workGroupSize, NULL, 0, NULL, &eventArr[i]);

    CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueNDRangeKernel");
  }
  error_ = _wrapper->clFinish(cmdQueues_[_deviceId]);
  CHECK_RESULT(error_, "clFinish failed");
  timer.Stop();
  double sec1 = timer.GetElapsedTime();
  double sec2 = 0;
  for (unsigned int i = 0; i < NUM_ITER; ++i) {
    cl_ulong startTime = 0, endTime = 0;
    error_ = _wrapper->clGetEventProfilingInfo(eventArr[i], CL_PROFILING_COMMAND_START,
                                               sizeof(cl_ulong), &startTime, 0);
    CHECK_RESULT(error_, "clGetEventProfilingInfo failed");
    error_ = _wrapper->clGetEventProfilingInfo(eventArr[i], CL_PROFILING_COMMAND_END,
                                               sizeof(cl_ulong), &endTime, 0);
    CHECK_RESULT(error_, "clGetEventProfilingInfo failed");
    sec2 += 1e-9 * (endTime - startTime);
    error_ = _wrapper->clReleaseEvent(eventArr[i]);
    CHECK_RESULT(error_, "clReleaseEvent failed");
  }

  validate();

  // Buffer copy bandwidth in GB/s
  double perf1 = ((double)SIZE * NUM_READS * NUM_ITER * sizeof(cl_float) * (double)(1e-09)) / sec1;
  double perf2 = ((double)SIZE * NUM_READS * NUM_ITER * sizeof(cl_float) * (double)(1e-09)) / sec2;
  _perfInfo = (float)perf2;

  std::ostringstream strStream;
  switch (_openTest) {
    case 0:
      strStream << "OCL1.2      ";
      break;
    case 1:
      strStream << "OCL2.0      ";
      break;
    case 2:
      strStream << "OCL2.0/flag ";
      break;
  }

  strStream << std::fixed << std::setprecision(2) << perf1 << " timer GB/s ";
  strStream << "time: " << std::setprecision(3) << sec1 << "s (profile GB/s)";
  testDescString = strStream.str();
  ;
}

unsigned int OCLPerfUncoalescedRead::close(void) {
  if (input_buff) {
    free(input_buff);
  }
  return OCLTestImp::close();
}
