// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0

#include "win32_progress_callback.h"
#include "resource.h"
#include "win32_window_util.h"

#include "common/log.h"
#include "common/string_util.h"

#include <CommCtrl.h>
#include <shellscalingapi.h>

LOG_CHANNEL(Host);

Win32ProgressCallback::Win32ProgressCallback(HWND parent_hwnd) : UpdaterProgressCallback(), m_parent_hwnd(parent_hwnd)
{
  Create();
}

Win32ProgressCallback::~Win32ProgressCallback()
{
  Destroy();
}

void Win32ProgressCallback::SetTitle(const std::string_view title)
{
  SetWindowText(m_window_hwnd, StringUtil::UTF8StringToWideString(title).c_str());
}

bool Win32ProgressCallback::Create()
{
  static constexpr LPCWSTR CLASS_NAME = L"DSWin32ProgressCallbackWindow";
  static bool class_registered = false;

  if (!class_registered)
  {
    InitCommonControls();

    WNDCLASSEX wc = {};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.lpfnWndProc = WndProcThunk;
    wc.hInstance = GetModuleHandle(nullptr);
    wc.hIcon = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1));
    wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1));
    wc.hCursor = LoadCursor(NULL, IDC_WAIT);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpszClassName = CLASS_NAME;
    if (!RegisterClassEx(&wc))
    {
      ERROR_LOG("Failed to register window class");
      return false;
    }

    class_registered = true;
  }

  m_dpi = GetDpiForSystem();

  RECT adjusted_rect = {0, 0, Scale(WINDOW_WIDTH), Scale(WINDOW_HEIGHT)};
  AdjustWindowRectExForDpi(&adjusted_rect, WS_OVERLAPPEDWINDOW, FALSE, WS_EX_CLIENTEDGE, m_dpi);

  const int window_width = adjusted_rect.right - adjusted_rect.left;
  const int window_height = adjusted_rect.bottom - adjusted_rect.top;

  m_window_hwnd =
    CreateWindowEx(WS_EX_CLIENTEDGE, CLASS_NAME, L"DuckStation Update Installer", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
                   CW_USEDEFAULT, window_width, window_height, m_parent_hwnd, nullptr, GetModuleHandle(nullptr), this);
  if (!m_window_hwnd)
  {
    ERROR_LOG("Failed to create window");
    return false;
  }

  // Disable parent window to make this modal
  if (m_parent_hwnd)
    EnableWindow(m_parent_hwnd, FALSE);

  SetWindowLongPtr(m_window_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
  Win32WindowUtil::CenterWindowOnMonitorAtCursorPosition(m_window_hwnd);
  ShowWindow(m_window_hwnd, SW_SHOW);
  PumpMessages();
  return true;
}

void Win32ProgressCallback::Destroy()
{
  if (!m_window_hwnd)
    return;

  if (m_font)
  {
    DeleteObject(m_font);
    m_font = nullptr;
  }

  DestroyWindow(m_window_hwnd);
  m_window_hwnd = {};
  m_text_hwnd = {};
  m_progress_hwnd = {};

  // Re-enable and restore focus to parent window
  if (m_parent_hwnd)
  {
    EnableWindow(m_parent_hwnd, TRUE);
    SetForegroundWindow(m_parent_hwnd);
  }
}

void Win32ProgressCallback::PumpMessages()
{
  MSG msg;
  while (PeekMessage(&msg, m_window_hwnd, 0, 0, PM_REMOVE))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
}

void Win32ProgressCallback::StateChanged(StateChange changed)
{
  const int percent =
    static_cast<int>((static_cast<float>(m_progress_value) / static_cast<float>(m_progress_range)) * 100.0f);
  if (percent == m_last_progress_percent && !(changed & STATE_CHANGE_STATUS_TEXT))
  {
    PumpMessages();
    return;
  }

  m_last_progress_percent = percent;

  SendMessage(m_progress_hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, m_progress_range));
  SendMessage(m_progress_hwnd, PBM_SETPOS, static_cast<WPARAM>(m_progress_value), 0);
  SetWindowText(m_text_hwnd, StringUtil::UTF8StringToWideString(m_status_text).c_str());
  RedrawWindow(m_text_hwnd, nullptr, nullptr, RDW_INVALIDATE);
  PumpMessages();
}

LRESULT CALLBACK Win32ProgressCallback::WndProcThunk(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
  Win32ProgressCallback* cb;
  if (msg == WM_CREATE)
  {
    const CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);
    cb = static_cast<Win32ProgressCallback*>(cs->lpCreateParams);
  }
  else
  {
    cb = reinterpret_cast<Win32ProgressCallback*>(GetWindowLongPtrA(hwnd, GWLP_USERDATA));
  }

  return cb->WndProc(hwnd, msg, wparam, lparam);
}

