#include "Sweep_Spectrum.h"
#include "Curve_Sampling.h"
#include "Plottable_Real_Time_Data.h"
#include "../render/Blend2D_Cache.h"
#include <algorithm>
#include <cmath>
#include <deque>
namespace renderive {
namespace detail {
namespace {
using Sweep_Spectrum_History = Plottable_History_Real_Time_Data<std::vector<double>, std::deque<std::vector<double>>>;
std::vector<double> flatten(const std::deque<std::vector<double>>& blocks) {
    std::vector<double> values;
    for(const auto& block : blocks)
        values.insert(values.end(), block.begin(), block.end());
    return values;
}
}
struct Sweep_Spectrum_Control::Impl {
    struct Prepare_Buffer {
        std::vector<PointF> points;
        PointF current_first;
        PointF current_second;
        bool valid{};
    };
    Impl(Sweep_Spectrum_Control& owner, std::shared_ptr<Axis> frequency, std::shared_ptr<Axis> power)
        : frequency_axis(std::move(frequency)), power_axis(std::move(power)), blocks(owner) {}
    std::shared_ptr<Axis> frequency_axis;
    std::shared_ptr<Axis> power_axis;
    Sweep_Spectrum_History blocks;
    Prepare_Buffer prepare_buffer;
};
Sweep_Spectrum_Control::Sweep_Spectrum_Control(::Scene_Base& scene, const Sweep_Spectrum_Properties& properties, std::shared_ptr<Axis> frequency_axis, std::shared_ptr<Axis> power_axis)
    : Plottable_State(scene, properties), impl_(std::make_unique<Impl>(*this, std::move(frequency_axis), std::move(power_axis))) {}
Sweep_Spectrum_Control::~Sweep_Spectrum_Control() = default;
void Sweep_Spectrum_Control::append_block(std::span<const double> values) {
    if(get<&Sweep_Spectrum_Properties::bins_per_block>() <= 0)
        set<&Sweep_Spectrum_Properties::bins_per_block>(static_cast<int>(values.size()));
    const int limit = get<&Sweep_Spectrum_Properties::block_count>();
    impl_->blocks.update({values.begin(), values.end()}, static_cast<std::size_t>(limit));
    changed();
}
void Sweep_Spectrum_Control::append_block(std::pmr::vector<double>&& values) {
    append_block(std::span<const double>(values.data(), values.size()));
}
std::size_t Sweep_Spectrum_Control::stored_block_count() const {
    return impl_->blocks.size();
}
std::size_t Sweep_Spectrum_Control::stored_point_count() const {
    const auto blocks = impl_->blocks.snapshot();
    std::size_t count{};
    for(const auto& block : blocks)
        count += block.size();
    return count;
}
std::size_t Sweep_Spectrum_Control::rendered_point_count() const {
    const auto state = properties();
    const auto values = flatten(impl_->blocks.snapshot());
    return curve_points(values, state.frequency_range, impl_->frequency_axis->transform(), impl_->power_axis->transform(), state.visible_range_only, state.interpolation_mode).size();
}
void Sweep_Spectrum_Control::publish() {
    publish_properties();
    const int limit = get<&Sweep_Spectrum_Properties::block_count>();
    impl_->blocks.retain_latest(static_cast<std::size_t>(limit));
}
void Sweep_Spectrum_Control::prepare_frame(const Prepare_Render_Context& context) {
    const auto& view = context.frame.render_state;
    const auto& state = render_properties(view);
    const auto& blocks = view.get(impl_->blocks);
    const auto values = flatten(blocks);
    auto& output = impl_->prepare_buffer;
    output = {};
    if (values.size() < 2)
        return;
    const Axis_Transform x = impl_->frequency_axis->transform(view);
    const Axis_Transform y = impl_->power_axis->transform(view);
    output.points = curve_points(values, state.frequency_range, x, y,
                                 state.visible_range_only, state.interpolation_mode);
    const double completed = std::min(1.0, static_cast<double>(blocks.size()) / state.block_count.get());
    const double frequency = state.frequency_range.origin + state.frequency_range.length() * completed;
    output.current_first = mapped_point(x, frequency, y, y.coordinate_range.origin);
    output.current_second = mapped_point(x, frequency, y, y.coordinate_range.target);
    output.valid = true;
}
void Sweep_Spectrum_Control::paint(Painter& painter,
                                   const Paint_Render_Context& context) {
    const auto& output = impl_->prepare_buffer;
    if (!output.valid)
        return;
    const auto& view = context.frame.render_state;
    const auto& state = render_properties(view);
    painter.polyline(output.points, state.pen);
    painter.line(output.current_first, output.current_second,
                 state.current_frequency_pen);
}
}
}
