#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include <VersionHelpers.h>

#include "flutter_window.h"
#include "utils.h"
#include "dump.h"

#define APP_MUTEX_NAME L"flashlink_win.mutex"
#define APP_WINDOW_NAME L"FlashLink VPN"

UINT (*MyGetDpiForWindow) (HWND) = [] (HWND) { return 96u; };
int (*MyGetSystemMetricsForDpi) (int, UINT) = [] (int nIndex, UINT) { return GetSystemMetrics(nIndex); };

void runAsAdmin()
{
  WCHAR czFileName[1024] = {0};
  GetModuleFileName(NULL, czFileName, _countof(czFileName) - 1);
  SHELLEXECUTEINFO EI;
  memset(&EI, 0, sizeof(EI));
  EI.cbSize = sizeof(SHELLEXECUTEINFO);
  EI.lpVerb = TEXT("runas");
  EI.fMask = 0x00000040;
  EI.lpFile = czFileName;
  EI.nShow = SW_SHOW;
  ShellExecuteEx(&EI);
}

BOOL isRunAsAdmin()
{
  BOOL isRunAsAdmin = FALSE;
  HANDLE hToken = NULL;
  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
    return FALSE;
  }
  TOKEN_ELEVATION tokenEle;
  DWORD dwRetLen = 0;
  if (GetTokenInformation(hToken, TokenElevation, &tokenEle, sizeof(tokenEle), &dwRetLen)) {
    if (dwRetLen == sizeof(tokenEle)) {
      isRunAsAdmin = tokenEle.TokenIsElevated;
    }
  }
  CloseHandle(hToken);
  return isRunAsAdmin;
}

int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
                      _In_ wchar_t *command_line, _In_ int show_command) {
  bool isDebugMode = false;
  char buffer[2];
  if (GetEnvironmentVariableA("DEBUG_MODE", buffer, sizeof(buffer)) > 0) {
    isDebugMode = true;
  }

  // 以管理员权限运行
  if (!isDebugMode && !isRunAsAdmin()) {
    runAsAdmin();
    exit(0);
  }

  // 捕捉异常
  //SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);

  // Attach to console when present (e.g., 'flutter run') or create a
  // new console when running with a debugger.
  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
    CreateAndAttachConsole();
  }

  // Initialize COM, so that it is available for use in the library and/or
  // plugins.
  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);

  flutter::DartProject project(L"data");

  std::vector<std::string> command_line_arguments =
      GetCommandLineArguments();

  project.set_dart_entrypoint_arguments(std::move(command_line_arguments));

  FlutterWindow window(project);
  UINT scrWidth, scrHeight, xShaft, yShaft;

  double scale_factor_x = 1.0;
  double scale_factor_y = 1.0;

  // 计算屏幕逻辑尺寸
  if (IsWindows10OrGreater()) {
    UINT dpiX, dpiY;

    HDC screen = GetDC(NULL);
    dpiX = GetDeviceCaps(screen, LOGPIXELSX);
    dpiY = GetDeviceCaps(screen, LOGPIXELSY);
    ReleaseDC(NULL, screen);

    scale_factor_x = dpiX / 96.0;
    scale_factor_y = dpiY / 96.0;

    if (auto user32 = LoadLibraryA("User32.dll")) {
      if (auto fn = GetProcAddress(user32, "GetDpiForWindow")) {
          MyGetDpiForWindow = (decltype(MyGetDpiForWindow)) fn;
          MyGetSystemMetricsForDpi = (decltype(MyGetSystemMetricsForDpi)) GetProcAddress(user32, "GetSystemMetricsForDpi");
      }
      scrWidth = static_cast<UINT>(round(MyGetSystemMetricsForDpi(SM_CXFULLSCREEN, dpiX) / scale_factor_x));
      scrHeight = static_cast<UINT>(round(MyGetSystemMetricsForDpi(SM_CYFULLSCREEN, dpiY) / scale_factor_y));
    } else {
      scrWidth = GetSystemMetrics(SM_CXSCREEN);
      scrHeight = GetSystemMetrics(SM_CYSCREEN);
    }
  } else {
    scrWidth = GetSystemMetrics(SM_CXSCREEN);
    scrHeight = GetSystemMetrics(SM_CYSCREEN);
  }

  // 程序窗口大小
  UINT windowWidth = 340, windowHeight = 640;
  Win32Window::Size size(windowWidth, windowHeight);

  // 计算程序显示坐标原点(屏幕中间)
  xShaft = (scrWidth - windowWidth) / 2;
  yShaft = (scrHeight - windowHeight) / 2;
  Win32Window::Point origin(xShaft, yShaft);

  if (!window.Create(APP_WINDOW_NAME, origin, size)) {
    return EXIT_FAILURE;
  }
  window.SetQuitOnClose(true);

  // int minWidth = static_cast<int>(360 * scale_factor_x);
  // int minHeight = static_cast<int>(640 * scale_factor_y);
  // window.SetMinimumSize({minWidth, minHeight});
  window.SetMinimumSize(SIZE{ 340, 640 });

  // 设置窗口不可最大化以及不可缩放
  HWND hWnd = window.GetHandle();
  DWORD gwlStyle = GetWindowLong(hWnd, GWL_STYLE);
  gwlStyle &= ~WS_THICKFRAME;
  gwlStyle &= ~WS_MAXIMIZEBOX;
  SetWindowLong(hWnd, GWL_STYLE, gwlStyle);

  ::MSG msg;
  while (::GetMessage(&msg, nullptr, 0, 0)) {
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
  }

  ::CoUninitialize();
  return EXIT_SUCCESS;
}
