#pragma once
#include "a83/path.hpp"
#include "a83/timer.hpp"
#include "scene/scene-loading.hpp"
#include "scene/scene-graphic.hpp"
#include "scene/scene-select.hpp"
#include "scene/scene-game.hpp"
#include "scene/scene-options.hpp"
#include "scene/scene-pause.hpp"
#include "scene/scene-gameover.hpp"
#include "scene/scene-menu.hpp"
#include "scene/scene-debuger.hpp"
#include "game-archive.hpp"
#include "font/font-unifont.hpp"
//! контролирует обновление игры и графику игры
namespace Game {
  Scene* pScene = nullptr; ///< текущая игровая сцена
  int frameCount = 0; ///< количество отображённых кадров
static void load_font();
//! прогрузка контента
void game_load() {
  Game::config.load(current_dir + "config.yml");
  archive.open(current_dir + sconv<std::string>(Game::config.path.data));
  load_font();
}
void update_input() {
  if (Game::menu_press(KEY_FPS)) { // print FPS
    Game::status ^= GF_PRINT_FPS;
    Game::fps_min = num_max(float);
    Game::fps_max = 0;
  }
  if (Game::menu_press(KEY_FULSCRN)) {
    Game::status ^= GF_FULLSCREEN;
    events.push(Event{event_e::SET_FULLSCREEN,
      int(Game::status & GF_FULLSCREEN)});
  }
  if (Game::menu_press(KEY_SCREENSHOT)) {
    // сохранение скрина по дате и времени
    auto t = std::time(nullptr);
    auto tm = *std::localtime(&t);
    std::ostringstream oss;
    oss << std::put_time(&tm, "%d-%m-%Y %H-%M-%S");
    auto timeStr = oss.str();
    cauto scrn_dir = current_dir
      + sconv<std::string>(Game::config.path.screenshots);
    a83::make_dir_if_not_exist(scrn_dir);
    Game::screen.save(scrn_dir + '/'
      + timeStr + '-' + a83::n2s(a83::irand() % 100'000) + ".png");
  }
}
//! считает и выводит FPS
void fps_calc() {
  using namespace std::literals::chrono_literals;
  static a83::TimePoint st = {};
  cauto est_time = a83::getTime() - st; // 0.0..1.0 sec for calc fps
  if (est_time > 1s) {
    st = a83::getTime();
    auto cur_fps = frameCount / a83::time2sec(est_time);
    Game::fps = cur_fps;
    Game::fps_min = std::min(Game::fps_min, cur_fps);
    Game::fps_max = std::max(Game::fps_max, cur_fps);
    frameCount = 0;
  }
} // fps_calc
//! определяет лагает ли игра
void check_VLAGS() {
  //! только когда два кадра подряд лагают - это лаг, иначе это статтеринг
  static bool prev_frame_lag = false;
  const bool is_lag = Game::host_loop_mcs >= Game::bad_frame_mcs;
  set_flag_if(prev_frame_lag && is_lag, Game::status, GF_VLAGS);
  prev_frame_lag = is_lag;
}
//! получить директорию запуска
void getCurDir(const char* str) {
  IFERROR( !str, "current dir not finded - argv error");
  const a83::Str _str(str);
  // если без разделителей, то аргумент содержит имя проги, его игнорить
  if (_str.find_first_of(SEPARATOR) != a83::npos) {
    current_dir = a83::get_filedir(_str);
    a83::convSep(current_dir);
  }
  if (current_dir.empty())
    current_dir = ".";
  current_dir += SEPARATOR;
  a83::log("Game launch dir: \"" + current_dir + "\"");
}
/** вытаскивание новых сцен, если их нет и сейчас
сцена не активна, то выход из игры */
void update_scene_switch() {
  auto state = scene_st::NONE;
  if ( !scene_state.empty()) {
    state = scene_state.top();
    scene_state.pop();
  } else {
    if ( !pScene)
      Game::gameover = true;
  }
  // переключение сцен:
  switch (state) {
    default:
    case scene_st::NONE: break;
    // при возврате текущая сцена удаляется
    case scene_st::PREV: {
      if (pScene) {
        auto tmp = pScene->pPrev;
        delete pScene;
        pScene = tmp;
      } else
        Game::gameover = true;
      break;
    }
    case scene_st::PREVx2: {
      for (int i = 0; i < 2; ++i)
        if (pScene) {
          auto tmp = pScene->pPrev;
          delete pScene;
          pScene = tmp;
        } else
          Game::gameover = true;
      break;
    }
    case scene_st::END: {
      // рекурсивное закрытие всех сцен
      while (pScene) {
        auto tmp = pScene->pPrev;
        delete pScene;
        pScene = tmp;
      }
      gameover = true;
      break;
    }
    case scene_st::LOADING: pScene = new Scene_loading(pScene); break;
    case scene_st::GAME: pScene = new Scene_game(pScene); break;
    case scene_st::SELECT: pScene = new Scene_select(pScene); break;
    case scene_st::PAUSE: pScene = new Scene_pause(pScene); break;
    case scene_st::OPTIONS: pScene = new Scene_options(pScene); break;
    // case scene_st::INPUT: __ break; TODO
    // case scene_st::SOUND: __ break; TODO
    case scene_st::GRAPHIC: pScene = new Scene_graphic(pScene); break;
    case scene_st::MENU: pScene = new Scene_menu(pScene); break;
    case scene_st::GAME_OVER: pScene = new Scene_gameover(pScene); break;
    #ifdef DEBUG
    case scene_st::DEBUGER: pScene = new Scene_debuger(pScene); break;
    #endif
  } // switch state
} // update_switch_state
void init(int /*argc*/, char** argv) {
  #ifdef STATIC_SEED
    a83::set_random_seed(1);
  #else
    a83::set_random_seed(uint(time(0)));
  #endif
  Game::cur_dt = 1.0f;
  getCurDir(argv[0]);
  screen.init(Game::X, Game::Y);
  Game::gameover = false;
  game_load();
#ifdef RELEASE // Normal game menu
  Game::scene_state.push(scene_st::MENU); // первая сцена при старте
#else // TODO del:
  Game::scene_state.push(scene_st::GAME);
  Game::scene_state.push(scene_st::LOADING);
#endif
  events.push(Event{event_e::SET_FULLSCREEN, int(Game::status & GF_FULLSCREEN)});
  events.push(Event{event_e::SET_VSYNC, int(Game::status & GF_VSYNC)});
  events.push(Event{event_e::SET_RESIZE_MODE, int(wnd.mode)});
}
void update(float dt) {
  update_input();
  if (pScene)
    pScene->update(dt);
  update_scene_switch();
  game_time += dt;
}
void redraw() {
  using namespace a83;
  using namespace std::string_literals;
  if (pScene) pScene->draw();
  if (Game::status & GF_PRINT_FPS) {
    fps_calc();
    font->print(screen, 5, 5,  U"FPS Cur = " + n2s<utf32>(Game::fps, FPS_PREC), true);
    font->print(screen, 5, 15, U"FPS Max = " + n2s<utf32>(Game::fps_max, FPS_PREC), true);
    font->print(screen, 5, 25, U"FPS Min = " + n2s<utf32>(Game::fps_min, FPS_PREC), true);
    font->print(screen, 5, 35, U"Loop Mcs = " + n2s<utf32>(Game::host_loop_mcs), true);
    font->print(screen, 5, 45, U"dt = " + n2s<utf32>(Game::cur_dt, get_max_prec(float)), true);
    font->print(screen, 5, 55, U"VLags = " + yn2s<utf32>(Game::status & GF_VLAGS), true);
    #ifdef DEBUG
    font->print(screen, 5, 65, U"checkmoves = " + n2s<utf32>(debuger::check_moves), true);
    font->print(screen, 5, 75, U"update physics = " + n2s<utf32>(debuger::update_physics), true);
    font->print(screen, 5, 85, U"check hits = " + n2s<utf32>(debuger::check_hits), true);
    font->print(screen, 5, 95, U"collide checks = " + n2s<utf32>(debuger::collide_checks), true);
    #endif
  }
  #ifdef DEBUG
  debuger::check_moves = debuger::update_physics
    = debuger::check_hits = debuger::collide_checks = 0;
  #endif
  ++frameCount;
  ++timer_anim;
  check_VLAGS();
} // redraw
void free() {
  // сохранить изменения в настройках
  Game::config.save(Game::current_dir + "config.yml");
  screen.free();
  // рекурсивное закрытие всех сцен
  while (pScene) {
    auto tmp = pScene->pPrev;
    delete pScene;
    pScene = tmp;
  }
  // чистка стека сцен
  while ( !scene_state.empty())
    scene_state.pop();
  archive.close();
  zdelete(font);
} // free
//! грузит новый шрифт из Game::config.path.font
static void load_font() {
  auto data_file = Game::archive.get_file(
    sconv<a83::Str>(Game::config.path.font));
  YAML::Node file = YAML::Load(std::string(data_file.data,
    data_file.data + data_file.size));
  auto node_font = file["font"];
  IFERROR(!node_font, "load_font: !node_font");
  cauto font_fname = node_font["file"].as<std::string>();
  cauto font_width = node_font["width"].as<int>();
  cauto font_height = node_font["height"].as<int>();
  bool mono = node_font["mono"].as<bool>();
  a83::log(
    "font info:"s +
    "\n  file name: " + font_fname +
    "\n  width x height: " + a83::n2s(font_width) +
    " x " + a83::n2s(font_height) +
    "\n  monochrome " + yn2s<a83::Str>(mono)
  );
  // парс диапазонов блоков
  auto range_str = split<std::string, std::string::value_type>
    (file["unicode blocks"].as<std::string>(), ',');
  Unifont::range_t range;
  for (auto& str: range_str) {
    remove_all<std::string, std::string::value_type>(str, ' ');
    auto values = split<std::string, std::string::value_type>(str, '-');
    if (values.empty())
      continue;
    uint a, b;
    a = s2n<std::string, uint>(values[0], num_base::hex);
    b = (values.size() > 1)
      ? s2n<std::string, uint>(values[1], num_base::hex) : a;
    range.push_back(std::make_pair(a, b));
  }
  font = new Unifont(Game::archive, font_fname,
    font_width, font_height, mono, range);
} // load_font
} // Game ns