// SPDX-License-Identifier: Unlicense

#include "window.h"

#include "global.h"
#include "input.h"
#include "opengl.h"
#include "type.h"

#ifdef SHR3D_WINDOW_WIN32
#include <Windows.h>
#endif // SHR3D_WINDOW_WIN32
#ifdef SHR3D_WINDOW_SDL
#include <SDL.h>
#include <SDL_syswm.h>
#endif // SHR3D_WINDOW_SDL

#ifdef SHR3D_WINDOW_SDL
void Window_::toggleFullscreen(SDL_Window* hwnd, FullscreenMode& fullscreenMode)
{
  switch (fullscreenMode)
  {
  case FullscreenMode::windowed:
    fullscreenMode = FullscreenMode::borderless;
    SDL_SetWindowFullscreen(hwnd, SDL_WINDOW_FULLSCREEN_DESKTOP);
    break;
  case FullscreenMode::borderless:
    fullscreenMode = FullscreenMode::windowed;
    SDL_SetWindowFullscreen(hwnd, 0);
    break;
  default:
    unreachable();
  }
}

void Window_::handleInput(SDL_Event& event)
{
  switch (event.type)
  {
  case SDL_QUIT:
    Global::appQuit = true;
    break;
  case SDL_KEYDOWN:
#ifdef SHR3D_COOP
    if (event.window.windowID == Global::coopWindowId)
    {
      if (event.key.keysym.sym == SDLK_F4)
      {
        const i32 arrangementIndexCount = Global::songTracks.size();

        if (arrangementIndexCount >= 1)
        {
          Global::coopSelectedArrangementIndex = (Global::coopSelectedArrangementIndex + 1) % arrangementIndexCount;

          if (Global::songInfos[Global::selectedSongIndex].arrangementInfos[Global::coopSelectedArrangementIndex].arrangementName == "Vocals")
            Global::coopSelectedArrangementIndex = (Global::coopSelectedArrangementIndex + 1) % arrangementIndexCount;
        }
      }
    }
    else
#endif // SHR3D_COOP
    {
      Input::keyStateChange(event.key.keysym.sym, true);
    }
    break;
  case SDL_KEYUP:
#ifdef SHR3D_COOP
    if (event.window.windowID == Global::coopWindowId)
    {
    }
    else
#endif // SHR3D_COOP
    {
      Input::keyStateChange(event.key.keysym.sym, false);
    }
    break;
  case SDL_MOUSEBUTTONDOWN:
    Input::keyStateChange(event.button.button, true);
    break;
  case SDL_MOUSEBUTTONUP:
    Input::keyStateChange(event.button.button, false);
    break;
  case SDL_MOUSEMOTION:
    Global::inputUseController = false;
    Global::inputCursorPosX = event.motion.x;
    Global::inputCursorPosY = event.motion.y;
#ifdef SHR3D_OPENXR_PCVR
    Global::inputCursorPosXrX += event.motion.xrel;
    Global::inputCursorPosXrY += event.motion.yrel;
#endif // SHR3D_OPENXR_PCVR
    break;
  case SDL_CONTROLLERBUTTONDOWN:
    switch (event.cbutton.button)
    {
    case SDL_CONTROLLER_BUTTON_A:
      break;
    case SDL_CONTROLLER_BUTTON_B:
      break;
    case SDL_CONTROLLER_BUTTON_X:
      break;
    case SDL_CONTROLLER_BUTTON_Y:
      break;
    case SDL_CONTROLLER_BUTTON_RIGHTSTICK:
      break;
    case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
      break;
    case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
      break;
    }
    break;
  case SDL_CONTROLLERBUTTONUP:
    switch (event.cbutton.button)
    {
    case SDL_CONTROLLER_BUTTON_A:
      break;
    case SDL_CONTROLLER_BUTTON_B:
      break;
    case SDL_CONTROLLER_BUTTON_X:
      break;
    case SDL_CONTROLLER_BUTTON_Y:
      break;
    case SDL_CONTROLLER_BUTTON_RIGHTSTICK:
      break;
    case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
      break;
    case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
      break;
    }
    break;
  case SDL_JOYAXISMOTION:
    switch (event.caxis.axis)
    {
    case SDL_CONTROLLER_AXIS_LEFTX:
      break;
    case SDL_CONTROLLER_AXIS_LEFTY:
      break;
    case SDL_CONTROLLER_AXIS_RIGHTX:
      break;
    case SDL_CONTROLLER_AXIS_RIGHTY:
      break;
    case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
      break;
    case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
      break;
    }
    break;
  case SDL_CONTROLLERDEVICEADDED:
    if (Global::gameController == nullptr && SDL_IsGameController(0))
      Global::gameController = SDL_GameControllerOpen(0);
    break;
  case SDL_CONTROLLERDEVICEREMOVED:
    SDL_GameControllerClose(Global::gameController);
    Global::gameController = nullptr;
    break;
  case SDL_WINDOWEVENT:
  {
    switch (event.window.event)
    {
#ifdef SHR3D_COOP
    case SDL_WINDOWEVENT_CLOSE:
      if (event.window.windowID == Global::coopWindowId)
        Global::inputCoop.toggled = !Global::inputCoop.toggled;
      else
        Global::appQuit = true;
      break;
#endif // SHR3D_COOP
    case SDL_WINDOWEVENT_SIZE_CHANGED:
#ifdef SHR3D_COOP
      if (event.window.windowID == Global::coopWindowId)
      {
        Global::coopResolutionWidth = event.window.data1;
        Global::coopResolutionHeight = event.window.data2;
      }
      else
#endif // SHR3D_COOP
      {
        Global::resolutionWidth = event.window.data1;
        Global::resolutionHeight = event.window.data2;

        if (Settings::graphicsFullscreen == FullscreenMode::windowed)
        {
          Settings::graphicsWindowWidth = Global::resolutionWidth;
          Settings::graphicsWindowHeight = Global::resolutionHeight;
        }
        GL(glViewport(0, 0, Global::resolutionWidth, Global::resolutionHeight));
      }
      break;
    }
  }
  break;
  }
}
#endif // SHR3D_WINDOW_SDL

