// SPDX-License-Identifier: Unlicense

#include "coop.h"

#ifdef SHR3D_COOP

#include "camera.h"
#include "geometry.h"
#include "global.h"
#include "highway.h"
#include "highway2.h"
#include "hud.h"
#include "hud2.h"
#include "opengl.h"
#include "version.h"
#include "window.h"

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

#ifndef GCL_HICON // winuser.h
#define GCL_HICON -14
#endif // GCL_HICON

#ifdef SHR3D_WINDOW_WIN32
static LRESULT CALLBACK WndProcCoop(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
  case WM_INPUT:
  {
    HRAWINPUT hRawInput = (HRAWINPUT)lParam;

    UINT size;
    if (GetRawInputData(hRawInput, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER)) != 0)
    {
      ASSERT(false);
      break;
    }

    LPBYTE buffer = new BYTE[size];
    ASSERT(buffer != nullptr);

    if (GetRawInputData(hRawInput, RID_INPUT, buffer, &size, sizeof(RAWINPUTHEADER)) != size)
    {
      ASSERT(false);
      delete[] buffer;
      break;
    }

    RAWINPUT* raw = (RAWINPUT*)buffer;
    if (raw->header.dwType == RIM_TYPEKEYBOARD)
    {
      if (GetForegroundWindow() == hwnd)
      {
        RAWKEYBOARD& keyboard = raw->data.keyboard;
        if (keyboard.Flags == RI_KEY_MAKE) // not sure if RI_KEY_E0 is needed
        {
          //Input::keyStateChange(keyboard.VKey, true);

          if (keyboard.VKey == VK_RETURN && GetKeyState(VK_MENU) & (1 << 15)) // alt return
          {
            static WINDOWPLACEMENT lastWindowPlacement;
            Window_::toggleFullscreen(hwnd, Global::coopResolutionWidth, Global::coopResolutionHeight, Global::coopWindowFullscreenMode, lastWindowPlacement);
          }

          if (keyboard.VKey == VK_F4)
          {
            const i32 arrangementIndexCount = i32(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 if (keyboard.Flags & RI_KEY_BREAK)
        {
          //Input::keyStateChange(keyboard.VKey, false);
        }
      }
    }
    if (raw->header.dwType == RIM_TYPEMOUSE)
    {
      if (GetForegroundWindow() == hwnd)
      {
        // Handle mouse input
        RAWMOUSE& mouse = raw->data.mouse;

        if (mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN)
        {
          //Input::keyStateChange(VK_LBUTTON, true);
        }
        else if (mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP)
        {
          //Input::keyStateChange(VK_LBUTTON, false);
        }
        else if (mouse.usButtonFlags & RI_MOUSE_WHEEL)
        {
          //Global::inputWheelDelta += i16(mouse.usButtonData);
        }
      }
    }
    delete[] buffer;
    break;
  }
  break;
  case WM_MOUSEMOVE:
  {
    //Global::inputCursorPosX = LOWORD(lParam);
    //Global::inputCursorPosY = HIWORD(lParam);
  }
  break;
  case WM_SYSCOMMAND:
    if (wParam == SC_KEYMENU && (lParam >> 16) <= 0) // disable alt key. It causes beeps because it tries with shortcurts from the non exsisting menu bar.
      return 0;
    return DefWindowProcA(hwnd, msg, wParam, lParam);
  case WM_SIZE:
    Global::coopResolutionWidth = LOWORD(lParam);
    Global::coopResolutionHeight = HIWORD(lParam);

    //if (Settings::graphicsFullscreen == FullscreenMode::windowed)
    //{
    //  Settings::graphicsWindowWidth = Global::resolutionWidth;
    //  Settings::graphicsWindowHeight = Global::resolutionHeight;
    //}
    break;
  case WM_CLOSE:
    DestroyWindow(hwnd);
    hwnd = 0;
    Global::inputCoop.toggled = !Global::inputCoop.toggled;
    break;
  default:
    return DefWindowProcA(hwnd, msg, wParam, lParam);
  }
  return 0;
}
#endif // SHR3D_WINDOW_WIN32

void Coop::tick()
{
#ifdef SHR3D_WINDOW_SDL
  if (Global::coopWindow != nullptr && !Global::inputCoop.toggled)
  {
    SDL_DestroyWindow(Global::coopWindow);
    Global::coopWindow = nullptr;
    Global::coopWindowId = 0;
  }
  else if (Global::coopWindow == nullptr && Global::inputCoop.toggled)
  {
    Global::coopWindow = SDL_CreateWindow("Shr3D Co-Op", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 320, 240, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);
    Global::coopWindowId = SDL_GetWindowID(Global::coopWindow);
    ASSERT(Global::coopWindow != nullptr);

    SDL_GL_MakeCurrent(Global::coopWindow, Global::glContext);
    SDL_GL_SetSwapInterval(to_underlying_(Settings::graphicsVSync));
    SDL_GL_MakeCurrent(Global::window, Global::glContext);
  }

  if (Global::inputCoop.toggled)
  {
    const i32 arrangementIndexCount = i32(Global::songTracks.size());
    if (arrangementIndexCount > 0)
      if (Global::coopSelectedArrangementIndex >= arrangementIndexCount)
        Global::coopSelectedArrangementIndex = (Global::coopSelectedArrangementIndex + 1) % arrangementIndexCount;
  }
#endif // SHR3D_WINDOW_SDL

#ifdef SHR3D_WINDOW_WIN32
  if (Global::coopWindow != 0 && !Global::inputCoop.toggled)
  {
    DestroyWindow(Global::coopWindow);
    Global::coopWindow = 0;
  }
  else if (Global::coopWindow == 0 && Global::inputCoop.toggled)
  {
    HINSTANCE hInstanceMain = (HINSTANCE)GetWindowLongPtr(Global::window, GWLP_HINSTANCE);

    static bool windowRegistered;
    if (!windowRegistered)
    {
      WNDCLASSA lpWndClass = {
        .style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
        .lpfnWndProc = WndProcCoop,
        .hInstance = hInstanceMain,
        .hIcon = reinterpret_cast<HICON>(static_cast<u64>((GetClassLong(Global::window, GCL_HICON)))),
        .hCursor = GetCursor(),
        .hbrBackground = 0,
        .lpszClassName = "Shr3DCoopWindowClass"
      };
      if (!RegisterClassA(&lpWndClass)) {
        ASSERT(false); // Failed to register window.
      }
      windowRegistered = true;
    }

    RECT rect = { 0, 0, 320, 240 };
    AdjustWindowRectEx(&rect, Window_::windowedStyle, FALSE, 0);

    const i32 windowWidth = rect.right - rect.left;
    const i32 windowHeight = rect.bottom - rect.top;

    Global::coopWindow = CreateWindowExA(
      0,
      "Shr3DCoopWindowClass",
      "Shr3D v" VERSION_STR " Co-Op",
      Window_::windowedStyle,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      windowWidth,
      windowHeight,
      Global::window,
      0,
      hInstanceMain,
      0
    );

    Global::coopHdc = GetDC(Global::coopWindow);

    if (!SetPixelFormat(Global::coopHdc, Global::pixelFormat, &Global::pixelFormatDescriptor)) {
      ASSERT(false); // Failed to set the pixel format
    }

    wglMakeCurrent(Global::coopHdc, Global::glContext);
    wglSwapIntervalEXT(to_underlying_(Settings::graphicsVSync));

    //ShowWindow(Global::coopWindow, SW_SHOWNORMAL);

    wglMakeCurrent(Global::hdc, Global::glContext);
  }

  {
    const i32 arrangementIndexCount = i32(Global::songTracks.size());
    if (arrangementIndexCount > 0)
      if (Global::coopSelectedArrangementIndex >= arrangementIndexCount)
        Global::coopSelectedArrangementIndex = (Global::coopSelectedArrangementIndex + 1) % arrangementIndexCount;
  }
#endif // SHR3D_WINDOW_WIN32
}

void Coop::render()
{
  if (Global::coopWindow == nullptr)
    return;

#ifdef SHR3D_WINDOW_SDL
  SDL_GL_MakeCurrent(Global::coopWindow, Global::glContext);
#else // SHR3D_WINDOW_SDL
  wglMakeCurrent(Global::coopHdc, Global::glContext);
#endif // SHR3D_WINDOW_SDL

  GL(glViewport(0, 0, Global::coopResolutionWidth, Global::coopResolutionHeight));

  GL(glBindVertexArray(Global::dynamicDrawVao));

  GL(glClearColor(Settings::environmentClearColor.r, Settings::environmentClearColor.g, Settings::environmentClearColor.b, 1.0f));
  GL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));

  Highway::tick(Global::coopHighwayCtx, Global::coopSelectedArrangementIndex);
  Hud::tick(Global::coopHudCtx, Global::coopSelectedArrangementIndex, Global::coopResolutionWidth, Global::coopResolutionHeight);

  GL(glBindVertexArray(Global::staticDrawVao));

  {
    static vec3 cameraTargetPosition;
    static vec3 cameraCurrentPosition;
    const mat4 coopViewMat = Camera::calculateViewMat(Settings::cameraMode, Global::coopSelectedArrangementIndex, cameraTargetPosition, cameraCurrentPosition);
    const mat4 coopViewProjectionMat = Camera::calculateProjectionViewMat(Global::coopResolutionWidth, Global::coopResolutionHeight, coopViewMat);
    static vec3 highwayTargetPosition;
    static vec3 highwayCurrentPosition;
    const mat4 coopHighwayViewProjectionMat = Camera::calculateHighwayProjectionViewMat(Settings::cameraMode, coopViewProjectionMat, highwayTargetPosition, highwayCurrentPosition);

#ifdef SHR3D_RENDERER_DEVELOPMENT
    switch (Settings::highwayRenderer)
    {
    case Renderer::production:
#endif // SHR3D_RENDERER_DEVELOPMENT
      Highway::render(Global::coopHighwayCtx, Global::coopSelectedArrangementIndex, coopHighwayViewProjectionMat);
#ifdef SHR3D_RENDERER_DEVELOPMENT
      break;
    case Renderer::development:
      Highway2::render(Global::coopHighwayCtx, Global::coopSelectedArrangementIndex, coopHighwayViewProjectionMat);
      break;
    }
#endif // SHR3D_RENDERER_DEVELOPMENT
  }

  GL(glBindVertexArray(Global::dynamicDrawVao));

#ifdef SHR3D_HUD_DEVELOPMENT
  switch (Settings::hudRenderer)
  {
  case Renderer::production:
#endif // SHR3D_HUD_DEVELOPMENT
    Hud::render(Global::coopHudCtx, Global::coopSelectedArrangementIndex, Global::coopResolutionWidth, Global::coopResolutionHeight);
#ifdef SHR3D_HUD_DEVELOPMENT
    break;
  case Renderer::development:
    Hud2::render(Global::coopHudCtx, Global::coopSelectedArrangementIndex, Global::coopResolutionWidth, Global::coopResolutionHeight);
    break;
  }
#endif // SHR3D_HUD_DEVELOPMENT

#ifdef SHR3D_WINDOW_SDL
  SDL_GL_SwapWindow(Global::coopWindow);
  SDL_GL_MakeCurrent(Global::window, Global::glContext);
#else // SHR3D_WINDOW_SDL
  SwapBuffers(Global::coopHdc);
  wglMakeCurrent(Global::hdc, Global::glContext);
#endif // SHR3D_WINDOW_SDL

  GL(glViewport(0, 0, Global::resolutionWidth, Global::resolutionHeight));
}

#endif // SHR3D_COOP
