#pragma once

#include <imgui.h>
#include <implot.h>

#include <algorithm>
#include <chrono>
#include <cstddef>
#include <vector>

#include "debug/ComponentGui.hpp"
#include "network/EntityInterpolation.hpp"
#include "world/Transform.hpp"


template<>
class tw::dbg::ComponentGui<tw::Transform> {
private:
    using Clock = std::chrono::high_resolution_clock;

    /** Ten seconds of frames at sixty a second. */
    static constexpr size_t CAPACITY = 600;

    /** How much of the trace the plot shows. */
    static constexpr float WINDOW_IN_SECONDS = 10.0f;

    inline static const Transform*   m_subject    = nullptr;
    inline static int                m_last_frame = -1;
    inline static Clock::time_point  m_started_at {};
    inline static size_t             m_head       = 0;
    inline static size_t             m_count      = 0;
    inline static std::vector<float> m_times;
    inline static std::vector<float> m_positions;

    void restart(const Transform* instance) {
        m_subject    = instance;
        m_last_frame = -1;
        m_started_at = Clock::now();
        m_head       = 0;
        m_count      = 0;

        m_times.resize(CAPACITY);
        m_positions.resize(CAPACITY);
    }

    /** Keeps one sample per frame, so drawing twice does not double up. */
    void sample(float time, float position) {
        const int frame = ImGui::GetFrameCount();
        if(m_last_frame == frame) {
            return;
        }
        m_last_frame = frame;

        m_times[m_head]     = time;
        m_positions[m_head] = position;

        m_head  = (m_head + 1) % CAPACITY;
        m_count = std::min(m_count + 1, CAPACITY);
    }

public:
    void draw(Transform* instance) {
        ImGui::SeparatorText("Drawn Position");

        static int axis = 0;
        ImGui::Combo("Axis", &axis, "X\0Y\0Z\0");

        if(m_subject != instance) {
            restart(instance);
        }

        const float time     = std::chrono::duration<float>(Clock::now() - m_started_at).count();
        const float position = instance->position()[axis];

        sample(time, position);

        ImGui::Text("%.3f", position);

        if(ImPlot::BeginPlot("Position", ImVec2(-1.0f, 180.0f))) {
            ImPlot::SetupAxes("seconds", "position", ImPlotAxisFlags_None, ImPlotAxisFlags_AutoFit);
            ImPlot::SetupAxisLimits(ImAxis_X1,
                time - WINDOW_IN_SECONDS, time, ImGuiCond_Always);

            // The trace wraps around, so it is read from wherever the oldest
            // sample ended up.
            const int offset = m_count == CAPACITY ? (int)m_head : 0;
            ImPlot::PlotLine("Drawn", m_times.data(), m_positions.data(),
                (int)m_count, ImPlotLineFlags_None, offset);

            ImPlot::EndPlot();
        }
    }
};

/**
 * Says whether the position above stands still because nothing new arrives for
 * the entity, or because what arrives is the same position over and over.
 */
template<>
class tw::dbg::ComponentGui<tw::net::EntityPositionInterpolation> {
private:
    using Clock = std::chrono::high_resolution_clock;

    static int64_t millis_since(Clock::time_point point) {
        return std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - point).count();
    }

public:
    void draw(net::EntityPositionInterpolation* instance) {
        ImGui::SeparatorText("Received Positions");

        const size_t newest = instance->values().size() - 1;

        ImGui::Text("Newest %.3f %.3f %.3f, %ld ms ago",
            instance->values()[newest].x,
            instance->values()[newest].y,
            instance->values()[newest].z,
            millis_since(instance->times()[newest]));

        ImGui::Text("Oldest %.3f %.3f %.3f, %ld ms ago",
            instance->values()[0].x,
            instance->values()[0].y,
            instance->values()[0].z,
            millis_since(instance->times()[0]));
    }
};
