#include "SDL_log.h"
#include <SDL2/SDL.h>
#if defined(_WIN32)
#include <SDL2/SDL_syswm.h>
#include <imm.h>
#endif
#include <frostbite2D/core/window.h>
#include <frostbite2D/resource/asset.h>
#include <glad/glad.h>

namespace frostbite2D {

#if defined(_WIN32)
namespace {

HWND resolveWindowHandle(SDL_Window* sdlWindow) {
  if (!sdlWindow) {
    return nullptr;
  }

  SDL_SysWMinfo wmInfo;
  SDL_VERSION(&wmInfo.version);
  if (!SDL_GetWindowWMInfo(sdlWindow, &wmInfo)) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "Window: SDL_GetWindowWMInfo failed: %s", SDL_GetError());
    return nullptr;
  }

  if (wmInfo.subsystem != SDL_SYSWM_WINDOWS) {
    return nullptr;
  }

  return wmInfo.info.win.window;
}

} // namespace
#endif

bool Window::create(const WindowConfig& cfg) {
  Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;

#ifndef __SWITCH__
  if (cfg.fullscreen) {
    flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
  } else if (cfg.borderless) {
    flags |= SDL_WINDOW_BORDERLESS;
  }
  if (cfg.resizable) {
    flags |= SDL_WINDOW_RESIZABLE;
  }
  if (!cfg.decorated) {
    flags |= SDL_WINDOW_BORDERLESS;
  }
#endif

  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);

  if (cfg.multisamples > 0) {
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, cfg.multisamples);
  }

  int x = cfg.centered ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED;
  int y = cfg.centered ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED;

  sdlWindow_ = SDL_CreateWindow(cfg.title.c_str(), x, y, cfg.width, cfg.height,
                                flags);
  if (!sdlWindow_) {
    SDL_Log("Failed to create window: %s", SDL_GetError());
    return false;
  }

  glContext_ = SDL_GL_CreateContext(sdlWindow_);
  if (!glContext_) {
    SDL_Log("Failed to create OpenGL context: %s", SDL_GetError());
    SDL_DestroyWindow(sdlWindow_);
    sdlWindow_ = nullptr;
    return false;
  }

  if (!gladLoadGLES2Loader((GLADloadproc)SDL_GL_GetProcAddress)) {
    SDL_Log("Failed to initialize GLAD GLES2");
    SDL_GL_DeleteContext(glContext_);
    glContext_ = nullptr;
    SDL_DestroyWindow(sdlWindow_);
    sdlWindow_ = nullptr;
    return false;
  }

  SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0);
  refreshMetrics(false);

  fullscreen_ = (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
  vsync_ = cfg.vsync;
  cursorVisible_ = cfg.showCursor;
  showCursor(cfg.showCursor);
  setImmEnabled(immEnabled_);

#ifndef __SWITCH__
  if (!cfg.icon.file_path.empty()) {
    Asset& asset = Asset::get();
    std::string resolvedPath = asset.resolveAssetPath(cfg.icon.file_path);
    SDL_Surface* icon = SDL_LoadBMP(resolvedPath.c_str());
    if (icon) {
      SDL_SetWindowIcon(sdlWindow_, icon);
      SDL_FreeSurface(icon);
    } else {
      SDL_Log("Failed to load window icon: %s", resolvedPath.c_str());
    }
  }
#if defined(_WIN32)
  if (cfg.icon.resource_id != 0) {
    SDL_Log("Loading icon from resource ID: %u", cfg.icon.resource_id);
  }
#endif
#endif

  return true;
}

void Window::destroy() {
#if defined(_WIN32)
  if (sdlWindow_ && immDisabledByApp_) {
    HWND hwnd = resolveWindowHandle(sdlWindow_);
    if (hwnd) {
      ImmAssociateContext(hwnd, static_cast<HIMC>(immRestoreContext_));
    }
    immRestoreContext_ = nullptr;
    immDisabledByApp_ = false;
  }
#endif

  if (glContext_) {
    SDL_GL_DeleteContext(glContext_);
    glContext_ = nullptr;
  }
  if (sdlWindow_) {
    SDL_DestroyWindow(sdlWindow_);
    sdlWindow_ = nullptr;
  }
}

void Window::poll() {}

void Window::swap() {
  if (sdlWindow_ && glContext_) {
    SDL_GL_SwapWindow(sdlWindow_);
  }
}

void Window::close() {
  shouldClose_ = true;
  if (closeCb_) {
    closeCb_();
  }
}

void Window::setTitle(const std::string& title) {
  if (sdlWindow_) {
    SDL_SetWindowTitle(sdlWindow_, title.c_str());
  }
}

void Window::setSize(int w, int h) {
  if (sdlWindow_) {
    SDL_SetWindowSize(sdlWindow_, w, h);
    refreshMetrics(true);
    return;
  }

  width_ = w;
  height_ = h;
  drawableWidth_ = w;
  drawableHeight_ = h;
  scaleX_ = 1.0f;
  scaleY_ = 1.0f;
  if (resizeCb_) {
    resizeCb_(w, h);
  }
}

void Window::setPos(int x, int y) {
  if (sdlWindow_) {
    SDL_SetWindowPosition(sdlWindow_, x, y);
  }
}

void Window::setFullscreen(bool fs) {
  fullscreen_ = fs;
  if (!sdlWindow_) {
    return;
  }

#ifndef __SWITCH__
  Uint32 flags = fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
  SDL_SetWindowFullscreen(sdlWindow_, flags);
  refreshMetrics(true);
#endif
}

void Window::setVSync(bool vsync) {
  vsync_ = vsync;
  if (glContext_) {
    SDL_GL_SetSwapInterval(vsync ? 1 : 0);
  }
}

