#include "backends/audio_thread_scheduling.h"

#include <cstddef>
#include <utility>

#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <avrt.h>
#endif

namespace aptt {
namespace {

#if defined(_WIN32)

std::wstring Utf8ToWide(const std::string& value) {
  if (value.empty()) {
    return {};
  }
  const int required =
      MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0);
  if (required <= 1) {
    return {};
  }
  std::wstring converted(static_cast<std::size_t>(required - 1), L'\0');
  MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, converted.data(), required);
  return converted;
}

AVRT_PRIORITY ToAvrtPriority(std::int32_t priority) {
  switch (priority) {
    case -1:
      return AVRT_PRIORITY_LOW;
    case 1:
      return AVRT_PRIORITY_HIGH;
    case 2:
      return AVRT_PRIORITY_CRITICAL;
    case 0:
    default:
      return AVRT_PRIORITY_NORMAL;
  }
}

#endif

std::int32_t NormalizePriority(std::int32_t priority) {
  if (priority < -1) {
    return -1;
  }
  if (priority > 2) {
    return 2;
  }
  return priority;
}

std::string NormalizeTaskName(std::string task_name) {
  if (task_name == "Audio") {
    return task_name;
  }
  return "Pro Audio";
}

}  // namespace

std::string AudioThreadMmcssPriorityLabel(std::int32_t priority) {
  switch (NormalizePriority(priority)) {
    case -1:
      return "Low";
    case 1:
      return "High";
    case 2:
      return "Critical";
    case 0:
    default:
      return "Normal";
  }
}

void AddAudioThreadMmcssBackendFields(
    std::map<std::string, std::string>& fields,
    const AudioThreadMmcssRegistrationSnapshot& snapshot) {
  fields["mmcss_enabled"] = snapshot.enabled ? "true" : "false";
  fields["mmcss_attempted"] = snapshot.attempted ? "true" : "false";
  fields["mmcss_active"] = snapshot.active ? "true" : "false";
  fields["mmcss_priority_applied"] =
      snapshot.priority_applied ? "true" : "false";
  fields["mmcss_revert_attempted"] =
      snapshot.revert_attempted ? "true" : "false";
  fields["mmcss_revert_succeeded"] =
      snapshot.revert_succeeded ? "true" : "false";
  fields["mmcss_task_name"] = snapshot.task_name;
  fields["mmcss_priority"] = std::to_string(snapshot.priority);
  fields["mmcss_priority_label"] =
      AudioThreadMmcssPriorityLabel(snapshot.priority);
  fields["mmcss_task_index"] = std::to_string(snapshot.task_index);
  fields["thread_id"] = std::to_string(snapshot.thread_id);
  fields["mmcss_error_code"] = std::to_string(snapshot.error_code);
  fields["mmcss_error_stage"] = snapshot.error_stage;
}

ScopedAudioThreadMmcssRegistration::ScopedAudioThreadMmcssRegistration(
    AudioThreadMmcssConfig config) {
  snapshot_.enabled = config.enabled;
  snapshot_.task_name = NormalizeTaskName(std::move(config.task_name));
  snapshot_.priority = NormalizePriority(config.priority);

#if defined(_WIN32)
  snapshot_.thread_id = ::GetCurrentThreadId();
  if (!snapshot_.enabled) {
    return;
  }

  snapshot_.attempted = true;
  DWORD task_index = 0;
  const std::wstring task_name = Utf8ToWide(snapshot_.task_name);
  HANDLE handle =
      AvSetMmThreadCharacteristicsW(task_name.c_str(), &task_index);
  snapshot_.task_index = static_cast<std::uint32_t>(task_index);
  if (handle == nullptr) {
    snapshot_.error_code = ::GetLastError();
    snapshot_.error_stage = "AvSetMmThreadCharacteristics";
    return;
  }

  handle_ = handle;
  snapshot_.active = true;
  if (!AvSetMmThreadPriority(handle, ToAvrtPriority(snapshot_.priority))) {
    snapshot_.error_code = ::GetLastError();
    snapshot_.error_stage = "AvSetMmThreadPriority";
    return;
  }
  snapshot_.priority_applied = true;
#else
  (void)config;
#endif
}

ScopedAudioThreadMmcssRegistration::~ScopedAudioThreadMmcssRegistration() {
  Revert();
}

AudioThreadMmcssRegistrationSnapshot ScopedAudioThreadMmcssRegistration::
    snapshot() const {
  return snapshot_;
}

AudioThreadMmcssRegistrationSnapshot ScopedAudioThreadMmcssRegistration::
    Revert() {
#if defined(_WIN32)
  if (handle_ != nullptr) {
    snapshot_.revert_attempted = true;
    if (AvRevertMmThreadCharacteristics(static_cast<HANDLE>(handle_))) {
      snapshot_.revert_succeeded = true;
      snapshot_.active = false;
    } else {
      const auto revert_error = ::GetLastError();
      if (snapshot_.error_code == 0) {
        snapshot_.error_code = revert_error;
        snapshot_.error_stage = "AvRevertMmThreadCharacteristics";
      }
    }
    handle_ = nullptr;
  }
#endif
  return snapshot_;
}

}  // namespace aptt