#ifdef SHR3D_WINDOW_WIN32

const LONG Window_::windowedStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
const LONG Window_::borderlessStyle = WS_POPUP | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

void Window_::toggleFullscreen(const HWND hwnd, const i32 resolutionWidth, const i32 resolutionHeight, FullscreenMode& fullscreenMode, WINDOWPLACEMENT& lastWindowPlacement)
{
  if (fullscreenMode == FullscreenMode::windowed)
  {
    fullscreenMode = FullscreenMode::borderless;

    MONITORINFO mi = { sizeof(mi) };
    GetWindowPlacement(hwnd, &lastWindowPlacement);
    if (GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &mi)) {

      SetWindowLong(Global::window, GWL_STYLE, borderlessStyle);
      SetWindowPos(hwnd, HWND_TOP,
        mi.rcMonitor.left, mi.rcMonitor.top,
        mi.rcMonitor.right - mi.rcMonitor.left,
        mi.rcMonitor.bottom - mi.rcMonitor.top,
        SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
    }
  }
  else
  {
    if (lastWindowPlacement.length == 0) // lastWindowPlacement was not initialized, then initialize it to the center of screen and the known window width/height
    {
      RECT rect = { 0, 0, resolutionWidth, resolutionHeight };
      AdjustWindowRectEx(&rect, windowedStyle, FALSE, 0);
      const i32 windowWidth = rect.right - rect.left;
      const i32 windowHeight = rect.bottom - rect.top;

      lastWindowPlacement = WINDOWPLACEMENT{
        .length = sizeof(lastWindowPlacement),
        .showCmd = 1,
        .ptMinPosition {
          .x = -1,
          .y = -1
        },
        .ptMaxPosition {
          .x = -1,
          .y = -1
        },
        .rcNormalPosition {
          .left = (GetSystemMetrics(SM_CXSCREEN) - windowWidth) / 2,
          .top = (GetSystemMetrics(SM_CYSCREEN) - windowHeight) / 2,
          .right = (GetSystemMetrics(SM_CXSCREEN) + windowWidth) / 2,
          .bottom = (GetSystemMetrics(SM_CYSCREEN) + windowHeight) / 2,
        }
      };
    }

    fullscreenMode = FullscreenMode::windowed;

    SetWindowLong(hwnd, GWL_STYLE, windowedStyle);
    SetWindowPlacement(hwnd, &lastWindowPlacement);
    SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
      SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
      SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
  }
}
#endif // SHR3D_WINDOW_WIN32


