#pragma once
#include "host.hpp"
#include <GLFW/glfw3.h>
#include "a83/timer.hpp"
#include "a83/string-util.hpp"
#include "a83/error.hpp"
#include "common/sync.hpp"
namespace HostGLFW {
  Input input_copy = {}; ///< копия ввода для потока
  GLFWwindow* window = nullptr;
  int WndPosX = 480;
  int WndPosY = 40;
//! колбэк для ошибок нужен для GLFW
void error_callback(int error, const char* description)
  { a83::error("GLFW error " + a83::n2s(error) + ": " + description); }
//! обновляет статус нажатых клавишь
void update_input() {
// обработка нажатых и зажатых клавишь:
  int ret;
  #define KEY_PnR(GLFW_K, HPW_K) \
  ret = glfwGetKey(window, GLFW_K); \
  if (ret == GLFW_PRESS || ret == GLFW_REPEAT) input_copy.set(HPW_K);
  KEY_PnR(GLFW_KEY_UP,      KEY_UP);
  KEY_PnR(GLFW_KEY_DOWN,    KEY_DOWN);
  KEY_PnR(GLFW_KEY_LEFT,    KEY_LEFT);
  KEY_PnR(GLFW_KEY_RIGHT,   KEY_RIGHT);
  KEY_PnR(GLFW_KEY_W,       KEY_SHOOT);
  KEY_PnR(GLFW_KEY_S,       KEY_SHOOT_BACK);
  KEY_PnR(GLFW_KEY_A,       KEY_SHIELD);
  KEY_PnR(GLFW_KEY_D,       KEY_SHOOT_SIDE);
  KEY_PnR(GLFW_KEY_Q,       KEY_RADAR);
  KEY_PnR(GLFW_KEY_E,       KEY_RADAR_MODE);
  KEY_PnR(GLFW_KEY_LEFT_ALT, KEY_FOCUS);
  KEY_PnR(GLFW_KEY_ESCAPE,  KEY_ESCAPE);
  KEY_PnR(GLFW_KEY_ENTER,   KEY_ENABLE);
  KEY_PnR(GLFW_KEY_F1,      KEY_FPS);
  KEY_PnR(GLFW_KEY_F2,      KEY_SCREENSHOT);
  KEY_PnR(GLFW_KEY_F11,     KEY_FULSCRN);
  #ifdef DEBUG
  KEY_PnR(GLFW_KEY_F5,      KEY_RESET);
  KEY_PnR(GLFW_KEY_F3,      KEY_DEBUG);
  #endif
  // альтернативное сочетание клавишь для полного экрана:
  if ((glfwGetKey(window, GLFW_KEY_LEFT_ALT) == GLFW_PRESS ||
    glfwGetKey(window, GLFW_KEY_LEFT_ALT) == GLFW_REPEAT) &&
    (glfwGetKey(window, GLFW_KEY_ENTER) == GLFW_PRESS ||
    glfwGetKey(window, GLFW_KEY_ENTER) == GLFW_REPEAT))
    input_copy.set(KEY_FULSCRN);
  #undef KEY_PnR
  #undef PRESS
}
//! вывод на весь экран
void set_fullscreen(GLFWwindow* window, bool enable) {
  using namespace Host;
  if (enable) {
    GLFWmonitor* monitor = glfwGetPrimaryMonitor();
    const GLFWvidmode* mode = glfwGetVideoMode(monitor);
    glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
    glfwSwapInterval((Game::status & GF_VSYNC) ? 1 : 0);
    ReshapeOpenGL(mode->width, mode->height, Game::wnd);
  } else { // переключение обратно в окно
    auto wx = Game::X * 2;
    auto wy = Game::Y * 2;
    glfwSetWindowMonitor(window, nullptr, WndPosX, WndPosY, wx, wy, GLFW_DONT_CARE);
    glfwSwapInterval((Game::status & GF_VSYNC) ? 1 : 0);
    ReshapeOpenGL(wx, wy, Game::wnd);
  }
}
//! обработка всех платформенных вызовов
void process_events(GLFWwindow* window) {
  using namespace Host;
  while ( !Game::events.empty()) {
    cauto EV = Game::events.top();
    switch (EV.type) {
      case event_e::NONE: break;
      case event_e::SET_FULLSCREEN: set_fullscreen(window, EV.a); break;
      case event_e::SET_VSYNC: glfwSwapInterval(EV.a ? 1 : 0); break;
      case event_e::SET_RESIZE_MODE: {
        Game::wnd.mode = ResizeMode(EV.a % int(ResizeMode::MAX));
        int wx, wy;
        glfwGetWindowSize(window, &wx, &wy);
        ReshapeOpenGL(wx, wy, Game::wnd);
        break;
      }
      default: break;
    } // switch
    Game::events.pop();
  } // while event
  glfwPollEvents(); // GLFW events
}
void reshape_callback(GLFWwindow* /*window*/, int w, int h)
  { Host::ReshapeOpenGL(w, h, Game::wnd); }
void init(int argc, char** argv) {
  using namespace Host;
  glfwSetErrorCallback(error_callback);
  IFERROR( !glfwInit(), "glfw init error");
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  display_ctx.x = Game::X * 2;
  display_ctx.y = Game::Y * 2;
  Game::wnd = display_ctx;
  window = glfwCreateWindow(Game::wnd.x, Game::wnd.y,
    Game::GAME_NAME, nullptr, nullptr);
  if ( !window) {
    glfwTerminate();
    a83::error("bad glfw window init");
  }
  glfwSetWindowPos(window, WndPosX, WndPosY);
  glfwMakeContextCurrent(window);
  Host::post_init(argc, argv);
  glfwSetWindowSizeCallback(window, reshape_callback);
  glfwSwapInterval(0);
}
void loop() {
  using namespace Host;
  using namespace std;
  using namespace std::chrono;
  using namespace a83;
  while (true) {
    cauto st_time = glfwGetTime();
    if (Game::gameover || glfwWindowShouldClose(window))
      break;
    Game::redraw();
    DrawOpenGL(Game::wnd);
    glfwSwapBuffers(window);
    process_events(window);
    update_input();
    Game::input_prev = Game::input;
    Game::input = input_copy;
    input_copy.clear();
    Game::update(Game::cur_dt);
    Game::host_loop_mcs = (glfwGetTime() - st_time) * 1000000.0;
    // вычислить dt. рваные движения заменить на фризы
    Game::cur_dt = std::min(glfwGetTime() - st_time,
      1.0 / double(Game::BAD_FPS));
  }
  Game::gameover = true;
}
void free() {
  Host::free();
  if (window) {
    glfwDestroyWindow(window);
    window = nullptr;
  }
  glfwTerminate();
}
} // HostGLFW ns