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

#include <cmath>
#include <cstddef>
#include <string>

namespace {

class FakeCanvasViewServices final : public pp::app::CanvasViewServices {
public:
    void reset_camera(const pp::app::CanvasCameraState& state) override
    {
        camera_state = state;
        reset_calls += 1;
        call_order += "reset;";
    }

    void set_density(const pp::app::CanvasViewDensityPlan& plan) override
    {
        density = plan.density;
        recreates_buffers = plan.recreates_buffers;
        density_calls += 1;
        call_order += "density;";
    }

    void set_cursor_mode(const pp::app::CanvasViewCursorModePlan& plan) override
    {
        cursor_mode = plan.mode;
        cursor_calls += 1;
        call_order += "cursor;";
    }

    pp::app::CanvasCameraState camera_state;
    float density = 0.0F;
    bool recreates_buffers = false;
    pp::app::CanvasViewCursorMode cursor_mode = pp::app::CanvasViewCursorMode::never;
    int reset_calls = 0;
    int density_calls = 0;
    int cursor_calls = 0;
    std::string call_order;
};

void camera_reset_projects_legacy_defaults(pp::tests::Harness& harness)
{
    const auto state = pp::app::plan_canvas_camera_reset();

    for (std::size_t index = 0; index < state.rotation.size(); ++index) {
        const bool diagonal = index == 0 || index == 5 || index == 10 || index == 15;
        PP_EXPECT(harness, state.rotation[index] == (diagonal ? 1.0F : 0.0F));
    }

    PP_EXPECT(harness, state.position[0] == 0.0F);
    PP_EXPECT(harness, state.position[1] == 0.0F);
    PP_EXPECT(harness, state.position[2] == 0.0F);
    PP_EXPECT(harness, state.field_of_view_degrees == 85.0F);
    PP_EXPECT(harness, state.pan[0] == 0.0F);
    PP_EXPECT(harness, state.pan[1] == 0.0F);
}

void density_rejects_non_positive_or_non_finite_values(pp::tests::Harness& harness)
{
    PP_EXPECT(harness, !pp::app::plan_canvas_view_density(0.0F));
    PP_EXPECT(harness, !pp::app::plan_canvas_view_density(-1.0F));
    PP_EXPECT(harness, !pp::app::plan_canvas_view_density(std::nanf("")));
}

void density_projects_buffer_recreation(pp::tests::Harness& harness)
{
    const auto plan = pp::app::plan_canvas_view_density(1.5F);
    PP_EXPECT(harness, plan);
    if (plan) {
        PP_EXPECT(harness, plan.value().density == 1.5F);
        PP_EXPECT(harness, plan.value().recreates_buffers);
    }
}

void cursor_mode_rejects_out_of_range_values(pp::tests::Harness& harness)
{
    PP_EXPECT(harness, !pp::app::plan_canvas_view_cursor_mode(-1));
    PP_EXPECT(harness, !pp::app::plan_canvas_view_cursor_mode(4));
}

void cursor_mode_projects_legacy_integer_modes(pp::tests::Harness& harness)
{
    PP_EXPECT(
        harness,
        pp::app::plan_canvas_view_cursor_mode(0).value().mode == pp::app::CanvasViewCursorMode::never);
    PP_EXPECT(
        harness,
        pp::app::plan_canvas_view_cursor_mode(1).value().mode == pp::app::CanvasViewCursorMode::small_brush);
    PP_EXPECT(
        harness,
        pp::app::plan_canvas_view_cursor_mode(2).value().mode == pp::app::CanvasViewCursorMode::not_painting);
    PP_EXPECT(
        harness,
        pp::app::plan_canvas_view_cursor_mode(3).value().mode == pp::app::CanvasViewCursorMode::always);
}

void executor_dispatches_canvas_view_services(pp::tests::Harness& harness)
{
    FakeCanvasViewServices services;

    PP_EXPECT(harness, pp::app::execute_canvas_camera_reset(services).ok());
    PP_EXPECT(harness, pp::app::execute_canvas_view_density(2.0F, services).ok());
    PP_EXPECT(harness, pp::app::execute_canvas_view_cursor_mode(3, services).ok());

    PP_EXPECT(harness, services.reset_calls == 1);
    PP_EXPECT(harness, services.density_calls == 1);
    PP_EXPECT(harness, services.cursor_calls == 1);
    PP_EXPECT(harness, services.camera_state.field_of_view_degrees == 85.0F);
    PP_EXPECT(harness, services.density == 2.0F);
    PP_EXPECT(harness, services.recreates_buffers);
    PP_EXPECT(harness, services.cursor_mode == pp::app::CanvasViewCursorMode::always);
    PP_EXPECT(harness, services.call_order == "reset;density;cursor;");
}

void executor_rejects_invalid_canvas_view_requests(pp::tests::Harness& harness)
{
    FakeCanvasViewServices services;

    PP_EXPECT(harness, !pp::app::execute_canvas_view_density(0.0F, services).ok());
    PP_EXPECT(harness, !pp::app::execute_canvas_view_cursor_mode(99, services).ok());
    PP_EXPECT(harness, services.density_calls == 0);
    PP_EXPECT(harness, services.cursor_calls == 0);
}

} // namespace

int main()
{
    pp::tests::Harness harness;
    harness.run("camera reset projects legacy defaults", camera_reset_projects_legacy_defaults);
    harness.run("density rejects non-positive or non-finite values", density_rejects_non_positive_or_non_finite_values);
    harness.run("density projects buffer recreation", density_projects_buffer_recreation);
    harness.run("cursor mode rejects out of range values", cursor_mode_rejects_out_of_range_values);
    harness.run("cursor mode projects legacy integer modes", cursor_mode_projects_legacy_integer_modes);
    harness.run("executor dispatches canvas view services", executor_dispatches_canvas_view_services);
    harness.run("executor rejects invalid canvas view requests", executor_rejects_invalid_canvas_view_requests);
    return harness.finish();
}
