#include "Selection_Rectangle_Overlay.h"
#include "Plottable_Real_Time_Data.h"
#include "Heatmap_Utils.h"
#include "../render/Blend2D_Cache.h"
#include "../renderable/Renderable_p.h"
#include <algorithm>
#include <cmath>
#include <sstream>
#include <optional>
namespace renderive {
namespace detail {
namespace {
struct Selection_Interaction_Base {};
struct Selection_Interaction {
    bool selecting{};
    PointF selection_start{};
    PointF selection_current{};
};
using Selection_Interaction_State = Double_State_Strategy<Selection_Interaction_Base, Selection_Interaction>;
RectF axis_content_rect(const Axis_Transform& first, const Axis_Transform& second) {
    return mapped_rect(first, second, first.coordinate_range, second.coordinate_range);
}
}
struct Selection_Rectangle_Overlay_Control::Impl : Renderable::Impl {
    struct Prepared_Region {
        RectF pixels;
        std::string label;
    };
    struct Prepare_Buffer {
        std::vector<Prepared_Region> regions;
        RectF active_selection;
        bool selecting{};
    };
    Impl(renderive_Owner<Abs_Axis> horizontal,
         renderive_Owner<Abs_Axis> vertical)
        : horizontal_axis(std::move(horizontal)),
          vertical_axis(std::move(vertical)) {}
    renderive_Owner<Abs_Axis> horizontal_axis;
    renderive_Owner<Abs_Axis> vertical_axis;
    std::optional<Plottable_History_Real_Time_Data<RectF,
                                                   std::vector<RectF>>> regions;
    Selection_Interaction_State interaction;
    Prepare_Buffer prepare_buffer;
};
Selection_Rectangle_Overlay_Control::Selection_Rectangle_Overlay_Control(const Selection_Rectangle_Overlay_Properties& properties, renderive_Owner<Abs_Axis> horizontal_axis, renderive_Owner<Abs_Axis> vertical_axis)
    : Plottable_State(properties, std::make_unique<Impl>(
          std::move(horizontal_axis), std::move(vertical_axis))) {
    d_func<Impl>().regions.emplace(*this);
}
Selection_Rectangle_Overlay_Control::~Selection_Rectangle_Overlay_Control() = default;
std::vector<RectF> Selection_Rectangle_Overlay_Control::selected_regions() const {
    return d_func<Impl>().regions->snapshot();
}
void Selection_Rectangle_Overlay_Control::clear_selected_regions() {
    d_func<Impl>().regions->clear();
    d_func<Impl>().interaction.update([](Selection_Interaction& interaction) {
        interaction.selecting = false;
    });
    changed();
}
void Selection_Rectangle_Overlay_Control::handle_event(const Event& event) {
    const RectF content = axis_content_rect(d_func<Impl>().horizontal_axis->transform(), d_func<Impl>().vertical_axis->transform());
    if(event.type == Event_Type::Pointer_Press) {
        const auto& pointer = static_cast<const Pointer_Event&>(event);
        if(pointer.button != Mouse_Button::Left || !content.contains(pointer.position))
            return;
        d_func<Impl>().interaction.update([&](Selection_Interaction& interaction) {
            interaction.selecting = true;
            interaction.selection_start = interaction.selection_current = pointer.position;
        });
        event.accept();
        changed();
        return;
    }
    if(event.type == Event_Type::Pointer_Move) {
        const auto& pointer = static_cast<const Pointer_Event&>(event);
        bool selecting{};
        d_func<Impl>().interaction.update([&](Selection_Interaction& interaction) {
            selecting = interaction.selecting;
            if(selecting)
                interaction.selection_current = pointer.position;
        });
        if(!selecting)
            return;
        event.accept();
        changed();
        return;
    }
    if(event.type != Event_Type::Pointer_Release)
        return;
    const auto& pointer = static_cast<const Pointer_Event&>(event);
    PointF start;
    bool selecting{};
    d_func<Impl>().interaction.update([&](Selection_Interaction& interaction) {
        selecting = interaction.selecting;
        if(!selecting)
            return;
        interaction.selecting = false;
        start = interaction.selection_start;
        interaction.selection_current = pointer.position;
    });
    if(!selecting)
        return;
    const Axis_Transform horizontal = d_func<Impl>().horizontal_axis->transform();
    const Axis_Transform vertical = d_func<Impl>().vertical_axis->transform();
    const double first_start = horizontal.point_to_coord(start);
    const double second_start = vertical.point_to_coord(start);
    const RectF region{first_start, second_start,
                       horizontal.point_to_coord(pointer.position) - first_start,
                       vertical.point_to_coord(pointer.position) - second_start};
    if(std::abs(region.width) > 1e-9 && std::abs(region.height) > 1e-9)
        d_func<Impl>().regions->update(region.normalized());
    event.accept();
    changed();
}
void Selection_Rectangle_Overlay_Control::publish() {
    publish_properties();
    d_func<Impl>().interaction.publish();
}
void Selection_Rectangle_Overlay_Control::prepare_frame(
    const Prepare_Render_Context& context) {
    const auto& view = context.frame.render_state;
    const auto& state = render_properties(view);
    const auto& interaction = view.get(d_func<Impl>().interaction);
    const auto horizontal = d_func<Impl>().horizontal_axis->transform(view);
    const auto vertical = d_func<Impl>().vertical_axis->transform(view);
    auto& output = d_func<Impl>().prepare_buffer;
    output = {};
    for (const RectF& region : view.get(*d_func<Impl>().regions)) {
        const RectF pixels = mapped_rect(horizontal, vertical,
                                         {region.x, region.right()},
                                         {region.y, region.bottom()});
        std::ostringstream text;
        text << region.width << " x " << region.height;
        output.regions.push_back({pixels, text.str()});
    }
    output.selecting = interaction.selecting;
    output.active_selection = {
        interaction.selection_start.x,
        interaction.selection_start.y,
        interaction.selection_current.x - interaction.selection_start.x,
        interaction.selection_current.y - interaction.selection_start.y};
}
void Selection_Rectangle_Overlay_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 auto& region : output.regions) {
        painter.rect(region.pixels, state.selection_border_pen,
                     state.selection_brush);
        const RectF normalized = region.pixels.normalized();
        painter.text({normalized.x + 3.0, normalized.y + 3.0}, region.label,
                     state.label_font, state.label_pen);
    }
    if (output.selecting)
        painter.rect(output.active_selection, state.selection_border_pen,
                     state.selection_brush);
}
}
}