void Window::setVisible(bool visible) {
  if (!sdlWindow_) {
    return;
  }

  if (visible) {
    SDL_ShowWindow(sdlWindow_);
  } else {
    SDL_HideWindow(sdlWindow_);
  }
}

int Window::width() const { return width_; }

int Window::height() const { return height_; }

int Window::drawableWidth() const { return drawableWidth_; }

int Window::drawableHeight() const { return drawableHeight_; }

Size Window::size() const {
  return Size{static_cast<float>(width_), static_cast<float>(height_)};
}

Vec2 Window::pos() const {
  if (sdlWindow_) {
    int x = 0;
    int y = 0;
    SDL_GetWindowPosition(sdlWindow_, &x, &y);
    return Vec2{static_cast<float>(x), static_cast<float>(y)};
  }
  return Vec2{0.0f, 0.0f};
}

bool Window::fullscreen() const { return fullscreen_; }

bool Window::vsync() const { return vsync_; }

bool Window::focused() const { return focused_; }

bool Window::minimized() const { return minimized_; }

void Window::setImmEnabled(bool enabled) {
  immEnabled_ = enabled;

#if defined(_WIN32)
  if (!sdlWindow_) {
    return;
  }

  HWND hwnd = resolveWindowHandle(sdlWindow_);
  if (!hwnd) {
    return;
  }

  if (enabled) {
    if (!immDisabledByApp_) {
      return;
    }

    ImmAssociateContext(hwnd, static_cast<HIMC>(immRestoreContext_));
    immRestoreContext_ = nullptr;
    immDisabledByApp_ = false;
    SDL_Log("Window IMM enabled");
    return;
  }

  if (immDisabledByApp_) {
    return;
  }

  immRestoreContext_ = reinterpret_cast<void*>(ImmAssociateContext(hwnd, nullptr));
  immDisabledByApp_ = true;
  SDL_Log("Window IMM disabled");
#else
  (void)enabled;
#endif
}

bool Window::isImmEnabled() const { return immEnabled_; }

float Window::scaleX() const { return scaleX_; }

float Window::scaleY() const { return scaleY_; }

bool Window::refreshMetrics(bool emitResize) {
  if (!sdlWindow_) {
    return false;
  }

  int prevWidth = width_;
  int prevHeight = height_;
  int prevDrawableWidth = drawableWidth_;
  int prevDrawableHeight = drawableHeight_;
  bool prevFocused = focused_;

  SDL_GetWindowSize(sdlWindow_, &width_, &height_);
  SDL_GL_GetDrawableSize(sdlWindow_, &drawableWidth_, &drawableHeight_);

  if (width_ > 0 && height_ > 0) {
    scaleX_ = static_cast<float>(drawableWidth_) / static_cast<float>(width_);
    scaleY_ = static_cast<float>(drawableHeight_) / static_cast<float>(height_);
  } else {
    scaleX_ = 1.0f;
    scaleY_ = 1.0f;
  }

  Uint32 windowFlags = SDL_GetWindowFlags(sdlWindow_);
  focused_ = (windowFlags & SDL_WINDOW_INPUT_FOCUS) != 0;
  minimized_ = (windowFlags & SDL_WINDOW_MINIMIZED) != 0;
  fullscreen_ = (windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0 ||
                (windowFlags & SDL_WINDOW_FULLSCREEN) != 0;

  bool changed = prevWidth != width_ || prevHeight != height_ ||
                 prevDrawableWidth != drawableWidth_ ||
                 prevDrawableHeight != drawableHeight_;
  if (emitResize && changed && resizeCb_) {
    resizeCb_(width_, height_);
  }
  if (prevFocused != focused_ && focusCb_) {
    focusCb_(focused_);
  }

  return true;
}

void Window::setCursor(CursorType cursor) {
  SDL_SystemCursor systemCursor = SDL_SYSTEM_CURSOR_ARROW;
  switch (cursor) {
    case CursorType::Arrow:
      systemCursor = SDL_SYSTEM_CURSOR_ARROW;
      break;
    case CursorType::TextInput:
      systemCursor = SDL_SYSTEM_CURSOR_IBEAM;
      break;
    case CursorType::Hand:
      systemCursor = SDL_SYSTEM_CURSOR_HAND;
      break;
    case CursorType::SizeAll:
      systemCursor = SDL_SYSTEM_CURSOR_SIZEALL;
      break;
    case CursorType::SizeWE:
      systemCursor = SDL_SYSTEM_CURSOR_SIZEWE;
      break;
    case CursorType::SizeNS:
      systemCursor = SDL_SYSTEM_CURSOR_SIZENS;
      break;
    case CursorType::SizeNESW:
      systemCursor = SDL_SYSTEM_CURSOR_SIZENESW;
      break;
    case CursorType::SizeNWSE:
      systemCursor = SDL_SYSTEM_CURSOR_SIZENWSE;
      break;
    case CursorType::No:
      systemCursor = SDL_SYSTEM_CURSOR_ARROW;
      break;
  }

  SDL_Cursor* sdlCursor = SDL_CreateSystemCursor(systemCursor);
  if (sdlCursor) {
    SDL_SetCursor(sdlCursor);
  }
}

void Window::showCursor(bool show) {
  cursorVisible_ = show;
  SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
}

void Window::lockCursor(bool lock) {
  cursorLocked_ = lock;
  SDL_SetRelativeMouseMode(lock ? SDL_TRUE : SDL_FALSE);
}

void Window::onResize(ResizeCb cb) { resizeCb_ = cb; }

void Window::onClose(CloseCb cb) { closeCb_ = cb; }

void Window::onFocus(FocusCb cb) { focusCb_ = cb; }

void* Window::native() const { return nullptr; }

} // namespace frostbite2D
