#include "app_core/quick_ui.h"
#include "test_harness.h"

#include <cmath>
#include <string>

namespace {

class FakeQuickUiServices final : public pp::app::QuickUiServices {
public:
    void select_slot(pp::app::QuickUiSlotKind slot_kind, int slot_index, bool fire_event) override
    {
        selects += 1;
        last_slot_kind = slot_kind;
        last_slot_index = slot_index;
        last_fire_event = fire_event;
        call_order += "select;";
    }

    void open_slot_popup(pp::app::QuickUiSlotKind slot_kind, int slot_index) override
    {
        popups += 1;
        last_slot_kind = slot_kind;
        last_slot_index = slot_index;
        call_order += "popup;";
    }

    void restore_state(int brush_index, int color_index, bool fire_event) override
    {
        restores += 1;
        last_brush_index = brush_index;
        last_color_index = color_index;
        last_fire_event = fire_event;
        call_order += "restore;";
    }

    void reset_state(bool fire_event) override
    {
        resets += 1;
        last_fire_event = fire_event;
        call_order += "reset;";
    }

    int selects = 0;
    int popups = 0;
    int restores = 0;
    int resets = 0;
    pp::app::QuickUiSlotKind last_slot_kind = pp::app::QuickUiSlotKind::brush;
    int last_slot_index = -1;
    int last_brush_index = -1;
    int last_color_index = -1;
    bool last_fire_event = false;
    std::string call_order;
};

void slot_click_selects_or_opens_popup(pp::tests::Harness& harness)
{
    const auto select_brush = pp::app::plan_quick_slot_click(pp::app::QuickUiSlotKind::brush, 0, 2, 3);
    PP_EXPECT(harness, select_brush);
    if (select_brush) {
        PP_EXPECT(harness, select_brush.value().operation == pp::app::QuickUiOperation::select_slot);
        PP_EXPECT(harness, select_brush.value().slot_kind == pp::app::QuickUiSlotKind::brush);
        PP_EXPECT(harness, select_brush.value().slot_index == 2);
        PP_EXPECT(harness, select_brush.value().previous_index == 0);
        PP_EXPECT(harness, select_brush.value().brush_index == 2);
        PP_EXPECT(harness, select_brush.value().updates_selection);
        PP_EXPECT(harness, select_brush.value().invokes_change_callback);
        PP_EXPECT(harness, select_brush.value().mutates_quick_state);
        PP_EXPECT(harness, !select_brush.value().opens_brush_popup);
    }

    const auto open_brush = pp::app::plan_quick_slot_click(pp::app::QuickUiSlotKind::brush, 1, 1, 3);
    PP_EXPECT(harness, open_brush);
    if (open_brush) {
        PP_EXPECT(harness, open_brush.value().operation == pp::app::QuickUiOperation::open_slot_popup);
        PP_EXPECT(harness, open_brush.value().opens_brush_popup);
        PP_EXPECT(harness, !open_brush.value().opens_color_picker);
        PP_EXPECT(harness, !open_brush.value().updates_selection);
        PP_EXPECT(harness, !open_brush.value().mutates_quick_state);
    }

    const auto open_color = pp::app::plan_quick_slot_click(pp::app::QuickUiSlotKind::color, 0, 0, 3);
    PP_EXPECT(harness, open_color);
    if (open_color) {
        PP_EXPECT(harness, open_color.value().opens_color_picker);
        PP_EXPECT(harness, !open_color.value().opens_brush_popup);
    }
}

void slot_click_rejects_invalid_indices(pp::tests::Harness& harness)
{
    PP_EXPECT(harness, !pp::app::plan_quick_slot_click(pp::app::QuickUiSlotKind::brush, -1, 0, 3));
    PP_EXPECT(harness, !pp::app::plan_quick_slot_click(pp::app::QuickUiSlotKind::brush, 0, 3, 3));
    PP_EXPECT(harness, !pp::app::plan_quick_slot_click(pp::app::QuickUiSlotKind::color, 0, 0, 0));
}

void restore_and_reset_validate_state(pp::tests::Harness& harness)
{
    const auto restore = pp::app::plan_quick_state_restore(2, 1, 3, true);
    PP_EXPECT(harness, restore);
    if (restore) {
        PP_EXPECT(harness, restore.value().operation == pp::app::QuickUiOperation::restore_state);
        PP_EXPECT(harness, restore.value().brush_index == 2);
        PP_EXPECT(harness, restore.value().color_index == 1);
        PP_EXPECT(harness, restore.value().slot_count == 3);
        PP_EXPECT(harness, restore.value().fire_event);
        PP_EXPECT(harness, restore.value().restores_slots);
        PP_EXPECT(harness, restore.value().redraws_brush_previews);
        PP_EXPECT(harness, restore.value().invokes_change_callback);
    }

    const auto reset = pp::app::plan_quick_state_reset(3, false);
    PP_EXPECT(harness, reset);
    if (reset) {
        PP_EXPECT(harness, reset.value().operation == pp::app::QuickUiOperation::reset_state);
        PP_EXPECT(harness, reset.value().brush_index == 0);
        PP_EXPECT(harness, reset.value().color_index == 0);
        PP_EXPECT(harness, reset.value().resets_slots);
        PP_EXPECT(harness, reset.value().redraws_brush_previews);
        PP_EXPECT(harness, !reset.value().invokes_change_callback);
        PP_EXPECT(harness, reset.value().mutates_quick_state);
    }

    PP_EXPECT(harness, !pp::app::plan_quick_state_restore(3, 0, 3, false));
    PP_EXPECT(harness, !pp::app::plan_quick_state_restore(0, -1, 3, false));
    PP_EXPECT(harness, !pp::app::plan_quick_state_reset(0, false));
}

void slider_preview_projects_cursor_and_mode_flags(pp::tests::Harness& harness)
{
    const auto ltr = pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
        .ui_rtl = false,
        .slider_x = 10.0F,
        .slider_y = 20.0F,
        .slider_height = 40.0F,
        .zoom = 2.0F,
        .has_pen_mode = true,
        .has_line_mode = false,
    });
    PP_EXPECT(harness, ltr);
    if (ltr) {
        PP_EXPECT(harness, ltr.value().cursor_x == 220.0F);
        PP_EXPECT(harness, ltr.value().cursor_y == 80.0F);
        PP_EXPECT(harness, ltr.value().updates_pen_mode);
        PP_EXPECT(harness, !ltr.value().updates_line_mode);
        PP_EXPECT(harness, ltr.value().draws_tip);
        PP_EXPECT(harness, ltr.value().disables_pen_outline);
        PP_EXPECT(harness, ltr.value().redraws_brush_preview);
        PP_EXPECT(harness, ltr.value().invokes_change_callback);
    }

    const auto rtl_line = pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
        .ui_rtl = true,
        .slider_x = 50.0F,
        .slider_y = 10.0F,
        .slider_height = 20.0F,
        .zoom = 1.5F,
        .has_pen_mode = false,
        .has_line_mode = true,
    });
    PP_EXPECT(harness, rtl_line);
    if (rtl_line) {
        PP_EXPECT(harness, rtl_line.value().cursor_x == -75.0F);
        PP_EXPECT(harness, rtl_line.value().cursor_y == 30.0F);
        PP_EXPECT(harness, !rtl_line.value().updates_pen_mode);
        PP_EXPECT(harness, rtl_line.value().updates_line_mode);
        PP_EXPECT(harness, rtl_line.value().draws_tip);
        PP_EXPECT(harness, !rtl_line.value().disables_pen_outline);
    }
}

