#include "Constellation_Diagram.h"
#include "Plottable_Real_Time_Data.h"
#include "Heatmap_Utils.h"
#include "../render/Blend2D_Cache.h"
#include "../renderable/Renderable_p.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <deque>
#include <numbers>
#include <optional>
namespace renderive {
namespace detail {
namespace {
struct Timed_Point {
    PointF point;
    std::chrono::steady_clock::time_point time;
};
using Constellation_History = Plottable_History_Real_Time_Data<Timed_Point, std::deque<Timed_Point>>;
}
struct Constellation_Diagram_Control::Impl : Renderable::Impl {
    struct Prepare_Buffer {
        std::vector<PointF> anchors;
        std::vector<PointF> points;
    };
    Impl(renderive_Owner<Axis> i, renderive_Owner<Axis> q)
        : i_axis(std::move(i)), q_axis(std::move(q)) {}
    renderive_Owner<Axis> i_axis;
    renderive_Owner<Axis> q_axis;
    std::optional<Constellation_History> points;
    Prepare_Buffer prepare_buffer;
};
Constellation_Diagram_Control::Constellation_Diagram_Control(const Constellation_Diagram_Properties& properties, renderive_Owner<Axis> i_axis, renderive_Owner<Axis> q_axis)
    : Plottable_State(properties, std::make_unique<Impl>(
          std::move(i_axis), std::move(q_axis))) {
    d_func<Impl>().points.emplace(*this);
}
Constellation_Diagram_Control::~Constellation_Diagram_Control() = default;
void Constellation_Diagram_Control::append_point(PointF point) {
    const auto now = std::chrono::steady_clock::now();
    const int lifetime = get<&Constellation_Diagram_Properties::point_lifetime_ms>();
    const auto cutoff = now - std::chrono::milliseconds(lifetime);
    const auto cutoff_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(cutoff.time_since_epoch()).count();
    d_func<Impl>().points->update({point, now});
    d_func<Impl>().points->discard_before_time_ns(static_cast<std::uint64_t>(cutoff_ns));
    changed();
}
std::size_t Constellation_Diagram_Control::point_count() const {
    return d_func<Impl>().points->size();
}
void Constellation_Diagram_Control::fit_square_to_axes() {
    const auto state = properties();
    const double side = std::max(state.i_range.size(), state.q_range.size());
    d_func<Impl>().i_axis->set<&Axis_Properties::coordinates>(
        Range{state.i_range.center() - side * 0.5, state.i_range.center() + side * 0.5});
    d_func<Impl>().q_axis->set<&Axis_Properties::coordinates>(
        Range{state.q_range.center() + side * 0.5, state.q_range.center() - side * 0.5});
}
void Constellation_Diagram_Control::publish() {
    publish_properties();
}
void Constellation_Diagram_Control::prepare_frame(
    const Prepare_Render_Context& context) {
    const auto& view = context.frame.render_state;
    const auto& state = render_properties(view);
    const auto& points = view.get(*d_func<Impl>().points);
    const Axis_Transform x = d_func<Impl>().i_axis->transform(view);
    const Axis_Transform y = d_func<Impl>().q_axis->transform(view);
    auto& output = d_func<Impl>().prepare_buffer;
    output = {};
    const int count = static_cast<int>(state.type);
    const double radius = std::min(state.i_range.size(), state.q_range.size()) * 0.4;
    output.anchors.reserve(static_cast<std::size_t>(count));
    for (int index = 0; index < count; ++index) {
        const double angle = state.phase_offset_radians + 2.0 * std::numbers::pi * index / count;
        const PointF point{state.i_range.center() + std::cos(angle) * radius, state.q_range.center() + std::sin(angle) * radius};
        output.anchors.push_back(mapped_point(x, point.x, y, point.y));
    }
    const auto cutoff = std::chrono::steady_clock::now() - std::chrono::milliseconds(state.point_lifetime_ms.get());
    output.points.reserve(points.size());
    for (const auto& value : points) {
        if (value.time < cutoff)
            continue;
        output.points.push_back(mapped_point(x, value.point.x, y, value.point.y));
    }
}
void Constellation_Diagram_Control::paint(Painter& painter,
                                          const Paint_Render_Context& context) {
    const auto& output = d_func<Impl>().prepare_buffer;
    const auto& view = context.frame.render_state;
    const auto& state = render_properties(view);
    for (const PointF point : output.anchors)
        painter.circle(point, 3.0, Pen{state.anchor_color},
                       Brush{state.anchor_color, Brush_Style::Solid});
    for (const PointF point : output.points)
        painter.circle(point, 2.0, Pen{state.point_color},
                       Brush{state.point_color, Brush_Style::Solid});
}
}
}
