#pragma once

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

#include "metrics/MetricSeries.hpp"

#include <algorithm>
#include <chrono>
#include <string>
#include <vector>

namespace tw::dbg::tools {

/**
 * Plots one series against the seconds behind now, ending at the last second
 * that has fully elapsed.
 *
 * The line follows whichever statistic the series is read with. Reading an
 * average also shades the quietest and busiest value of each second behind it;
 * a total has no such range to show, since its buckets already hold every value
 * of that second added together.
 */
class MetricWidget {
public:
    using Series = metrics::MetricSeries<std::chrono::seconds>;

private:
    std::string                m_name;
    std::string                m_unit;
    const Series*              m_series;
    metrics::MetricField       m_field;

    /**
     * The second in progress is left out: it only holds the part of itself
     * that has elapsed, so drawing it makes the newest point drop and climb
     * back once a second.
     */
    static constexpr size_t SKIP_IN_PROGRESS = 1;

    std::vector<double> m_ages;
    std::vector<double> m_values;
    std::vector<double> m_lows;
    std::vector<double> m_highs;

    bool has_range() const {
        return m_field == metrics::MetricField::Avg;
    }

    /** Describes what is drawn, so the numbers always match the line. */
    void draw_summary() const {
        if(m_values.empty()) {
            ImGui::TextUnformatted("no samples yet");
            return;
        }

        auto [low, high] = std::minmax_element(m_values.begin(), m_values.end());

        double total = 0.0;
        for(double value : m_values) {
            total += value;
        }

        ImGui::Text("min %.1f %s   avg %.1f %s   max %.1f %s",
            *low, m_unit.c_str(),
            total / (double)m_values.size(), m_unit.c_str(),
            *high, m_unit.c_str());
    }

public:
    MetricWidget(std::string name, std::string unit, const Series& series, metrics::MetricField field) :
        m_name(std::move(name)),
        m_unit(std::move(unit)),
        m_series(&series),
        m_field(field)
    { }

    /** Draws the last `history_in_seconds` seconds of the series. */
    void draw(size_t history_in_seconds) {
        ImGui::PushID(m_name.c_str());

        m_series->linearize(m_ages, m_values, m_field, history_in_seconds, SKIP_IN_PROGRESS);

        if(has_range()) {
            m_series->linearize(m_ages, m_lows, metrics::MetricField::Min,
                history_in_seconds, SKIP_IN_PROGRESS);
            m_series->linearize(m_ages, m_highs, metrics::MetricField::Max,
                history_in_seconds, SKIP_IN_PROGRESS);
        }

        draw_summary();

        if(ImPlot::BeginPlot(m_name.c_str(), ImVec2(-1.0f, 150.0f))) {
            ImPlot::SetupAxes("seconds ago", m_unit.c_str(),
                ImPlotAxisFlags_None, ImPlotAxisFlags_AutoFit);
            ImPlot::SetupAxisLimits(ImAxis_X1, -(double)history_in_seconds, 0.0, ImGuiCond_Always);

            const int count = (int)m_values.size();

            if(has_range() && count > 0) {
                ImPlot::PlotShaded("range", m_ages.data(), m_lows.data(), m_highs.data(), count);
            }

            if(count > 0) {
                ImPlot::PlotLine(m_name.c_str(), m_ages.data(), m_values.data(), count);
            }

            ImPlot::EndPlot();
        }

        ImGui::PopID();
    }
};

}
