#include "runtime.hpp"

#include "app/ClientArgs.hpp"
#include "Address.hpp"
#include "SDLWindow.h"
#include "debug/tools/PerformanceStatsGui.hpp"
#include "entt/entity/fwd.hpp"
#include "io/InputState.hpp"
#include "draw/MeshData.hpp"

#include "imgui.h"
#include "imgui_impl_vulkan.h"
#include "imgui_impl_sdl2.h"

#include "implot.h"
#include "implot_internal.h"
#include "world/Transform.hpp"

#include "spdlog/spdlog.h"

#include <SDL_events.h>
#include <glm/glm.hpp>
#include <tracy/Tracy.hpp>
#include <memory>

namespace tw {

std::unique_ptr<lft::win::Window> create_window(const std::string& name, VkExtent2D extent) {
    return std::make_unique<lft::win::SDLWindow>(name, (VkRect2D){
            0, 0,
            extent.width, extent.height
    });
}

/**
 * The address to connect to without asking, if one was given on the command
 * line. A malformed one is reported and dropped, leaving the user in the lobby.
 */
static std::optional<net::Address> auto_connect_address(int argc, char** argv) {
    auto argument = app::server_arg(argc, argv);
    if(!argument) {
        return {};
    }

    auto parsed = app::parse_address(*argument);
    if(!parsed) {
        spdlog::error("Failed to parse server address: {}", parsed.error());
        return {};
    }

    return *parsed;
}

Runtime::Runtime(int argc, char** argv) :
    m_files(argv, argc),
    m_window(create_window("towards", {1920, 1200})),
    m_world{},
    m_physics_world(&m_world),
    m_world_renderer("towards", m_window.get(), &m_world, &m_files),
    m_input_manager(m_window.get()),
    m_network_metrics(),
    m_lockstep(60),
    m_debug_ui(),
    m_perf_stats(m_lockstep),
    m_state(std::in_place_type<app::LobbyState>, auto_connect_address(argc, argv))
{
    m_debug_ui.windows().add(&m_perf_stats);
}

Runtime::~Runtime() {
    m_debug_ui.windows().remove(&m_perf_stats);
}

app::GameContext Runtime::context() {
    return app::GameContext{
        &m_world,
        &m_physics_world,
        &m_world_renderer,
        &m_input_manager,
        &m_network_metrics,
        &m_debug_ui.windows(),
    };
}

void Runtime::update_state(double delta_time) {
    if(auto* lobby = std::get_if<app::LobbyState>(&m_state)) {
        lobby->update(delta_time);

        if(auto result = lobby->take_result()) {
            m_state.emplace<app::GameState>(context(), std::move(result->connection));
        }
        return;
    }

    std::get<app::GameState>(m_state).update(delta_time);
}

void Runtime::run() {
    ImPlot::CreateContext();
    m_is_running = true;

    while(is_running()) {
        if(m_lockstep.wait_for_next_step()) {
            continue;
        }

        ImGui_ImplVulkan_NewFrame();
        ImGui_ImplSDL2_NewFrame();
        ImGui::NewFrame();

        m_input_manager.update();
        if(m_input_manager.is_quit()) {
            m_is_running = false;
        }

        // Anything that docks needs the dock space to already be there, so the
        // host goes up before the state draws.
        m_debug_ui.draw_dockspace();

        update_state(m_lockstep.delta_time());

        m_debug_ui.draw_windows();

        m_world_renderer.render();
        FrameMark;
    }
}

}
