#include "Numeric_Axis.h"

namespace renderive::detail {

Axis_Control::Axis_Control(const Axis_Properties& properties)
    : Axis_State_Strategy(properties) {}

void Axis_Control::handle_event(const Event& event) {
    if (event.type == Event_Type::Wheel && get<&Axis_Properties::wheel>()) {
        const auto& wheel = static_cast<const Wheel_Event&>(event);
        const Range range = get<&Axis_Properties::coordinates>();
        const Orientation orientation = get<&Axis_Base_Properties::orientation>();
        const double anchor_pixel = orientation == Orientation::Horizontal
                                        ? wheel.position.x
                                        : wheel.position.y;
        const double anchor = transform().pixel_to_coord(anchor_pixel);
        const double factor = wheel.angle_delta_y >= 0.0 ? 0.9 : 1.1;
        const Range next{anchor + (range.origin - anchor) * factor,
                         anchor + (range.target - anchor) * factor};
        set<&Axis_Properties::coordinates>(next);
        event.accept();
        return;
    }
    if (!get<&Axis_Properties::drag>())
        return;
    if (event.type == Event_Type::Pointer_Press) {
        const auto& pointer = static_cast<const Pointer_Event&>(event);
        if (pointer.button == Mouse_Button::Left) {
            interaction_.update([&](Axis_Interaction_State& interaction) {
                interaction.dragging = true;
                interaction.last_pointer = pointer.position;
            });
            event.accept();
        }
    } else if (event.type == Event_Type::Pointer_Move) {
        const auto& pointer = static_cast<const Pointer_Event&>(event);
        PointF previous;
        bool dragging{};
        interaction_.update([&](Axis_Interaction_State& interaction) {
            dragging = interaction.dragging;
            if (!dragging)
                return;
            previous = interaction.last_pointer;
            interaction.last_pointer = pointer.position;
        });
        if (!dragging)
            return;
        const Orientation orientation = get<&Axis_Base_Properties::orientation>();
        const double delta = orientation == Orientation::Horizontal
                                 ? pointer.position.x - previous.x
                                 : pointer.position.y - previous.y;
        const Range range = get<&Axis_Properties::coordinates>();
        const std::size_t pixel_length = get<&Axis_Base_Properties::pixel_length>();
        const double shift = pixel_length == 0
                                 ? 0.0
                                 : -delta * range.length() / static_cast<double>(pixel_length);
        set<&Axis_Properties::coordinates>(Range{range.origin + shift, range.target + shift});
        event.accept();
    } else if (event.type == Event_Type::Pointer_Release) {
        bool accepted{};
        interaction_.update([&](Axis_Interaction_State& interaction) {
            accepted = interaction.dragging;
            interaction.dragging = false;
        });
        if (accepted)
            event.accept();
    }
}

void Axis_Control::publish() {
    Axis_State_Strategy::publish();
    interaction_.publish();
}

std::uint64_t Axis_Control::state_revision() const {
    return Axis_State_Strategy::state_revision() + interaction_.state_revision();
}

Range Axis_Control::coordinate_range(const Axis_Properties& state) const noexcept {
    return state.coordinates.get();
}

} // namespace renderive::detail
