/**
 * @file win32helpers.cc
 * @brief Windows-specific utility functions
 * @author Supakorn "Jamie" Rassameemasmuang (jamievlin [at] outlook.com)
 */

#if defined(_WIN32)
#include "win32helpers.h"
#include "errormsg.h"
#include <shellapi.h>

using camp::reportError;

namespace camp::w32
{

bool checkShellExecuteResult(INT_PTR const shellExecResult, bool const reportWarning)
{
  switch (shellExecResult)
  {
    // see https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea
    // ERROR_FILE_NOT_FOUND and ERROR_PATH_NOT_FOUND shares the same error code as
    // SE_ERR_FNF and SE_ERR_PNF, respectively
    case 0:
    case ERROR_BAD_FORMAT:
    case SE_ERR_ACCESSDENIED:
    case SE_ERR_ASSOCINCOMPLETE:
    case SE_ERR_DDEBUSY:
    case SE_ERR_DDEFAIL:
    case SE_ERR_DDETIMEOUT:
    case SE_ERR_DLLNOTFOUND:
    case SE_ERR_FNF:
    case SE_ERR_NOASSOC:
    case SE_ERR_OOM:
    case SE_ERR_PNF:
    case SE_ERR_SHARE:
    {
      if (reportWarning)
      {
        DWORD const errorCode= GetLastError();
        ostringstream msg;
        msg << "Error code: 0x" << std::hex << errorCode << std::dec << "; message: " << getErrorMessage(errorCode);
        camp::reportWarning(msg.str());
      }
    }
      return false;
    default:
      return true;
  }
}

void reportAndFailWithLastError(string const& message)
{
  DWORD errorCode= GetLastError();
  ostringstream msg;
  msg << message << "; error code = 0x" << std::hex << errorCode << std::dec
      << "; Windows Message: " << getErrorMessage(errorCode);
  reportError(msg);
}

void checkResult(BOOL result, string const& message)
{
  if (!result)
  {
    reportAndFailWithLastError(message);
  }
}

void checkLStatus(LSTATUS result, string const& message)
{
  checkResult(result == ERROR_SUCCESS, message);
}

bool isProcessRunning(DWORD const& pid)
{
  if (pid == 0)
  {
    return true; // system idle is always running
  }

  HandleRaiiWrapper const processHandle(OpenProcess(PROCESS_QUERY_INFORMATION, false, pid));
  if (processHandle.getHandle() == nullptr)
  {
    // handle not in system, returns false
    return false;
  }

  DWORD exitCode=999;
  if (GetExitCodeProcess(processHandle.getHandle(), &exitCode))
  {
    if (exitCode == STILL_ACTIVE)
    {
      return true;
    }
    else
    {
      return false;
    }
  }

  return false;
}

#pragma region RegKeyWrapper

RegKeyWrapper::RegKeyWrapper(HKEY const& regKey)
    : key(regKey)
{
}
RegKeyWrapper::RegKeyWrapper() : key(nullptr)
{
}
RegKeyWrapper::~RegKeyWrapper()
{
  closeExistingKey();
}
RegKeyWrapper::RegKeyWrapper(RegKeyWrapper&& other) noexcept
        : key(std::exchange(other.key, nullptr))
{
}

RegKeyWrapper& RegKeyWrapper::operator=(RegKeyWrapper&& other) noexcept
{
  if (this != &other)
  {
    closeExistingKey();
    this->key = std::exchange(other.key, nullptr);
  }
  return *this;
}

HKEY RegKeyWrapper::getKey() const
{
  return key;
}

void RegKeyWrapper::closeExistingKey()
{
  if (this->key != nullptr)
  {
    RegCloseKey(this->key);
    this->key = nullptr;
  }
}

PHKEY RegKeyWrapper::put()
{
  closeExistingKey();
  return &(this->key);
}
void RegKeyWrapper::release()
{
  this->key = nullptr;
}

#pragma endregion

#pragma region HandleRaiiWrapper

HandleRaiiWrapper::HandleRaiiWrapper(HANDLE const& handle)
    : hd(handle)
{
}

HandleRaiiWrapper::~HandleRaiiWrapper()
{
  if (hd)
  {
    if (!CloseHandle(hd))
    {
      cerr << "Warning: Cannot close handle" << endl;
    }
  }
}

HandleRaiiWrapper::HandleRaiiWrapper(HandleRaiiWrapper&& other) noexcept
: hd(std::exchange(other.hd, nullptr))
{
}

HANDLE HandleRaiiWrapper::getHandle() const
{
  return hd;
}

LPHANDLE HandleRaiiWrapper::put()
{
  if (hd)
  {
    w32::checkResult(CloseHandle(hd));
    hd = nullptr;
  }

  return &hd;
}

#pragma endregion

string buildWindowsCmd(const mem::vector<string>& command)
{
  ostringstream out;
  for (auto it= command.begin(); it != command.end(); ++it)
  {
    out << '"' << *it << '"';
    if (std::next(it) != command.end())
    {
      out << ' ';
    }
  }

  return out.str();
}

string getErrorMessage(DWORD const& errorCode)
{
  LPSTR messageOut= nullptr;
  auto ret = FormatMessageA(
          FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
          nullptr,
          errorCode,
          MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
          reinterpret_cast<LPSTR>(&messageOut),
          0,
          nullptr
  );

  if (ret == 0)
  {
    return "Cannot determine error message";
  }

  string retString(messageOut);
  LocalFree(messageOut);
  
  return retString;
}

}// namespace camp::w32

#endif
