// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/shell/platform/windows/win32_window.h"

namespace flutter {

Win32Window::Win32Window() {
  // Assume Windows 10 1703 or greater for DPI handling.  When running on a
  // older release of Windows where this context doesn't exist, DPI calls will
  // fail and Flutter rendering will be impacted until this is fixed.
  // To handle downlevel correctly, dpi_helper must use the most recent DPI
  // context available should be used: Windows 1703: Per-Monitor V2, 8.1:
  // Per-Monitor V1, Windows 7: System See
  // https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
  // for more information.

  // TODO the calling applicaiton should participate in setting the DPI.
  // Currently dpi_helper is asserting per-monitor V2.  There are two problems
  // with this: 1) it is advised that the awareness mode is set using manifest,
  // not programatically.  2) The calling executable should be responsible for
  // setting an appropriate scaling mode, not a library.  This will be
  // particularly important once there is a means of hosting Flutter content in
  // an existing app.

  BOOL result = dpi_helper_->SetProcessDpiAwarenessContext(
      DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

  if (result != TRUE) {
    OutputDebugString(L"Failed to set PMV2");
  }
}
Win32Window::~Win32Window() {
  Destroy();
}

void Win32Window::Initialize(const char* title,
                             const unsigned int x,
                             const unsigned int y,
                             const unsigned int width,
                             const unsigned int height) {
  Destroy();
  std::wstring converted_title = NarrowToWide(title);

  WNDCLASS window_class = ResgisterWindowClass(converted_title);

  CreateWindow(window_class.lpszClassName, converted_title.c_str(),
               WS_OVERLAPPEDWINDOW | WS_VISIBLE, x, y, width, height, nullptr,
               nullptr, window_class.hInstance, this);
}

std::wstring Win32Window::NarrowToWide(const char* source) {
  size_t length = strlen(source);
  size_t outlen = 0;
  std::wstring wideTitle(length, L'#');
  mbstowcs_s(&outlen, &wideTitle[0], length + 1, source, length);
  return wideTitle;
}

WNDCLASS Win32Window::ResgisterWindowClass(std::wstring& title) {
  window_class_name_ = title;

  WNDCLASS window_class{};
  window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
  window_class.lpszClassName = title.c_str();
  window_class.style = CS_HREDRAW | CS_VREDRAW;
  window_class.cbClsExtra = 0;
  window_class.cbWndExtra = 0;
  window_class.hInstance = GetModuleHandle(nullptr);
  window_class.hIcon = nullptr;
  window_class.hbrBackground = 0;
  window_class.lpszMenuName = nullptr;
  window_class.lpfnWndProc = WndProc;
  RegisterClass(&window_class);
  return window_class;
}

LRESULT CALLBACK Win32Window::WndProc(HWND const window,
                                      UINT const message,
                                      WPARAM const wparam,
                                      LPARAM const lparam) noexcept {
  if (message == WM_NCCREATE) {
    auto cs = reinterpret_cast<CREATESTRUCT*>(lparam);
    SetWindowLongPtr(window, GWLP_USERDATA,
                     reinterpret_cast<LONG_PTR>(cs->lpCreateParams));

    auto that = static_cast<Win32Window*>(cs->lpCreateParams);

    // Since the application is running in Per-monitor V2 mode, turn on
    // automatic titlebar scaling
    BOOL result = that->dpi_helper_->EnableNonClientDpiScaling(window);
    if (result != TRUE) {
      OutputDebugString(L"Failed to enable non-client area autoscaling");
    }
    that->current_dpi_ = that->dpi_helper_->GetDpiForWindow(window);
    that->window_handle_ = window;
  } else if (Win32Window* that = GetThisFromHandle(window)) {
    return that->MessageHandler(window, message, wparam, lparam);
  }

  return DefWindowProc(window, message, wparam, lparam);
}

LRESULT
Win32Window::MessageHandler(HWND hwnd,
                            UINT const message,
                            WPARAM const wparam,
                            LPARAM const lparam) noexcept {
  int xPos = 0, yPos = 0;
  UINT width = 0, height = 0;
  auto window =
      reinterpret_cast<Win32Window*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));

