// Copyright 2014 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_window.h"

#include <optional>
#include <mutex>

#include <dwmapi.h>
#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>

#include "flutter/generated_plugin_registrant.h"
#include "utils.h"

/// Window attribute that enables dark mode window decorations.
///
/// Redefined in case the developer's machine has a Windows SDK older than
/// version 10.0.22000.0.
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif

/// Registry key for app theme preference.
///
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
/// value indicates apps should use light mode.
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
  L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";

FlutterWindow::FlutterWindow(const flutter::DartProject& project)
    : project_(project) {}

FlutterWindow::~FlutterWindow() {}

bool FlutterWindow::OnCreate() {
  if (!Win32Window::OnCreate()) {
    return false;
  }

  RECT frame = GetClientArea();

  // The size here must match the window dimensions to avoid unnecessary surface
  // creation / destruction in the startup path.
  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
      frame.right - frame.left, frame.bottom - frame.top, project_);
  // Ensure that basic setup of the controller was successful.
  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
    return false;
  }
  RegisterPlugins(flutter_controller_->engine());
  SetChildContent(flutter_controller_->view()->GetNativeWindow());

  static std::mutex visible_mutex;
  static bool visible = false;

  flutter_controller_->engine()->SetNextFrameCallback([&]() {
    std::scoped_lock lock(visible_mutex);
    this->Show();
    visible = true;
  });

  // Flutter can complete the first frame before the "show window" callback is
  // registered. The following call ensures a frame is pending to ensure the
  // window is shown. It is a no-op if the first frame hasn't completed yet.
  flutter_controller_->ForceRedraw();

  // Create a method channel to check the window's visibility.
  flutter::MethodChannel<> channel(
      flutter_controller_->engine()->messenger(), "tests.flutter.dev/windows_startup_test",
      &flutter::StandardMethodCodec::GetInstance());

  channel.SetMethodCallHandler(
    [&](const flutter::MethodCall<>& call,
       std::unique_ptr<flutter::MethodResult<>> result) {
       std::string method = call.method_name();

      if (method == "isWindowVisible") {
        std::scoped_lock lock(visible_mutex);
        result->Success(visible);
      } else if (method == "isAppDarkModeEnabled") {
        BOOL enabled;
        HRESULT hr = DwmGetWindowAttribute(GetHandle(),
                                           DWMWA_USE_IMMERSIVE_DARK_MODE,
                                           &enabled, sizeof(enabled));
        if (SUCCEEDED(hr)) {
          result->Success((bool)enabled);
        } else if (hr == E_INVALIDARG) {
          // Fallback if the operating system doesn't support dark mode.
          result->Success(false);
        } else {
          result->Error("error", "Received result handle " + hr);
        }
      } else if (method == "isSystemDarkModeEnabled") {
        DWORD data;
        DWORD data_size = sizeof(data);
        LONG status = RegGetValue(HKEY_CURRENT_USER,
                                  kGetPreferredBrightnessRegKey,
                                  kGetPreferredBrightnessRegValue,
                                  RRF_RT_REG_DWORD, nullptr, &data, &data_size);

        if (status == ERROR_SUCCESS) {
          // Preferred brightness is 0 if dark mode is enabled,
          // otherwise non-zero.
          result->Success(data == 0);
        } else if (status == ERROR_FILE_NOT_FOUND) {
          // Fallback if the operating system doesn't support dark mode.
          result->Success(false);
        } else {
          result->Error("error", "Received status " + status);
        }
      } else if (method == "convertString") {
        const flutter::EncodableValue* argument = call.arguments();
        const std::vector<int32_t> code_points = std::get<std::vector<int32_t>>(*argument);
        std::vector<wchar_t> wide_str;
        for (int32_t code_point : code_points) {
          wide_str.push_back((wchar_t)(code_point));
        }
        wide_str.push_back((wchar_t)0);
        const std::string string = Utf8FromUtf16(wide_str.data());
        result->Success(string);
      } else {
        result->NotImplemented();
      }
    });

  return true;
}

void FlutterWindow::OnDestroy() {
  if (flutter_controller_) {
    flutter_controller_ = nullptr;
  }

  Win32Window::OnDestroy();
}

LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
                              WPARAM const wparam,
                              LPARAM const lparam) noexcept {
  // Give Flutter, including plugins, an opportunity to handle window messages.
  if (flutter_controller_) {
    std::optional<LRESULT> result =
        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
                                                      lparam);
    if (result) {
      return *result;
    }
  }

  switch (message) {
    case WM_FONTCHANGE:
      flutter_controller_->engine()->ReloadSystemFonts();
      break;
  }

  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}