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

#include <cmath>

namespace {

void task_dispatch_executes_immediately_on_target_thread(pp::tests::Harness& harness)
{
    const auto plan = pp::app::plan_app_task_dispatch(true, true, 3, true, true, true);

    PP_EXPECT(harness, plan.execute_immediately);
    PP_EXPECT(harness, !plan.queue_task);
    PP_EXPECT(harness, !plan.remove_matching_unique_task);
    PP_EXPECT(harness, !plan.notify_worker);
    PP_EXPECT(harness, !plan.wait_for_completion);
    PP_EXPECT(harness, plan.request_redraw);
}

void task_dispatch_queues_unique_work_and_waits_for_running_worker(pp::tests::Harness& harness)
{
    const auto plan = pp::app::plan_app_task_dispatch(false, true, 2, true, true, false);

    PP_EXPECT(harness, !plan.execute_immediately);
    PP_EXPECT(harness, plan.queue_task);
    PP_EXPECT(harness, plan.remove_matching_unique_task);
    PP_EXPECT(harness, plan.notify_worker);
    PP_EXPECT(harness, plan.wait_for_completion);
    PP_EXPECT(harness, !plan.request_redraw);
}

void task_dispatch_does_not_wait_for_stopped_worker(pp::tests::Harness& harness)
{
    const auto plan = pp::app::plan_app_task_dispatch(false, false, 0, false, true, false);

    PP_EXPECT(harness, plan.queue_task);
    PP_EXPECT(harness, plan.notify_worker);
    PP_EXPECT(harness, !plan.wait_for_completion);
}

void task_dispatch_rejects_unsafe_cross_thread_mutations(pp::tests::Harness& harness)
{
    const auto plan = pp::app::plan_app_task_dispatch(false, true, 2, true, true, false, true);

    PP_EXPECT(harness, !plan.execute_immediately);
    PP_EXPECT(harness, !plan.queue_task);
    PP_EXPECT(harness, !plan.remove_matching_unique_task);
    PP_EXPECT(harness, !plan.notify_worker);
    PP_EXPECT(harness, !plan.wait_for_completion);
    PP_EXPECT(harness, !plan.request_redraw);
    PP_EXPECT(harness, plan.reject_unsafe_cross_thread_dispatch);
}

void render_queue_drain_wraps_non_empty_work_in_context(pp::tests::Harness& harness)
{
    const auto empty = pp::app::plan_app_render_queue_drain(0);
    const auto work = pp::app::plan_app_render_queue_drain(4);

    PP_EXPECT(harness, empty.mark_running);
    PP_EXPECT(harness, !empty.drain_tasks);
    PP_EXPECT(harness, !empty.wrap_in_render_context);
    PP_EXPECT(harness, work.mark_running);
    PP_EXPECT(harness, work.drain_tasks);
    PP_EXPECT(harness, work.wrap_in_render_context);
    PP_EXPECT(harness, work.task_count == 4U);
}

void ui_thread_tick_runs_tasks_and_schedules_redraw(pp::tests::Harness& harness)
{
    const auto plan = pp::app::plan_app_ui_thread_tick(3, true);

    PP_EXPECT(harness, plan.mark_running);
    PP_EXPECT(harness, plan.execute_tasks);
    PP_EXPECT(harness, plan.tick_app);
    PP_EXPECT(harness, plan.update_before_render);
    PP_EXPECT(harness, plan.enqueue_render_frame);
    PP_EXPECT(harness, plan.task_count == 3U);
}

void ui_loop_timers_report_fps_and_reload_on_threshold(pp::tests::Harness& harness)
{
    const auto plan = pp::app::plan_app_ui_loop_timers(0.25F, 0.5F, 0.9F, 0.9F, 7, true);

    PP_EXPECT(harness, plan);
    if (plan) {
        PP_EXPECT(harness, plan.value().update_platform_frame);
        PP_EXPECT(harness, plan.value().frame_accumulator == 0.75F);
        PP_EXPECT(harness, plan.value().fps_accumulator == 0.0F);
        PP_EXPECT(harness, plan.value().report_rendered_frames);
        PP_EXPECT(harness, plan.value().reported_frame_count == 7);
        PP_EXPECT(harness, plan.value().rendered_frames_after_report == 0);
        PP_EXPECT(harness, plan.value().reload_accumulator == 0.0F);
        PP_EXPECT(harness, plan.value().check_live_asset_reload);
    }
}

void ui_loop_timers_reject_invalid_values(pp::tests::Harness& harness)
{
    PP_EXPECT(harness, !pp::app::plan_app_ui_loop_timers(-0.1F, 0.0F, 0.0F, 0.0F, 0, false));
    PP_EXPECT(harness, !pp::app::plan_app_ui_loop_timers(0.1F, std::nanf(""), 0.0F, 0.0F, 0, false));
    PP_EXPECT(harness, !pp::app::plan_app_ui_loop_timers(0.1F, 0.0F, 0.0F, 0.0F, -1, false));
}

void ui_loop_redraw_increments_rendered_frames(pp::tests::Harness& harness)
{
    const auto idle = pp::app::plan_app_ui_loop_redraw(false, 2);
    const auto redraw = pp::app::plan_app_ui_loop_redraw(true, 2);

    PP_EXPECT(harness, idle.tick_app);
    PP_EXPECT(harness, !idle.enqueue_render_frame);
    PP_EXPECT(harness, idle.rendered_frames == 2);
    PP_EXPECT(harness, redraw.update_before_render);
    PP_EXPECT(harness, redraw.enqueue_render_frame);
    PP_EXPECT(harness, redraw.reset_frame_accumulator);
    PP_EXPECT(harness, redraw.rendered_frames == 3);
}

void thread_start_stop_preserve_legacy_intents(pp::tests::Harness& harness)
{
    const auto start = pp::app::plan_app_thread_start();
    const auto stop_joinable = pp::app::plan_app_thread_stop(true);
    const auto stop_detached = pp::app::plan_app_thread_stop(false);

    PP_EXPECT(harness, start.start_thread);
    PP_EXPECT(harness, start.mark_running);
    PP_EXPECT(harness, stop_joinable.mark_not_running);
    PP_EXPECT(harness, stop_joinable.notify_worker);
    PP_EXPECT(harness, stop_joinable.join_thread);
    PP_EXPECT(harness, !stop_detached.join_thread);
}

} // namespace

int main()
{
    pp::tests::Harness harness;
    harness.run("task dispatch executes immediately on target thread", task_dispatch_executes_immediately_on_target_thread);
    harness.run("task dispatch queues unique work and waits for running worker", task_dispatch_queues_unique_work_and_waits_for_running_worker);
    harness.run("task dispatch does not wait for stopped worker", task_dispatch_does_not_wait_for_stopped_worker);
    harness.run("task dispatch can reject unsafe cross-thread mutations", task_dispatch_rejects_unsafe_cross_thread_mutations);
    harness.run("render queue drain wraps non empty work in context", render_queue_drain_wraps_non_empty_work_in_context);
    harness.run("ui thread tick runs tasks and schedules redraw", ui_thread_tick_runs_tasks_and_schedules_redraw);
    harness.run("ui loop timers report fps and reload on threshold", ui_loop_timers_report_fps_and_reload_on_threshold);
    harness.run("ui loop timers reject invalid values", ui_loop_timers_reject_invalid_values);
    harness.run("ui loop redraw increments rendered frames", ui_loop_redraw_increments_rendered_frames);
    harness.run("thread start stop preserve legacy intents", thread_start_stop_preserve_legacy_intents);
    return harness.finish();
}
