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

#include <limits>
#include <string>

namespace {

class FakeRecordingServices final : public pp::app::RecordingServices {
public:
    void start_thread() override
    {
        starts += 1;
        call_order += "start;";
    }

    void stop_thread() override
    {
        stops += 1;
        call_order += "stop;";
    }

    void delete_recorded_files() override
    {
        deletes += 1;
        call_order += "delete;";
    }

    void set_frame_count(int frame_count) override
    {
        frame_count_value = frame_count;
        call_order += "count;";
    }

    void update_frame_label() override
    {
        label_updates += 1;
        call_order += "label;";
    }

    void begin_export(int progress_total) override
    {
        export_begins += 1;
        export_progress_total = progress_total;
        call_order += "begin;";
    }

    void write_mp4(std::string_view path) override
    {
        export_writes += 1;
        export_path = std::string(path);
        call_order += "write;";
    }

    void end_export() override
    {
        export_ends += 1;
        call_order += "end;";
    }

    int starts = 0;
    int stops = 0;
    int deletes = 0;
    int label_updates = 0;
    int frame_count_value = -1;
    int export_begins = 0;
    int export_writes = 0;
    int export_ends = 0;
    int export_progress_total = 0;
    std::string export_path;
    std::string call_order;
};

void recording_start_only_starts_when_not_running(pp::tests::Harness& harness)
{
    PP_EXPECT(
        harness,
        pp::app::plan_recording_start(false) == pp::app::RecordingStartAction::start_thread);
    PP_EXPECT(
        harness,
        pp::app::plan_recording_start(true) == pp::app::RecordingStartAction::no_op_already_running);
}

void recording_stop_only_stops_when_running(pp::tests::Harness& harness)
{
    PP_EXPECT(
        harness,
        pp::app::plan_recording_stop(true) == pp::app::RecordingStopAction::stop_thread);
    PP_EXPECT(
        harness,
        pp::app::plan_recording_stop(false) == pp::app::RecordingStopAction::no_op_not_running);
}

void recording_clear_resets_frames_and_preserves_platform_delete_flag(pp::tests::Harness& harness)
{
    const auto running_desktop = pp::app::plan_recording_clear(true, false);
    PP_EXPECT(harness, running_desktop.stop_running_recording);
    PP_EXPECT(harness, !running_desktop.delete_recorded_files);
    PP_EXPECT(harness, running_desktop.frame_count_after_clear == 0);

    const auto stopped_apple = pp::app::plan_recording_clear(false, true);
    PP_EXPECT(harness, !stopped_apple.stop_running_recording);
    PP_EXPECT(harness, stopped_apple.delete_recorded_files);
    PP_EXPECT(harness, stopped_apple.frame_count_after_clear == 0);
}

void recording_export_tracks_frame_count(pp::tests::Harness& harness)
{
    const auto plan = pp::app::plan_recording_export(120);
    PP_EXPECT(harness, plan.frame_count == 120);
    PP_EXPECT(harness, plan.progress_total == 120);
}

void recording_export_progress_dialog_preserves_legacy_title(pp::tests::Harness& harness)
{
    const auto export_plan = pp::app::plan_recording_export(7);
    const auto progress = pp::app::plan_recording_export_progress_dialog(export_plan);

    PP_EXPECT(harness, progress.title == "Exporting MP4 movie");
    PP_EXPECT(harness, progress.total == 7);
    PP_EXPECT(harness, progress.count == 0);
    PP_EXPECT(harness, progress.progress_fraction == 0.0F);
}

void recording_export_clamps_progress_total(pp::tests::Harness& harness)
{
    const auto too_many_frames = static_cast<std::size_t>(std::numeric_limits<int>::max()) + 1U;
    const auto plan = pp::app::plan_recording_export(too_many_frames);
    PP_EXPECT(harness, plan.frame_count == too_many_frames);
    PP_EXPECT(harness, plan.progress_total == std::numeric_limits<int>::max());
}

void recording_worker_iteration_encodes_only_when_ready(pp::tests::Harness& harness)
{
    const auto encode = pp::app::plan_recording_worker_iteration(true, true, true);
    const auto stopped = pp::app::plan_recording_worker_iteration(false, true, true);
    const auto missing_encoder = pp::app::plan_recording_worker_iteration(true, false, true);
    const auto missing_canvas = pp::app::plan_recording_worker_iteration(true, true, false);

    PP_EXPECT(harness, encode.continue_running);
    PP_EXPECT(harness, encode.encode_frame);
    PP_EXPECT(harness, encode.clear_dirty_stroke);
    PP_EXPECT(harness, encode.update_frame_label);
    PP_EXPECT(harness, !stopped.continue_running);
    PP_EXPECT(harness, !stopped.encode_frame);
    PP_EXPECT(harness, missing_encoder.continue_running);
    PP_EXPECT(harness, !missing_encoder.encode_frame);
    PP_EXPECT(harness, missing_canvas.continue_running);
    PP_EXPECT(harness, !missing_canvas.encode_frame);
}

void executor_dispatches_recording_lifecycle(pp::tests::Harness& harness)
{
    FakeRecordingServices services;

    PP_EXPECT(
        harness,
        pp::app::execute_recording_start_action(pp::app::RecordingStartAction::start_thread, services).ok());
    PP_EXPECT(
        harness,
        pp::app::execute_recording_stop_action(pp::app::RecordingStopAction::stop_thread, services).ok());
    PP_EXPECT(
        harness,
        pp::app::execute_recording_start_action(
            pp::app::RecordingStartAction::no_op_already_running,
            services)
            .ok());
    PP_EXPECT(
        harness,
        pp::app::execute_recording_stop_action(pp::app::RecordingStopAction::no_op_not_running, services).ok());

    PP_EXPECT(harness, services.starts == 1);
    PP_EXPECT(harness, services.stops == 1);
    PP_EXPECT(harness, services.call_order == "start;stop;");
}

void executor_dispatches_recording_clear_and_export(pp::tests::Harness& harness)
{
    FakeRecordingServices services;

    const auto clear = pp::app::plan_recording_clear(true, true);
    PP_EXPECT(harness, pp::app::execute_recording_clear_plan(clear, services).ok());

    const auto export_plan = pp::app::plan_recording_export(12);
    PP_EXPECT(
        harness,
        pp::app::execute_recording_export_plan(export_plan, services, "D:/Paint/out.mp4").ok());

    PP_EXPECT(harness, services.stops == 1);
    PP_EXPECT(harness, services.deletes == 1);
    PP_EXPECT(harness, services.frame_count_value == 0);
    PP_EXPECT(harness, services.label_updates == 1);
    PP_EXPECT(harness, services.export_begins == 1);
    PP_EXPECT(harness, services.export_progress_total == 12);
    PP_EXPECT(harness, services.export_writes == 1);
    PP_EXPECT(harness, services.export_path == "D:/Paint/out.mp4");
    PP_EXPECT(harness, services.export_ends == 1);
    PP_EXPECT(harness, services.call_order == "stop;delete;count;label;begin;write;end;");
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("recording start only starts when not running", recording_start_only_starts_when_not_running);
    harness.run("recording stop only stops when running", recording_stop_only_stops_when_running);
    harness.run(
        "recording clear resets frames and preserves platform delete flag",
        recording_clear_resets_frames_and_preserves_platform_delete_flag);
    harness.run("recording export tracks frame count", recording_export_tracks_frame_count);
    harness.run(
        "recording export progress dialog preserves legacy title",
        recording_export_progress_dialog_preserves_legacy_title);
    harness.run("recording export clamps progress total", recording_export_clamps_progress_total);
    harness.run("recording worker iteration encodes only when ready", recording_worker_iteration_encodes_only_when_ready);
    harness.run("executor dispatches recording lifecycle", executor_dispatches_recording_lifecycle);
    harness.run("executor dispatches recording clear and export", executor_dispatches_recording_clear_and_export);
    return harness.finish();
}