LRESULT CALLBACK Win32ProgressCallback::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
  switch (msg)
  {
    case WM_CREATE:
    {
      const CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);
      m_dpi = GetDpiForWindow(hwnd);
      EnableMenuItem(GetSystemMenu(hwnd, FALSE), SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);

      LOGFONT lf = {};
      SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0, m_dpi);
      m_font = CreateFontIndirect(&lf);

      SendMessage(hwnd, WM_SETFONT, WPARAM(m_font), TRUE);

      m_text_hwnd =
        CreateWindowEx(0, L"Static", nullptr, WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hwnd, nullptr, cs->hInstance, nullptr);
      SendMessage(m_text_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);

      m_progress_hwnd = CreateWindowEx(0, PROGRESS_CLASSW, nullptr, WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hwnd, nullptr,
                                       cs->hInstance, nullptr);

      m_list_box_hwnd =
        CreateWindowEx(0, L"LISTBOX", nullptr, WS_VISIBLE | WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_BORDER | LBS_NOSEL,
                       0, 0, 0, 0, hwnd, nullptr, cs->hInstance, nullptr);
      SendMessage(m_list_box_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);
    }
      [[fallthrough]];

    case WM_SIZE:
    {
      RECT window_rect = {};
      GetClientRect(m_window_hwnd, &window_rect);

      const int control_width = (window_rect.right - window_rect.left) - (Scale(WINDOW_MARGIN) * 2);
      int y = Scale(WINDOW_MARGIN);

      SetWindowPos(m_text_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, Scale(STATUS_TEXT_HEIGHT),
                   SWP_NOZORDER | SWP_NOACTIVATE);
      y += Scale(STATUS_TEXT_HEIGHT) + Scale(CONTROL_SPACING);

      SetWindowPos(m_progress_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, Scale(PROGRESS_BAR_HEIGHT),
                   SWP_NOZORDER | SWP_NOACTIVATE);
      y += Scale(PROGRESS_BAR_HEIGHT) + Scale(CONTROL_SPACING);

      const int listbox_height = (window_rect.bottom - window_rect.top) - y - Scale(WINDOW_MARGIN);
      SetWindowPos(m_list_box_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, listbox_height,
                   SWP_NOZORDER | SWP_NOACTIVATE);
    }
    break;

    case WM_DPICHANGED:
    {
      m_dpi = HIWORD(wparam);

      // Need to update the font.
      if (m_font)
      {
        DeleteObject(m_font);
        m_font = nullptr;
      }

      LOGFONTW lf = {};
      SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0, m_dpi);
      m_font = CreateFontIndirect(&lf);
      SendMessage(m_window_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);
      SendMessage(m_text_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);
      SendMessage(m_progress_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);
      SendMessage(m_list_box_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);

      // Will trigger WM_SIZE.
      const RECT* new_rect = reinterpret_cast<RECT*>(lparam);
      SetWindowPos(m_window_hwnd, nullptr, new_rect->left, new_rect->top, new_rect->right - new_rect->left,
                   new_rect->bottom - new_rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
    }
    break;

    default:
      return DefWindowProc(hwnd, msg, wparam, lparam);
  }

  return 0;
}

int Win32ProgressCallback::Scale(int value) const
{
  return MulDiv(value, m_dpi, 96);
}

void Win32ProgressCallback::DisplayError(const std::string_view message)
{
  ERROR_LOG(message);
  SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0,
              reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));
  SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
  PumpMessages();
}

void Win32ProgressCallback::DisplayWarning(const std::string_view message)
{
  WARNING_LOG(message);
  SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0,
              reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));
  SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
  PumpMessages();
}

void Win32ProgressCallback::DisplayInformation(const std::string_view message)
{
  INFO_LOG(message);
  SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0,
              reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));
  SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
  PumpMessages();
}

void Win32ProgressCallback::DisplayDebugMessage(const std::string_view message)
{
  DEV_LOG(message);
}

void Win32ProgressCallback::ModalError(const std::string_view message)
{
  PumpMessages();
  MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Error", MB_ICONERROR | MB_OK);
  PumpMessages();
}

bool Win32ProgressCallback::ModalConfirmation(const std::string_view message)
{
  PumpMessages();
  bool result = MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Confirmation",
                           MB_ICONQUESTION | MB_YESNO) == IDYES;
  PumpMessages();
  return result;
}

void Win32ProgressCallback::ModalInformation(const std::string_view message)
{
  MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Information",
             MB_ICONINFORMATION | MB_OK);
}