void slider_preview_rejects_invalid_geometry(pp::tests::Harness& harness)
{
    PP_EXPECT(harness, !pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
        .slider_x = std::nanf(""),
        .slider_height = 1.0F,
        .zoom = 1.0F,
    }));
    PP_EXPECT(harness, !pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
        .slider_height = -1.0F,
        .zoom = 1.0F,
    }));
    PP_EXPECT(harness, !pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
        .slider_height = 1.0F,
        .zoom = 0.0F,
    }));
}

void executor_dispatches_selection_popup_restore_and_reset(pp::tests::Harness& harness)
{
    FakeQuickUiServices services;

    const auto select = pp::app::plan_quick_slot_click(pp::app::QuickUiSlotKind::brush, 0, 2, 3);
    PP_EXPECT(harness, select);
    if (select) {
        PP_EXPECT(harness, pp::app::execute_quick_ui_plan(select.value(), services).ok());
    }

    const auto popup = pp::app::plan_quick_slot_click(pp::app::QuickUiSlotKind::color, 1, 1, 3);
    PP_EXPECT(harness, popup);
    if (popup) {
        PP_EXPECT(harness, pp::app::execute_quick_ui_plan(popup.value(), services).ok());
    }

    const auto restore = pp::app::plan_quick_state_restore(2, 1, 3, true);
    PP_EXPECT(harness, restore);
    if (restore) {
        PP_EXPECT(harness, pp::app::execute_quick_ui_plan(restore.value(), services).ok());
    }

    const auto reset = pp::app::plan_quick_state_reset(3, false);
    PP_EXPECT(harness, reset);
    if (reset) {
        PP_EXPECT(harness, pp::app::execute_quick_ui_plan(reset.value(), services).ok());
    }

    PP_EXPECT(harness, services.selects == 1);
    PP_EXPECT(harness, services.popups == 1);
    PP_EXPECT(harness, services.restores == 1);
    PP_EXPECT(harness, services.resets == 1);
    PP_EXPECT(harness, services.last_brush_index == 2);
    PP_EXPECT(harness, services.last_color_index == 1);
    PP_EXPECT(harness, !services.last_fire_event);
    PP_EXPECT(harness, services.call_order == "select;popup;restore;reset;");
}

