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

#include <string>

namespace {

class FakeHistoryUiServices final : public pp::app::HistoryUiServices {
public:
    void invoke_undo() override
    {
        undo_calls += 1;
        call_order += "undo;";
    }

    void invoke_redo() override
    {
        redo_calls += 1;
        call_order += "redo;";
    }

    void clear_history() override
    {
        clear_calls += 1;
        call_order += "clear;";
    }

    int undo_calls = 0;
    int redo_calls = 0;
    int clear_calls = 0;
    std::string call_order;
};

void undo_and_redo_plan_availability(pp::tests::Harness& harness)
{
    const auto undo = pp::app::plan_history_undo(2);
    PP_EXPECT(harness, undo);
    if (undo) {
        PP_EXPECT(harness, undo.value().operation == pp::app::HistoryUiOperation::undo);
        PP_EXPECT(harness, undo.value().undo_count == 2);
        PP_EXPECT(harness, undo.value().invokes_undo);
        PP_EXPECT(harness, undo.value().updates_memory_label);
        PP_EXPECT(harness, undo.value().updates_title);
        PP_EXPECT(harness, !undo.value().no_op);
    }

    const auto undo_empty = pp::app::plan_history_undo(0);
    PP_EXPECT(harness, undo_empty);
    if (undo_empty) {
        PP_EXPECT(harness, undo_empty.value().no_op);
        PP_EXPECT(harness, !undo_empty.value().invokes_undo);
    }

    const auto redo = pp::app::plan_history_redo(1);
    PP_EXPECT(harness, redo);
    if (redo) {
        PP_EXPECT(harness, redo.value().operation == pp::app::HistoryUiOperation::redo);
        PP_EXPECT(harness, redo.value().redo_count == 1);
        PP_EXPECT(harness, redo.value().invokes_redo);
        PP_EXPECT(harness, redo.value().updates_title);
    }

    const auto redo_empty = pp::app::plan_history_redo(0);
    PP_EXPECT(harness, redo_empty);
    if (redo_empty) {
        PP_EXPECT(harness, redo_empty.value().no_op);
        PP_EXPECT(harness, !redo_empty.value().invokes_redo);
    }
}

void clear_plan_tracks_stacks_and_memory(pp::tests::Harness& harness)
{
    const auto clear = pp::app::plan_history_clear(2, 1, 4096);
    PP_EXPECT(harness, clear);
    if (clear) {
        PP_EXPECT(harness, clear.value().operation == pp::app::HistoryUiOperation::clear);
        PP_EXPECT(harness, clear.value().undo_count == 2);
        PP_EXPECT(harness, clear.value().redo_count == 1);
        PP_EXPECT(harness, clear.value().memory_bytes == 4096);
        PP_EXPECT(harness, clear.value().clears_history);
        PP_EXPECT(harness, clear.value().updates_memory_label);
        PP_EXPECT(harness, !clear.value().updates_title);
    }

    const auto memory_only = pp::app::plan_history_clear(0, 0, 1024);
    PP_EXPECT(harness, memory_only);
    if (memory_only) {
        PP_EXPECT(harness, memory_only.value().clears_history);
        PP_EXPECT(harness, !memory_only.value().no_op);
    }

    const auto empty = pp::app::plan_history_clear(0, 0, 0);
    PP_EXPECT(harness, empty);
    if (empty) {
        PP_EXPECT(harness, empty.value().no_op);
        PP_EXPECT(harness, !empty.value().clears_history);
    }
}

void rejects_negative_metrics(pp::tests::Harness& harness)
{
    PP_EXPECT(harness, !pp::app::plan_history_undo(-1));
    PP_EXPECT(harness, !pp::app::plan_history_redo(-1));
    PP_EXPECT(harness, !pp::app::plan_history_clear(-1, 0, 0));
    PP_EXPECT(harness, !pp::app::plan_history_clear(0, -1, 0));
    PP_EXPECT(harness, !pp::app::plan_history_clear(0, 0, -1));
}

void executor_dispatches_undo_redo_and_clear(pp::tests::Harness& harness)
{
    FakeHistoryUiServices services;

    const auto undo = pp::app::plan_history_undo(2);
    PP_EXPECT(harness, undo);
    if (undo) {
        PP_EXPECT(harness, pp::app::execute_history_ui_plan(undo.value(), services).ok());
    }

    const auto redo = pp::app::plan_history_redo(1);
    PP_EXPECT(harness, redo);
    if (redo) {
        PP_EXPECT(harness, pp::app::execute_history_ui_plan(redo.value(), services).ok());
    }

    const auto clear = pp::app::plan_history_clear(2, 1, 4096);
    PP_EXPECT(harness, clear);
    if (clear) {
        PP_EXPECT(harness, pp::app::execute_history_ui_plan(clear.value(), services).ok());
    }

    PP_EXPECT(harness, services.undo_calls == 1);
    PP_EXPECT(harness, services.redo_calls == 1);
    PP_EXPECT(harness, services.clear_calls == 1);
    PP_EXPECT(harness, services.call_order == "undo;redo;clear;");
}

void executor_preserves_empty_history_noops(pp::tests::Harness& harness)
{
    FakeHistoryUiServices services;

    const auto undo = pp::app::plan_history_undo(0);
    PP_EXPECT(harness, undo);
    if (undo) {
        PP_EXPECT(harness, pp::app::execute_history_ui_plan(undo.value(), services).ok());
    }

    const auto redo = pp::app::plan_history_redo(0);
    PP_EXPECT(harness, redo);
    if (redo) {
        PP_EXPECT(harness, pp::app::execute_history_ui_plan(redo.value(), services).ok());
    }

    const auto clear = pp::app::plan_history_clear(0, 0, 0);
    PP_EXPECT(harness, clear);
    if (clear) {
        PP_EXPECT(harness, pp::app::execute_history_ui_plan(clear.value(), services).ok());
    }

    PP_EXPECT(harness, services.undo_calls == 0);
    PP_EXPECT(harness, services.redo_calls == 0);
    PP_EXPECT(harness, services.clear_calls == 0);
    PP_EXPECT(harness, services.call_order.empty());
}

void executor_rejects_invalid_metrics(pp::tests::Harness& harness)
{
    FakeHistoryUiServices services;
    pp::app::HistoryUiPlan plan;
    plan.operation = pp::app::HistoryUiOperation::undo;
    plan.undo_count = -1;
    plan.invokes_undo = true;

    const auto status = pp::app::execute_history_ui_plan(plan, services);
    PP_EXPECT(harness, !status.ok());
    PP_EXPECT(harness, status.code == pp::foundation::StatusCode::out_of_range);
    PP_EXPECT(harness, services.undo_calls == 0);
}

} // namespace

int main()
{
    pp::tests::Harness harness;
    harness.run("undo and redo plan availability", undo_and_redo_plan_availability);
    harness.run("clear plan tracks stacks and memory", clear_plan_tracks_stacks_and_memory);
    harness.run("rejects negative metrics", rejects_negative_metrics);
    harness.run("executor dispatches undo redo and clear", executor_dispatches_undo_redo_and_clear);
    harness.run("executor preserves empty history noops", executor_preserves_empty_history_noops);
    harness.run("executor rejects invalid metrics", executor_rejects_invalid_metrics);
    return harness.finish();
}
