#include "platform_api/platform_services.h"
#include "test_harness.h"

#include <string>
#include <string_view>
#include <utility>
#include <vector>

namespace {

class FakePlatformServices final : public pp::platform::PlatformServices {
public:
    explicit FakePlatformServices(std::string clipboard_value)
        : clipboard_value_(std::move(clipboard_value))
    {
    }

    [[nodiscard]] pp::platform::PlatformStoragePaths prepare_storage_paths() override
    {
        ++storage_prepares;
        return storage_paths;
    }

    void log_stacktrace() override
    {
        ++stacktrace_logs;
    }

    void trigger_crash_test() override
    {
        ++crash_tests;
    }

    [[nodiscard]] std::string clipboard_text() override
    {
        ++clipboard_reads;
        return clipboard_value_;
    }

    [[nodiscard]] bool set_clipboard_text(std::string_view text) override
    {
        ++clipboard_writes;
        clipboard_value_.assign(text);
        return true;
    }

    void set_cursor_visible(bool visible) override
    {
        ++cursor_updates;
        cursor_visible = visible;
    }

    void set_virtual_keyboard_visible(bool visible) override
    {
        ++keyboard_updates;
        keyboard_visible = visible;
    }

    void attach_ui_thread() override
    {
        ++ui_thread_attaches;
    }

    void detach_ui_thread() override
    {
        ++ui_thread_detaches;
    }

    void acquire_render_context() override
    {
        ++render_context_acquires;
    }

    void release_render_context() override
    {
        ++render_context_releases;
    }

    void present_render_context() override
    {
        ++render_context_presents;
    }

    void bind_default_render_target() override
    {
        ++default_render_target_binds;
    }

    void bind_main_render_target() override
    {
        ++main_render_target_binds;
    }

    void apply_render_platform_hints() override
    {
        ++render_platform_hint_applies;
    }

    void install_render_debug_callback() override
    {
        ++render_debug_callback_installs;
    }

    void begin_render_capture_frame() override
    {
        ++render_capture_begins;
    }

    void end_render_capture_frame() override
    {
        ++render_capture_ends;
    }

    [[nodiscard]] bool deletes_recorded_files_on_clear() override
    {
        ++recording_delete_policy_checks;
        return deletes_recorded_files;
    }

    void clear_recorded_files(std::string_view recording_path) override
    {
        ++recording_clears;
        cleared_recording_path.assign(recording_path);
    }

    [[nodiscard]] bool enables_live_asset_reloading() override
    {
        ++live_asset_reload_policy_checks;
        return live_asset_reloading;
    }

    void update_platform_frame(float delta_time_seconds) override
    {
        ++platform_frame_updates;
        last_platform_delta = delta_time_seconds;
    }

    void report_rendered_frames(int frames) override
    {
        ++frame_reports;
        last_frame_report = frames;
    }

    void display_file(std::string_view path) override
    {
        ++display_file_requests;
        displayed_path.assign(path);
    }

    void share_file(std::string_view path) override
    {
        ++share_file_requests;
        shared_path.assign(path);
    }

    void request_app_close() override
    {
        ++app_close_requests;
    }

    void pick_image(pp::platform::PickedPathCallback callback) override
    {
        ++pick_image_requests;
        callback(picker_path);
    }

    void pick_file(std::vector<std::string> file_types, pp::platform::PickedPathCallback callback) override
    {
        ++pick_file_requests;
        picked_file_types = std::move(file_types);
        callback(picker_path);
    }

    void pick_save_file(std::vector<std::string> file_types, pp::platform::PickedPathCallback callback) override
    {
        ++pick_save_file_requests;
        save_file_types = std::move(file_types);
        callback(save_path);
    }

    void pick_directory(pp::platform::PickedPathCallback callback) override
    {
        ++pick_directory_requests;
        callback(directory_path);
    }

    void save_prepared_file(
        std::string_view path,
        std::string_view suggested_name,
        pp::platform::PreparedFileCallback callback) override
    {
        ++save_prepared_file_requests;
        prepared_file_path.assign(path);
        prepared_file_name.assign(suggested_name);
        callback(prepared_file_path, prepared_file_saved);
    }

