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

#include <windows.h>
#define CL_USE_DEPRECATED_OPENCL_2_0_APIS 1
#include "CL/cl.hpp"

SERVICE_STATUS serviceStatus = {0};
SERVICE_STATUS_HANDLE serviceStatusHandle = 0;

const wchar_t* CrossProcessEventName = L"Global\\OpenCL_Test_serviceEvent";
const wchar_t* successMessage = L"OpenCL Service Test Success\n";
const wchar_t* serviceName = L"OpenCL Test service";
// this event is set whenever the service thread is finished executing
// all it's tasks
HANDLE RetireServiceEvent = 0;

DWORD WINAPI ThreadProc(LPVOID lpdwThreadParam);

//////////////////////////
// log relate functions //
//////////////////////////
void getLogFileName(wchar_t fileName[MAX_PATH]) {
  DWORD dwSize = GetModuleFileNameW(NULL, fileName, MAX_PATH);
  wchar_t* p = fileName + dwSize;
  while (*p != '\\' && p > fileName) p--;
  p++;
  wcscpy(p, L"result.txt");
}

VOID WriteLog(const wchar_t* pMsg) {
  static wchar_t fileName[MAX_PATH] = {0};
  if (fileName[0] == 0) getLogFileName(fileName);

  FILE* pLog = _wfopen(fileName, L"w");
  if (NULL != pLog) {
    fwprintf(pLog, pMsg);
    fclose(pLog);
  }
}

VOID AppendLog(const wchar_t* pMsg) {
  static wchar_t fileName[MAX_PATH] = {0};
  if (fileName[0] == 0) getLogFileName(fileName);
  FILE* pLog = _wfopen(fileName, L"a");
  if (NULL != pLog) {
    fwprintf(pLog, pMsg);
    fclose(pLog);
  }
}

VOID AppendLog(const char* pMsg) {
  static wchar_t fileName[MAX_PATH] = {0};
  if (fileName[0] == 0) getLogFileName(fileName);
  FILE* pLog = _wfopen(fileName, L"a");
  if (NULL != pLog) {
    fprintf(pLog, pMsg);
    fclose(pLog);
  }
}
///////////////////////////////
// service related functions //
///////////////////////////////
void WINAPI ServiceControlHandler(DWORD controlCode) {
  switch (controlCode) {
    case SERVICE_CONTROL_INTERROGATE:
      break;

    case SERVICE_CONTROL_SHUTDOWN:
    case SERVICE_CONTROL_STOP:
      serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
      if (!SetServiceStatus(serviceStatusHandle, &serviceStatus))
        AppendLog(L"SetServiceStatus SERVICE_STOP_PENDING failed\n");

      if (RetireServiceEvent) SetEvent(RetireServiceEvent);
      return;

    case SERVICE_CONTROL_PAUSE:
      break;

    case SERVICE_CONTROL_CONTINUE:
      break;

    default:
      if (controlCode >= 128 && controlCode <= 255)
        // user defined control code
        break;
      else
        // unrecognised control code
        break;
  }

  if (!SetServiceStatus(serviceStatusHandle, &serviceStatus))
    AppendLog(L"SetServiceStatus SERVICE_STOP_PENDING failed\n");
}

void WINAPI ServiceMain(DWORD /*argc*/, wchar_t* /*argv*/[]) {
  // initialise service status
  serviceStatus.dwServiceType = SERVICE_WIN32;
  serviceStatus.dwCurrentState = SERVICE_START_PENDING;
  serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN;
  serviceStatus.dwWin32ExitCode = NO_ERROR;
  serviceStatus.dwServiceSpecificExitCode = NO_ERROR;
  serviceStatus.dwCheckPoint = 0;
  serviceStatus.dwWaitHint = 0;

  serviceStatusHandle = RegisterServiceCtrlHandlerW(serviceName, ServiceControlHandler);

  if (serviceStatusHandle) {
    // service is starting
    serviceStatus.dwCurrentState = SERVICE_START_PENDING;
    if (!SetServiceStatus(serviceStatusHandle, &serviceStatus))
      AppendLog(L"SetServiceStatus SERVICE_START_PENDING failed\n");

    // do initialisation here
    RetireServiceEvent = CreateEvent(0, FALSE, FALSE, 0);

    // running
    serviceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
    serviceStatus.dwCurrentState = SERVICE_RUNNING;
    if (!SetServiceStatus(serviceStatusHandle, &serviceStatus))
      AppendLog(L"SetServiceStatus SERVICE_RUNNING failed\n");

    // Create the thread that actually does the CL testing
    CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    // wait for the thread to finish
    WaitForSingleObject(RetireServiceEvent, 60000);

    HANDLE crossProcessEvent = OpenEventW(EVENT_ALL_ACCESS, FALSE, CrossProcessEventName);
    if (NULL != crossProcessEvent) {
      SetEvent(crossProcessEvent);
    } else {
      AppendLog(L"cross process Event could not be openned\n");
    }

    // service was stopped
    serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
    if (!SetServiceStatus(serviceStatusHandle, &serviceStatus))
      AppendLog(L"SetServiceStatus SERVICE_STOP_PENDING failed\n");

    // do cleanup here
    CloseHandle(crossProcessEvent);
    CloseHandle(RetireServiceEvent);
    RetireServiceEvent = 0;

    // service is now stopped
    serviceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
    serviceStatus.dwCurrentState = SERVICE_STOPPED;
    if (!SetServiceStatus(serviceStatusHandle, &serviceStatus))
      AppendLog(L"SetServiceStatus SERVICE_STOPPED failed\n");
  }
}