#ifdef SHR3D_SFX_PLUGIN
Shr3DWindow Window_::createPluginHostWindow(const i32 x, const i32 y, const i32 width, const i32 height)
{
#ifdef SHR3D_WINDOW_SDL
  SDL_Window *childWindow = SDL_CreateWindow("Child", x, y, width, height, SDL_WINDOW_POPUP_MENU | SDL_WINDOW_SHOWN);

  // SDL2 does not support setting a parent window
  // use X11 code to do that

  SDL_SysWMinfo parentInfo;
  SDL_VERSION(&parentInfo.version);
  SDL_GetWindowWMInfo(Global::window, &parentInfo);

  SDL_SysWMinfo childInfo;
  SDL_VERSION(&childInfo.version);
  SDL_GetWindowWMInfo(childWindow, &childInfo);

  Display* display = parentInfo.info.x11.display;

  Window parent = parentInfo.info.x11.window;
  Window child = childInfo.info.x11.window;

  XReparentWindow(display, child, parent, 0, 0);
  XMapWindow(display, child);
  XFlush(display);

  return childWindow;
#endif // SHR3D_WINDOW_SDL
#ifdef SHR3D_WINDOW_WIN32
  const LPCWSTR className = L"Shr3DPluginWindow";

  if (static bool wndClassRegistered; !wndClassRegistered)
  {
    wndClassRegistered = true;
    WNDCLASS wc = {
      .style = 0,
      .lpfnWndProc = DefWindowProc, // Use default window procedure
      .cbClsExtra = 0,
      .cbWndExtra = 0,
      .hInstance = GetModuleHandle(NULL),
      .hIcon = LoadIcon(NULL, IDI_APPLICATION),
      .hCursor = LoadCursor(NULL, IDC_ARROW),
      .hbrBackground = (HBRUSH)(COLOR_WINDOW + 1),
      .lpszClassName = className
    };
    RegisterClass(&wc);
  }

  const HINSTANCE hInstanceMain = (HINSTANCE)GetWindowLongPtr(Global::window, GWLP_HINSTANCE);
  const HWND hwnd = CreateWindowExW(
    0,
    className,
    nullptr,
    WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
    x,
    y,
    width,
    height,
    Global::window,
    0,
    hInstanceMain,
    0
  );
  //ShowWindow(hwnd, SW_SHOWNORMAL);

  return hwnd;
#endif // SHR3D_WINDOW_WIN32
}
void Window_::destoryPluginHostWindow(Shr3DWindow window)
{
  ASSERT(window != NULL);

#ifdef SHR3D_WINDOW_SDL
  SDL_DestroyWindow(window);
#endif // SHR3D_WINDOW_SDL

#ifdef SHR3D_WINDOW_WIN32
  ShowWindow(window, SW_HIDE);
  CloseWindow(window);
  DestroyWindow(window);
#endif // SHR3D_WINDOW_WIN32
}

void Window_::setPluginHostWindowPosition(Shr3DWindow window, i32 x, i32 y, i32 width, i32 height)
{
  ASSERT(window != NULL);

#ifdef SHR3D_WINDOW_SDL
  SDL_SetWindowPosition(window, x, y);
#endif // SHR3D_WINDOW_SDL

#ifdef SHR3D_WINDOW_WIN32
  MoveWindow(window, x, y, width, height, TRUE);
#endif // SHR3D_WINDOW_WIN32
}

#endif // SHR3D_SFX_PLUGIN