  if (window != nullptr) {
    switch (message) {
      case WM_DPICHANGED:
        return HandleDpiChange(window_handle_, wparam, lparam);
        break;

      case WM_DESTROY:
        window->OnClose();
        return 0;
        break;

      case WM_SIZE:
        width = LOWORD(lparam);
        height = HIWORD(lparam);

        current_width_ = width;
        current_height_ = height;
        window->HandleResize(width, height);
        break;

      case WM_MOUSEMOVE:
        xPos = GET_X_LPARAM(lparam);
        yPos = GET_Y_LPARAM(lparam);

        window->OnPointerMove(static_cast<double>(xPos),
                              static_cast<double>(yPos));
        break;
      case WM_LBUTTONDOWN:
        xPos = GET_X_LPARAM(lparam);
        yPos = GET_Y_LPARAM(lparam);
        window->OnPointerDown(static_cast<double>(xPos),
                              static_cast<double>(yPos));
        break;
      case WM_LBUTTONUP:
        xPos = GET_X_LPARAM(lparam);
        yPos = GET_Y_LPARAM(lparam);
        window->OnPointerUp(static_cast<double>(xPos),
                            static_cast<double>(yPos));
        break;
      case WM_MOUSEWHEEL:
        window->OnScroll(
            0.0, -(static_cast<short>(HIWORD(wparam)) / (double)WHEEL_DELTA));
        break;
      case WM_CHAR:
      case WM_SYSCHAR:
      case WM_UNICHAR:
        if (wparam != VK_BACK) {
          window->OnChar(static_cast<unsigned int>(wparam));
        }
        break;
      case WM_KEYDOWN:
      case WM_SYSKEYDOWN:
      case WM_KEYUP:
      case WM_SYSKEYUP:
        unsigned char scancode = ((unsigned char*)&lparam)[2];
        unsigned int virtualKey = MapVirtualKey(scancode, MAPVK_VSC_TO_VK);
        const int key = virtualKey;
        const int action = message == WM_KEYDOWN ? WM_KEYDOWN : WM_KEYUP;
        window->OnKey(key, scancode, action, 0);
        break;
    }
    return DefWindowProc(hwnd, message, wparam, lparam);
  }

  return DefWindowProc(window_handle_, message, wparam, lparam);
}

UINT Win32Window::GetCurrentDPI() {
  return current_dpi_;
}

UINT Win32Window::GetCurrentWidth() {
  return current_width_;
}

UINT Win32Window::GetCurrentHeight() {
  return current_height_;
}

HWND Win32Window::GetWindowHandle() {
  return window_handle_;
}

void Win32Window::Destroy() {
  if (window_handle_) {
    DestroyWindow(window_handle_);
    window_handle_ = nullptr;
  }

  UnregisterClass(window_class_name_.c_str(), nullptr);
}

// DPI Change handler. on WM_DPICHANGE resize the window
LRESULT
Win32Window::HandleDpiChange(HWND hwnd, WPARAM wparam, LPARAM lparam) {
  if (hwnd != nullptr) {
    auto window =
        reinterpret_cast<Win32Window*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));

    UINT uDpi = HIWORD(wparam);
    current_dpi_ = uDpi;
    window->OnDpiScale(uDpi);

    // Resize the window
    auto lprcNewScale = reinterpret_cast<RECT*>(lparam);
    LONG newWidth = lprcNewScale->right - lprcNewScale->left;
    LONG newHeight = lprcNewScale->bottom - lprcNewScale->top;

    SetWindowPos(hwnd, nullptr, lprcNewScale->left, lprcNewScale->top, newWidth,
                 newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
  }
  return 0;
}

void Win32Window::HandleResize(UINT width, UINT height) {
  current_width_ = width;
  current_height_ = height;
  OnResize(width, height);
}

Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
  return reinterpret_cast<Win32Window*>(
      GetWindowLongPtr(window, GWLP_USERDATA));
}

}  // namespace flutter