#include "glfw.hpp"

#include "vertex_engine/event/type.hpp"
#include "vertex_engine/event/connector.hpp"

namespace VE
{
  glfw::glfw()
  {
    if (!glfwInit())  
    {
      hack::error()("Not glfw window init");
      std::terminate();
    }
  }

  glfw::~glfw()
  {
    hack::warn()("Destroy glfw window:", m_win_data.m_name);
    glfwDestroyWindow(m_win);
    glfwTerminate();
  }

  void glfw::init(std::string win_name)
  {
    m_win_data.m_name = win_name;

    set_hint();
    set_window();
    set_graphic_context();

    set_pointer();
    set_key_callback();
    set_mouse_callback();
    set_window_callback();
    set_opengl();
  }

  void glfw::set_hint()
  {
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);     
    glfwWindowHint(GLFW_SAMPLES, 4);
    hack::log()("Set glfw window hint");
  }

  void glfw::set_window()
  {
    // два последних nullptr
    // первый - устанавливает монитор на котром будет открытие(основной, дополнительнй)
    // второй - делиться ли ресурсами с кем-то
    m_win = glfwCreateWindow(
      glfwGetVideoMode(glfwGetPrimaryMonitor())->width,
      glfwGetVideoMode(glfwGetPrimaryMonitor())->height, 
      m_win_data.m_name.c_str(),
      nullptr, nullptr
    );

    if(m_win == NULL) 
    {
      hack::error()("Failed to create GLFW window");
      glfwTerminate();
      std::terminate();
    }

    glfwGetWindowSize(m_win, &m_win_data.m_width, &m_win_data.m_height);

    hack::log()("Created glfw window:", m_win_data.m_name);
    hack::log()(" width =", m_win_data.m_width);
    hack::log()(" height =", m_win_data.m_height);
  }

  void glfw::set_graphic_context()
  {
    m_context = std::make_unique<context>(m_win);
    hack::log()("Set context");
  }

  void glfw::set_pointer()
  {
    glfwSetWindowUserPointer(m_win, &m_win_data);
  }

  GLFWwindow* glfw::get_win() const
  { 
    return m_win; 
  }

  int glfw::width() const
  { 
    return m_win_data.m_width; 
  }

  int glfw::height() const
  { 
    return m_win_data.m_height; 
  }

  void glfw::update()
  {
    glfwPollEvents();
    m_context->swap_buffers();
  }

  void glfw::clear() const
  {
    glClearColor(m_bgcolor.r, m_bgcolor.g, m_bgcolor.b, m_bgcolor.a);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  }

  void glfw::set_key_callback()
  {
    glfwSetKeyCallback(m_win, [](GLFWwindow* w, int key, int scancode, int action, int mods)
    {
      // HERE
      // тут одноврекменно срабатывает только один сигнал
      // т.е. если нажада клавиша и удерживается сигнал о повторе идет
      // но если нажать одновременно еще, то сигнал о той нажатой пропадает
      // нужносамому реализовывать функцию повтора а не полагаться на внутреннюю
      // реализацию glfw
      switch (action)
      {
        case GLFW_PRESS:
        {
          event e { event_type::KEY_PRESSED, key };
          connector::EMIT(e);
          break;
        }
        case GLFW_RELEASE:
        {
          event e { event_type::KEY_RELEASED, key };
          connector::EMIT(e);
          break;
        }
				case GLFW_REPEAT:
				{
          event e { event_type::KEY_REPEATE, key };
          connector::EMIT(e);
          break;
				}
      }

    });
  }

  void glfw::set_mouse_callback()
  {
		glfwSetMouseButtonCallback(m_win, [](GLFWwindow* w, int button, int action, int mods)
		{
			switch (action)
			{
				case GLFW_PRESS:
				{
          static auto time = std::chrono::high_resolution_clock::now();
          auto local = std::chrono::high_resolution_clock::now();
          std::chrono::duration<double, std::milli> duration = local - time;

          if (duration.count() > 10 && duration.count() < 200)
          {
            event e { event_type::MOUSE_BUTTON_DOUBLE_PRESSED, button };
            connector::EMIT(e);
          }
          else
          {
            event e { event_type::MOUSE_BUTTON_PRESSED, button };
            connector::EMIT(e);
          }

          time = local;
					break;
				}
				case GLFW_RELEASE:
				{
          event e { event_type::MOUSE_BUTTON_RELEASED, button };
          connector::EMIT(e);
					break;
				}
			}
		});

		glfwSetScrollCallback(m_win, [](GLFWwindow* w, double xOffset, double yOffset)
		{
      try
      {
        auto data = std::pair<float, float>{ static_cast<float>(xOffset), static_cast<float>(yOffset) };
        event e { event_type::MOUSE_SCROLL, data };
        connector::EMIT(e);
      }
      catch(std::exception& e)
      {
        hack::log()("VE_ERROR:", e.what());
      }
		});

		glfwSetCursorPosCallback(m_win, [](GLFWwindow* w, double xPos, double yPos)
		{
      try
      {
        auto data = std::pair<float, float>{ static_cast<float>(xPos), static_cast<float>(yPos) };
        event e { event_type::MOUSE_CURSOR_POSITION, data };
        connector::EMIT(e);
      }
      catch(std::exception& e)
      {
        hack::log()("VE_ERROR:", e.what());
      }
		});
  }

  void glfw::set_window_callback()
  {
    glfwSetWindowSizeLimits(m_win, m_win_data.m_width, m_win_data.m_height, GLFW_DONT_CARE, GLFW_DONT_CARE);    

    glfwSetWindowSizeCallback(m_win, [](GLFWwindow* w, int width, int height)
    {
      auto d = static_cast<win_data*>(glfwGetWindowUserPointer(w));
      d->m_width = width;
      d->m_height = height;

      try
      {
        auto data = std::pair<float, float>{ static_cast<float>(width), static_cast<float>(height) };
        event e { event_type::WINDOW_RESIZE, data };
        connector::EMIT(e);
      }
      catch(std::exception& e)
      {
        hack::log()("VE_ERROR:", e.what());
      }
    });

    glfwSetWindowCloseCallback(m_win, [](GLFWwindow* w)
    {
      try
      {
        event e { event_type::WINDOW_CLOSE, nullptr };
        connector::EMIT(e);
      }
      catch(std::exception& e)
      {
        hack::log()("VE_ERROR:", e.what());
      }
    });

    glfwSetWindowFocusCallback(m_win, [](GLFWwindow* w, int focused)
    {
      try
      {
        event e { event_type::WINDOW_FOCUS, focused };
        connector::EMIT(e);
      }
      catch(std::exception& e)
      {
        hack::log()("VE_ERROR:", e.what());
      }
    });
  }

  void glfw::set_opengl()
  {
    glEnable(GL_DEPTH_TEST);
    glClearColor(0.07f, 0.13f, 0.17f, 1.0f);
    glViewport(0, 0, m_win_data.m_width, m_win_data.m_height);
  }
}
