#include "game-gui-glfw.hpp"

#include "compiler.hpp"
#include "consts.hpp"

queue<UIEvent> GameGui_GLFW::s_events;
string GameGui_GLFW::s_errorMessage;

string& GameGui_GLFW::getError() {
   return GameGui_GLFW::s_errorMessage;
}

bool GameGui_GLFW::init() {
   GameGui_GLFW::s_errorMessage = "No error";
   glfwSetErrorCallback(glfw_error_callback);

   windowWidth = -1;
   windowHeight = -1;

   return glfwInit() == GLFW_TRUE ? RTWO_SUCCESS : RTWO_ERROR;
}

void GameGui_GLFW::shutdown() {
   glfwTerminate();
}

void* GameGui_GLFW::createWindow(const string& title, int width, int height, bool fullscreen) {
   GLFWmonitor* mon = nullptr;

#if defined(GAMEGUI_INCLUDE_VULKAN)
   glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // This is for Vulkan, OpenGL needs different flags
#elif defined(MAC)
   glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
   glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
   glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
   glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#else
   glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
   glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
#endif

   glfwWindowHint(GLFW_SAMPLES, 16);
   glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true);

   if (fullscreen) {
      mon = glfwGetPrimaryMonitor();
      const GLFWvidmode* vmode = glfwGetVideoMode(mon);

      windowWidth = vmode->width;
      windowHeight = vmode->height;
   } else {
      windowWidth = width;
      windowHeight = height;
   }

   window = glfwCreateWindow(windowWidth, windowHeight, title.c_str(), mon, nullptr);

   // TODO: I should check the window size after it's created to make sure it matches the requested size
   // glfwGetFramebufferSize(window, width, height) segfaults on OSX, check other platforms
   // I think glfwGetWindowSize(window, width, height) works fine.

   return window;
}

void GameGui_GLFW::bindEventHandlers() {
   glfwSetMouseButtonCallback(window, glfw_mouse_button_callback);
   glfwSetKeyCallback(window, glfw_key_callback);
   glfwSetWindowSizeCallback(window, glfw_window_size_callback);
}

void GameGui_GLFW::destroyWindow() {
   // TODO: This function can throw some errors. They should be handled
   glfwDestroyWindow(window);
}

void GameGui_GLFW::processEvents() {
   glfwPollEvents();

   if (glfwWindowShouldClose(window)) {
      UIEvent e;
      e.event.type = UI_EVENT_QUIT;

      s_events.push(e);
   }
}

int GameGui_GLFW::pollEvent(UIEvent* event) {
   if (s_events.empty()) {
      return 0;
   }

   *event = s_events.front();
   s_events.pop();

   return 1;
}

void GameGui_GLFW::refreshWindowSize() {
   // glfwGetFramebufferSize(window, width, height) segfaults on OSX, check other platforms
   // I think glfwGetWindowSize(window, width, height) works fine.
   glfwGetWindowSize(window, &windowWidth, &windowHeight);
}

int GameGui_GLFW::getWindowWidth() {
   return windowWidth;
}

int GameGui_GLFW::getWindowHeight() {
   return windowHeight;
}

#ifdef GAMEGUI_INCLUDE_VULKAN

bool GameGui_GLFW::createVulkanSurface(VkInstance instance, VkSurfaceKHR* surface) {
   return glfwCreateWindowSurface(instance, window, nullptr, surface) == VK_SUCCESS ?
      RTWO_SUCCESS : RTWO_ERROR;
}

vector<const char*> GameGui_GLFW::getRequiredExtensions() {
   uint32_t glfwExtensionCount = 0;
   const char** glfwExtensions;

   glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

   vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);

   return extensions;
}

#endif

void glfw_error_callback(int error, const char* description) {
   GameGui_GLFW::s_errorMessage = description;
}

void glfw_mouse_button_callback(GLFWwindow* window, int button, int action, int mods) {
   double x, y;
   glfwGetCursorPos(window, &x, &y);

   /*
   MouseEvent e { button, action, (int)x, (int)y };

   mouseEvents.push(e);
   */
}

void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
   UIEvent e;
   e.event.type = action == GLFW_RELEASE ? UI_EVENT_KEYUP : UI_EVENT_KEYDOWN;
   e.event.key.keycode = key;

   GameGui_GLFW::s_events.push(e);
}

void glfw_window_size_callback(GLFWwindow* window, int width, int height) {
   UIEvent e;
   e.event.type = UI_EVENT_WINDOWRESIZE;
   e.event.windowResize.width = width;
   e.event.windowResize.height = height;

   GameGui_GLFW::s_events.push(e);
}
