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

#include <cmath>

namespace {

void pointer_plan_normalizes_coordinates_and_routes_designer_first(pp::tests::Harness& harness)
{
    const auto plan = pp::app::plan_app_pointer_dispatch(100.0F, 50.0F, 2.0F, true, true);

    PP_EXPECT(harness, plan);
    PP_EXPECT(harness, plan.value().request_redraw);
    PP_EXPECT(harness, plan.value().dispatch_designer_first);
    PP_EXPECT(harness, plan.value().dispatch_main_if_not_consumed);
    PP_EXPECT(harness, plan.value().normalized_x == 50.0F);
    PP_EXPECT(harness, plan.value().normalized_y == 25.0F);
}

void pointer_plan_rejects_invalid_zoom_or_coordinates(pp::tests::Harness& harness)
{
    PP_EXPECT(harness, !pp::app::plan_app_pointer_dispatch(100.0F, 50.0F, 0.0F, true, true));
    PP_EXPECT(harness, !pp::app::plan_app_pointer_dispatch(std::nanf(""), 50.0F, 1.0F, true, true));
}

void mouse_cancel_preserves_designer_first_routing(pp::tests::Harness& harness)
{
    const auto designer = pp::app::plan_app_mouse_cancel_dispatch(true, true);
    const auto main = pp::app::plan_app_mouse_cancel_dispatch(false, true);

    PP_EXPECT(harness, designer.request_redraw);
    PP_EXPECT(harness, designer.dispatch_designer_first);
    PP_EXPECT(harness, designer.dispatch_main_if_not_consumed);
    PP_EXPECT(harness, !main.dispatch_designer_first);
    PP_EXPECT(harness, main.dispatch_main_if_not_consumed);
}

void gesture_plan_computes_midpoint_distance_and_delta(pp::tests::Harness& harness)
{
    const auto plan = pp::app::plan_app_gesture_dispatch(
        0.0F,
        0.0F,
        6.0F,
        8.0F,
        0.0F,
        0.0F,
        3.0F,
        4.0F,
        2.0F,
        true);

    PP_EXPECT(harness, plan);
    PP_EXPECT(harness, plan.value().dispatch_main);
    PP_EXPECT(harness, plan.value().normalized_x == 1.5F);
    PP_EXPECT(harness, plan.value().normalized_y == 2.0F);
    PP_EXPECT(harness, plan.value().distance == 10.0F);
    PP_EXPECT(harness, plan.value().distance_delta == 5.0F);
    PP_EXPECT(harness, plan.value().position_delta_x == 1.5F);
    PP_EXPECT(harness, plan.value().position_delta_y == 2.0F);
}

void gesture_plan_rejects_invalid_input(pp::tests::Harness& harness)
{
    PP_EXPECT(
        harness,
        !pp::app::plan_app_gesture_dispatch(
            0.0F,
            0.0F,
            1.0F,
            1.0F,
            0.0F,
            0.0F,
            1.0F,
            1.0F,
            -1.0F,
            true));
}

void key_plan_tracks_state_and_vr_spacebar_sync(pp::tests::Harness& harness)
{
    const auto down = pp::app::plan_app_key_down_dispatch(true, true, true);
    const auto ordinary = pp::app::plan_app_key_down_dispatch(true, false, true);
    const auto up = pp::app::plan_app_key_up_dispatch(true);

    PP_EXPECT(harness, down.dispatch_main);
    PP_EXPECT(harness, down.set_key_down);
    PP_EXPECT(harness, down.sync_vr_camera_rotation);
    PP_EXPECT(harness, ordinary.set_key_down);
    PP_EXPECT(harness, !ordinary.sync_vr_camera_rotation);
    PP_EXPECT(harness, up.dispatch_main);
    PP_EXPECT(harness, !up.set_key_down);
}

void simple_input_plan_tracks_main_layout_availability(pp::tests::Harness& harness)
{
    const auto available = pp::app::plan_app_main_input_dispatch(true);
    const auto missing = pp::app::plan_app_main_input_dispatch(false);

    PP_EXPECT(harness, available.request_redraw);
    PP_EXPECT(harness, available.dispatch_main);
    PP_EXPECT(harness, missing.request_redraw);
    PP_EXPECT(harness, !missing.dispatch_main);
}

void ui_visibility_toggle_plan_flips_state_and_targets_panel_children(pp::tests::Harness& harness)
{
    const auto hidden = pp::app::plan_app_ui_visibility_toggle(true, true, 2U, 4U);
    const auto visible = pp::app::plan_app_ui_visibility_toggle(false, true, 3U, 1U);

    PP_EXPECT(harness, hidden);
    PP_EXPECT(harness, !hidden.value().next_ui_visible);
    PP_EXPECT(harness, hidden.value().first_panel_child_index == 1U);
    PP_EXPECT(harness, hidden.value().panel_child_count == 4U);
    PP_EXPECT(harness, visible);
    PP_EXPECT(harness, visible.value().next_ui_visible);
    PP_EXPECT(harness, visible.value().panel_child_count == 1U);
}

void ui_visibility_toggle_plan_rejects_missing_panel_container(pp::tests::Harness& harness)
{
    PP_EXPECT(harness, !pp::app::plan_app_ui_visibility_toggle(true, false, 0U, 0U));
    PP_EXPECT(harness, !pp::app::plan_app_ui_visibility_toggle(true, true, 1U, 0U));
}

void stylus_attach_plan_sets_touch_lock_only_when_canvas_exists(pp::tests::Harness& harness)
{
    const auto with_canvas = pp::app::plan_app_stylus_attach(true);
    const auto without_canvas = pp::app::plan_app_stylus_attach(false);

    PP_EXPECT(harness, with_canvas.set_has_stylus);
    PP_EXPECT(harness, with_canvas.enable_canvas_touch_lock);
    PP_EXPECT(harness, without_canvas.set_has_stylus);
    PP_EXPECT(harness, !without_canvas.enable_canvas_touch_lock);
}

} // namespace

int main()
{
    pp::tests::Harness harness;
    harness.run("pointer plan normalizes coordinates and routes designer first", pointer_plan_normalizes_coordinates_and_routes_designer_first);
    harness.run("pointer plan rejects invalid zoom or coordinates", pointer_plan_rejects_invalid_zoom_or_coordinates);
    harness.run("mouse cancel preserves designer first routing", mouse_cancel_preserves_designer_first_routing);
    harness.run("gesture plan computes midpoint distance and delta", gesture_plan_computes_midpoint_distance_and_delta);
    harness.run("gesture plan rejects invalid input", gesture_plan_rejects_invalid_input);
    harness.run("key plan tracks state and VR spacebar sync", key_plan_tracks_state_and_vr_spacebar_sync);
    harness.run("simple input plan tracks main layout availability", simple_input_plan_tracks_main_layout_availability);
    harness.run("UI visibility toggle plan flips state and targets panel children", ui_visibility_toggle_plan_flips_state_and_targets_panel_children);
    harness.run("UI visibility toggle plan rejects missing panel container", ui_visibility_toggle_plan_rejects_missing_panel_container);
    harness.run("stylus attach plan sets touch lock only when canvas exists", stylus_attach_plan_sets_touch_lock_only_when_canvas_exists);
    return harness.finish();
}