// This function services ocltst as a service when launched
// by the OS. It registers the service routines.
void serviceStubCall() {
  wchar_t serviceName[MAX_PATH];
  wcscpy(serviceName, ::serviceName);
  SERVICE_TABLE_ENTRYW serviceTable[] = {{serviceName, ServiceMain}, {0, 0}};
  DWORD session_id;
  BOOL retVal = ProcessIdToSessionId(GetCurrentProcessId(), &session_id);
  if (0 == session_id) {
    StartServiceCtrlDispatcherW(serviceTable);
  }
}
/////////////////////
// CL related code //
/////////////////////
const char c_kernelCode[] =
    " __kernel void hello(__global char * theArray)"
    "{"
    " size_t i =  get_global_id(0);"
    "if ( i < get_global_size(0))"
    "theArray[i] = 78;"
    "}";

const unsigned int c_bufferSize = 1024;

DWORD WINAPI ThreadProc(LPVOID lpdwThreadParam) {
  cl_int err;
  // Platform info
  std::vector<cl::Platform> platforms;

  err = cl::Platform::get(&platforms);
  if (err != CL_SUCCESS) {
    AppendLog(L"Platform::get() failed\n");
    return -1;
  }

  std::vector<cl::Platform>::iterator i;
  if (platforms.size() > 0) {
    for (i = platforms.begin(); i != platforms.end(); ++i) {
      if (!strcmp((*i).getInfo<CL_PLATFORM_VENDOR>(&err).c_str(), "Advanced Micro Devices, Inc.")) {
        break;
      }
    }
  }
  if (err != CL_SUCCESS) {
    AppendLog(L"Platform::getInfo() failed \n");
    return -1;
  }

  cl_context_properties cps[3] = {CL_CONTEXT_PLATFORM, (cl_context_properties)(*i)(), 0};

  cl::Context context(CL_DEVICE_TYPE_GPU, cps, NULL, NULL, &err);
  if (err != CL_SUCCESS) {
    AppendLog(L"Context::Context() failed \n");
    return -1;
  }

  std::vector<cl::Device> devices = context.getInfo<CL_CONTEXT_DEVICES>();
  if (err != CL_SUCCESS) {
    AppendLog(L"Context::getInfo() failed \n");
    return -1;
  }
  if (devices.size() == 0) {
    AppendLog(L"No device available\n");
    return -1;
  }

  cl::Program::Sources sources(1, std::make_pair(c_kernelCode, sizeof(c_kernelCode)));

  cl::Program program = cl::Program(context, sources, &err);
  if (err != CL_SUCCESS) {
    AppendLog(L"Program::Program() failed\n");
  }

  err = program.build(devices);
  if (err != CL_SUCCESS) {
    if (err == CL_BUILD_PROGRAM_FAILURE) {
      std::string str((char*)program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(devices[0]).c_str());

      AppendLog(L" \n\t\t\tBUILD LOG\n\n");
      AppendLog(L" ************************************************\n");
      AppendLog(str.c_str());
      AppendLog(L" ************************************************\n");
    }

    AppendLog(L"Program::build() failed\n");
    return -1;
  }

  cl::Kernel kernel(program, "hello", &err);
  if (err != CL_SUCCESS) {
    AppendLog(L"Kernel::Kernel() failed\n");
    return -1;
  }

  cl::Buffer buffer = cl::Buffer(context, CL_MEM_READ_WRITE, c_bufferSize, 0, &err);
  if (err != CL_SUCCESS) {
    AppendLog(L"Kernel::setArg() failed \n");
  }

  cl::CommandQueue queue(context, devices[0], 0, &err);
  if (err != CL_SUCCESS) {
    AppendLog(L"CommandQueue::CommandQueue() failed \n");
    return -1;
  }

  err = kernel.setArg(0, buffer);
  if (err != CL_SUCCESS) {
    AppendLog(L"Kernel::setArg() failed \n");
    return -1;
  }

  err = queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(c_bufferSize), cl::NullRange);

  if (err != CL_SUCCESS) {
    AppendLog(L"CommandQueue::enqueueNDRangeKernel()\n");
    return -1;
  }

  err = queue.finish();
  if (err != CL_SUCCESS) {
    AppendLog(L"Event::wait() failed \n");
  }
  char* ptr = (char*)malloc(c_bufferSize);
  err = queue.enqueueReadBuffer(buffer, CL_TRUE, 0, c_bufferSize, ptr, NULL, NULL);
  if (err != CL_SUCCESS) {
    AppendLog(L"CommandQueue::enqueueReadBuffer()\n");
    return -1;
  }

  bool validateSuccess = true;
  // validate the results
  for (int i = 0; i < c_bufferSize; i++) {
    if (ptr[i] != 78) validateSuccess = false;
  }

  free(ptr);
  if (validateSuccess) {
    WriteLog(successMessage);
    AppendLog(L"validate success");
  } else {
    AppendLog(L"Validate fail");
    return -1;
  }

  SetEvent(RetireServiceEvent);
  return 0;
}