    int clipboard_reads = 0;
    int clipboard_writes = 0;
    int cursor_updates = 0;
    int keyboard_updates = 0;
    int ui_thread_attaches = 0;
    int ui_thread_detaches = 0;
    int render_context_acquires = 0;
    int render_context_releases = 0;
    int render_context_presents = 0;
    int default_render_target_binds = 0;
    int main_render_target_binds = 0;
    int render_platform_hint_applies = 0;
    int render_debug_callback_installs = 0;
    int render_capture_begins = 0;
    int render_capture_ends = 0;
    int storage_prepares = 0;
    int stacktrace_logs = 0;
    int crash_tests = 0;
    int recording_delete_policy_checks = 0;
    int recording_clears = 0;
    int live_asset_reload_policy_checks = 0;
    int platform_frame_updates = 0;
    int frame_reports = 0;
    int display_file_requests = 0;
    int share_file_requests = 0;
    int app_close_requests = 0;
    int pick_image_requests = 0;
    int pick_file_requests = 0;
    int pick_save_file_requests = 0;
    int pick_directory_requests = 0;
    int save_prepared_file_requests = 0;
    bool cursor_visible = false;
    bool keyboard_visible = false;
    bool prepared_file_saved = true;
    bool deletes_recorded_files = true;
    bool live_asset_reloading = true;
    float last_platform_delta = 0.0f;
    int last_frame_report = 0;
    std::string displayed_path;
    std::string shared_path;
    std::string prepared_file_path;
    std::string prepared_file_name;
    std::string cleared_recording_path;
    std::string picker_path = "D:/Paint/import.png";
    std::string save_path = "D:/Paint/export.ppi";
    std::string directory_path = "D:/Paint";
    pp::platform::PlatformStoragePaths storage_paths{
        "D:/Paint",
        "D:/Paint/work",
        "D:/Paint/frames",
        "D:/Paint/tmp",
    };
    std::vector<std::string> picked_file_types;
    std::vector<std::string> save_file_types;

private:
    std::string clipboard_value_;
};

void platform_services_dispatch_clipboard_reads_and_writes(pp::tests::Harness& harness)
{
    FakePlatformServices fake("#112233");
    pp::platform::PlatformServices& services = fake;

    PP_EXPECT(harness, services.clipboard_text() == "#112233");
    PP_EXPECT(harness, services.set_clipboard_text("#ff00aa"));
    PP_EXPECT(harness, services.clipboard_text() == "#ff00aa");
    PP_EXPECT(harness, fake.clipboard_reads == 2);
    PP_EXPECT(harness, fake.clipboard_writes == 1);
}

void platform_services_dispatch_storage_path_preparation(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;

    const auto paths = services.prepare_storage_paths();

    PP_EXPECT(harness, fake.storage_prepares == 1);
    PP_EXPECT(harness, paths.data_path == "D:/Paint");
    PP_EXPECT(harness, paths.work_path == "D:/Paint/work");
    PP_EXPECT(harness, paths.recording_path == "D:/Paint/frames");
    PP_EXPECT(harness, paths.temporary_path == "D:/Paint/tmp");
}

void platform_services_dispatch_diagnostics(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;

    services.log_stacktrace();
    services.trigger_crash_test();

    PP_EXPECT(harness, fake.stacktrace_logs == 1);
    PP_EXPECT(harness, fake.crash_tests == 1);
}

void platform_services_preserve_empty_clipboard_writes(pp::tests::Harness& harness)
{
    FakePlatformServices fake("initial");
    pp::platform::PlatformServices& services = fake;

    PP_EXPECT(harness, services.set_clipboard_text(""));
    PP_EXPECT(harness, services.clipboard_text().empty());
    PP_EXPECT(harness, fake.clipboard_writes == 1);
}

void platform_services_dispatch_visibility_updates(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;

    services.set_cursor_visible(true);
    services.set_virtual_keyboard_visible(true);
    services.set_cursor_visible(false);
    services.set_virtual_keyboard_visible(false);

    PP_EXPECT(harness, fake.cursor_updates == 2);
    PP_EXPECT(harness, fake.keyboard_updates == 2);
    PP_EXPECT(harness, !fake.cursor_visible);
    PP_EXPECT(harness, !fake.keyboard_visible);
}

void platform_services_dispatch_ui_thread_lifecycle(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;

    services.attach_ui_thread();
    services.detach_ui_thread();

    PP_EXPECT(harness, fake.ui_thread_attaches == 1);
    PP_EXPECT(harness, fake.ui_thread_detaches == 1);
}

void platform_services_dispatch_render_context_lifecycle(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;

    services.acquire_render_context();
    services.present_render_context();
    services.bind_default_render_target();
    services.bind_main_render_target();
    services.apply_render_platform_hints();
    services.install_render_debug_callback();
    services.release_render_context();

    PP_EXPECT(harness, fake.render_context_acquires == 1);
    PP_EXPECT(harness, fake.render_context_presents == 1);
    PP_EXPECT(harness, fake.default_render_target_binds == 1);
    PP_EXPECT(harness, fake.main_render_target_binds == 1);
    PP_EXPECT(harness, fake.render_platform_hint_applies == 1);
    PP_EXPECT(harness, fake.render_debug_callback_installs == 1);
    PP_EXPECT(harness, fake.render_context_releases == 1);
}

void platform_services_dispatch_render_capture_hooks(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;

    services.begin_render_capture_frame();
    services.end_render_capture_frame();

    PP_EXPECT(harness, fake.render_capture_begins == 1);
    PP_EXPECT(harness, fake.render_capture_ends == 1);
}

void platform_services_dispatch_recording_cleanup(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;

    PP_EXPECT(harness, services.deletes_recorded_files_on_clear());
    services.clear_recorded_files("D:/Paint/frames");

    PP_EXPECT(harness, fake.recording_delete_policy_checks == 1);
    PP_EXPECT(harness, fake.recording_clears == 1);
    PP_EXPECT(harness, fake.cleared_recording_path == "D:/Paint/frames");
}

void platform_services_dispatch_live_asset_reload_policy(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;

    PP_EXPECT(harness, services.enables_live_asset_reloading());
    fake.live_asset_reloading = false;
    PP_EXPECT(harness, !services.enables_live_asset_reloading());

    PP_EXPECT(harness, fake.live_asset_reload_policy_checks == 2);
}

void platform_services_dispatch_frame_hooks(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;

    services.update_platform_frame(0.125f);
    services.report_rendered_frames(42);

    PP_EXPECT(harness, fake.platform_frame_updates == 1);
    PP_EXPECT(harness, fake.last_platform_delta == 0.125f);
    PP_EXPECT(harness, fake.frame_reports == 1);
    PP_EXPECT(harness, fake.last_frame_report == 42);
}

void platform_services_dispatch_file_actions(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;

    services.display_file("D:/Paint/export.png");
    services.share_file("D:/Paint/demo.ppi");
    services.request_app_close();

    PP_EXPECT(harness, fake.display_file_requests == 1);
    PP_EXPECT(harness, fake.share_file_requests == 1);
    PP_EXPECT(harness, fake.app_close_requests == 1);
    PP_EXPECT(harness, fake.displayed_path == "D:/Paint/export.png");
    PP_EXPECT(harness, fake.shared_path == "D:/Paint/demo.ppi");
}

void platform_services_dispatch_picker_callbacks(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;
    std::string image_path;
    std::string file_path;
    std::string save_path;
    std::string directory_path;

    services.pick_image([&](std::string path) { image_path = std::move(path); });
    services.pick_file({ "ppi", "ppbr" }, [&](std::string path) { file_path = std::move(path); });
    services.pick_save_file({ "ppi" }, [&](std::string path) { save_path = std::move(path); });
    services.pick_directory([&](std::string path) { directory_path = std::move(path); });

    PP_EXPECT(harness, fake.pick_image_requests == 1);
    PP_EXPECT(harness, fake.pick_file_requests == 1);
    PP_EXPECT(harness, fake.pick_save_file_requests == 1);
    PP_EXPECT(harness, fake.pick_directory_requests == 1);
    PP_EXPECT(harness, image_path == "D:/Paint/import.png");
    PP_EXPECT(harness, file_path == "D:/Paint/import.png");
    PP_EXPECT(harness, save_path == "D:/Paint/export.ppi");
    PP_EXPECT(harness, directory_path == "D:/Paint");
    PP_EXPECT(harness, fake.picked_file_types.size() == 2);
    PP_EXPECT(harness, fake.save_file_types.size() == 1);
}

void platform_services_dispatch_prepared_file_save(pp::tests::Harness& harness)
{
    FakePlatformServices fake("unused");
    pp::platform::PlatformServices& services = fake;
    std::string saved_path;
    bool saved = false;

    services.save_prepared_file(
        "D:/Paint/export.mp4",
        "export.mp4",
        [&](std::string path, bool success) {
            saved_path = std::move(path);
            saved = success;
        });

    PP_EXPECT(harness, fake.save_prepared_file_requests == 1);
    PP_EXPECT(harness, fake.prepared_file_path == "D:/Paint/export.mp4");
    PP_EXPECT(harness, fake.prepared_file_name == "export.mp4");
    PP_EXPECT(harness, saved_path == "D:/Paint/export.mp4");
    PP_EXPECT(harness, saved);
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("platform services dispatch storage path preparation", platform_services_dispatch_storage_path_preparation);
    harness.run("platform services dispatch diagnostics", platform_services_dispatch_diagnostics);
    harness.run("platform services dispatch clipboard reads and writes", platform_services_dispatch_clipboard_reads_and_writes);
    harness.run("platform services preserve empty clipboard writes", platform_services_preserve_empty_clipboard_writes);
    harness.run("platform services dispatch visibility updates", platform_services_dispatch_visibility_updates);
    harness.run("platform services dispatch UI thread lifecycle", platform_services_dispatch_ui_thread_lifecycle);
    harness.run("platform services dispatch render context lifecycle", platform_services_dispatch_render_context_lifecycle);
    harness.run("platform services dispatch render capture hooks", platform_services_dispatch_render_capture_hooks);
    harness.run("platform services dispatch recording cleanup", platform_services_dispatch_recording_cleanup);
    harness.run("platform services dispatch live asset reload policy", platform_services_dispatch_live_asset_reload_policy);
    harness.run("platform services dispatch frame hooks", platform_services_dispatch_frame_hooks);
    harness.run("platform services dispatch file actions", platform_services_dispatch_file_actions);
    harness.run("platform services dispatch picker callbacks", platform_services_dispatch_picker_callbacks);
    harness.run("platform services dispatch prepared file save", platform_services_dispatch_prepared_file_save);
    return harness.finish();
}
