//
// Created by m on 12/3/21.
//

#include "engine.hpp"
#include <cstdio>
#include <chrono>
#include <thread>
#include <exception>

using namespace std::chrono_literals;
void Engine::start_loop() {
    const auto one_second_milli = std::chrono::milliseconds(1s);
    if(game == nullptr) {
        throw MissingGameException();
    }
    if(renderer == nullptr) {
        throw MissingRendererException();
    }
    auto render_params = RendererParams{this->config.window_title, this->config.window_width, this->config.window_height, this->config.default_font};
    renderer->initialize(render_params);
    renderer->flush();
    auto game_params = GameInitArgs{this->config.target_fps, this->config.window_width, this->config.window_height};
    game->initialize(game_params);
    auto duration_per_frame = one_second_milli / this->config.target_fps;
    auto last_frame = std::chrono::high_resolution_clock::now();
    while(true) {
        auto starttime = std::chrono::high_resolution_clock::now();
        auto input = this->input_processor->process_input();
        bool end = game->update(input);
        if(end) {
            break;
        }
        game->render(renderer.get());
        auto endtime = std::chrono::high_resolution_clock::now();
        last_frame = render_fps(endtime, last_frame);
        renderer->flush(); // not technically correct to do this here, but is done so we can put the fps over the display

        std::this_thread::sleep_for(duration_per_frame);
    }
}

Engine::Engine(std::unique_ptr<Game> game, std::unique_ptr<Renderer> renderer,
               std::unique_ptr<InputProcessor> input_processor) {
    this->renderer = std::move(renderer);
    this->game = std::move(game);
    this->input_processor = std::move(input_processor);
    EngineConfig new_config = EngineConfig();
    load_config("engine.toml", [&new_config](const toml::table& table) {
        new_config.show_fps = table["overlay"]["show_fps"].value_or<bool>(DEFAULT_SHOW_FPS);
        new_config.window_width = table["display"]["window_width"].value_or(DEFAULT_WINDOW_WIDTH);
        new_config.window_height = table["display"]["window_height"].value_or(DEFAULT_WINDOW_HEIGHT);
        new_config.target_fps = table["display"]["target_fps"].value_or(DEFAULT_TARGET_FPS);
        new_config.window_title = table["display"]["window_title"].value_or(DEFAULT_WINDOW_TITLE);
        auto default_font_path = fmt::format("fonts/{}.ttf", table["font"]["default_font_name"].value_or(DEFAULT_FONT_NAME));
        auto default_font_size = table["font"]["default_font_size"].value_or(DEFAULT_FONT_SIZE);
        new_config.default_font = TextRenderDetails{default_font_path, default_font_size, COLOR_WHITE};
    });
    this->config = new_config;
}

std::chrono::time_point<std::chrono::system_clock>
Engine::render_fps(std::chrono::time_point<std::chrono::system_clock> end,
                   std::chrono::time_point<std::chrono::system_clock> lastframe) {
    double frames = 1;
    std::chrono::duration<double> dur = end - lastframe;
    if(this->config.show_fps) {
        //printf("fps: %f\n", frames/dur.count());
        this->renderer->draw_text(std::nullopt, fmt::format("{:.2f}", frames/dur.count()), 0, 0);
    }
    return end;
}

void Engine::load_config(const std::string &path, const std::function<void (toml::table)>& f) {
    std::string full_path = fmt::format("{}/{}", CONFIG_DIR, path);
    auto table = toml::parse_file(full_path);
    f(table);
}


Engine::~Engine() = default;

const char *MissingGameException::what() const noexcept {
    return MISSING_GAME_ERROR;
}

const char *MissingRendererException::what() const noexcept {
    return MISSING_RENDERER_ERROR;
}