void executor_rejects_malformed_quick_plans(pp::tests::Harness& harness)
{
    FakeQuickUiServices services;

    pp::app::QuickUiPlan select;
    select.operation = pp::app::QuickUiOperation::select_slot;
    select.slot_index = 0;
    select.slot_count = 3;
    select.updates_selection = false;
    PP_EXPECT(harness, !pp::app::execute_quick_ui_plan(select, services).ok());

    pp::app::QuickUiPlan popup;
    popup.operation = pp::app::QuickUiOperation::open_slot_popup;
    popup.slot_index = 0;
    popup.slot_count = 3;
    PP_EXPECT(harness, !pp::app::execute_quick_ui_plan(popup, services).ok());

    pp::app::QuickUiPlan mismatched_popup;
    mismatched_popup.operation = pp::app::QuickUiOperation::open_slot_popup;
    mismatched_popup.slot_kind = pp::app::QuickUiSlotKind::color;
    mismatched_popup.slot_index = 0;
    mismatched_popup.slot_count = 3;
    mismatched_popup.opens_brush_popup = true;
    PP_EXPECT(harness, !pp::app::execute_quick_ui_plan(mismatched_popup, services).ok());

    pp::app::QuickUiPlan restore;
    restore.operation = pp::app::QuickUiOperation::restore_state;
    restore.slot_count = 3;
    restore.brush_index = 2;
    restore.color_index = 3;
    restore.restores_slots = true;
    PP_EXPECT(harness, !pp::app::execute_quick_ui_plan(restore, services).ok());

    pp::app::QuickUiPlan reset;
    reset.operation = pp::app::QuickUiOperation::reset_state;
    reset.slot_count = 0;
    reset.resets_slots = true;
    PP_EXPECT(harness, !pp::app::execute_quick_ui_plan(reset, services).ok());

    PP_EXPECT(harness, services.selects == 0);
    PP_EXPECT(harness, services.popups == 0);
    PP_EXPECT(harness, services.restores == 0);
    PP_EXPECT(harness, services.resets == 0);
}

} // namespace

int main()
{
    pp::tests::Harness harness;
    harness.run("slot click selects or opens popup", slot_click_selects_or_opens_popup);
    harness.run("slot click rejects invalid indices", slot_click_rejects_invalid_indices);
    harness.run("restore and reset validate state", restore_and_reset_validate_state);
    harness.run("slider preview projects cursor and mode flags", slider_preview_projects_cursor_and_mode_flags);
    harness.run("slider preview rejects invalid geometry", slider_preview_rejects_invalid_geometry);
    harness.run("executor dispatches selection popup restore and reset", executor_dispatches_selection_popup_restore_and_reset);
    harness.run("executor rejects malformed quick plans", executor_rejects_malformed_quick_plans);
    return harness.finish();
}
