#include "pch.h"
#include "app_runtime.h"

#include "app.h"

AppRuntime::AppRuntime()
    : prepared_file_worker_([this](std::stop_token stop_token)
    {
        prepared_file_worker_main(stop_token);
    })
    , canvas_async_worker_([this](std::stop_token stop_token)
    {
        canvas_async_worker_main(stop_token);
    })
{
}

AppRuntime::~AppRuntime()
{
    canvas_async_worker_stop();
    prepared_file_worker_stop();
}

bool AppRuntime::is_render_thread() const noexcept
{
    return std::this_thread::get_id() == render_thread_id_;
}

bool AppRuntime::is_ui_thread() const noexcept
{
    return std::this_thread::get_id() == ui_thread_id_;
}

void AppRuntime::notify_render_worker() noexcept
{
    render_cv_.notify_all();
}

void AppRuntime::notify_ui_worker() noexcept
{
    ui_cv_.notify_all();
}

void AppRuntime::prepared_file_task(std::function<void()> task)
{
    {
        std::lock_guard<std::mutex> lock(prepared_file_task_mutex_);
        if (!prepared_file_running_)
            return;
        prepared_file_tasklist_.push_back(std::move(task));
    }
    prepared_file_cv_.notify_one();
}

void AppRuntime::canvas_async_task(std::function<void()> task)
{
    {
        std::lock_guard<std::mutex> lock(canvas_async_task_mutex_);
        if (!canvas_async_running_)
            return;
        canvas_async_tasklist_.push_back(std::move(task));
    }
    canvas_async_cv_.notify_one();
}

void AppRuntime::main_thread_task(std::packaged_task<void()> task)
{
    std::lock_guard<std::mutex> lock(main_thread_task_mutex_);
    main_thread_tasklist_.push_back(std::move(task));
}

void AppRuntime::drain_main_thread_tasks()
{
    std::deque<std::packaged_task<void()>> tasklist;
    {
        std::lock_guard<std::mutex> lock(main_thread_task_mutex_);
        tasklist = std::move(main_thread_tasklist_);
    }

    while (!tasklist.empty())
    {
        tasklist.front()();
        tasklist.pop_front();
    }
}

void AppRuntime::prepared_file_worker_main(std::stop_token stop_token)
{
    for (;;)
    {
        std::function<void()> task;
        {
            std::unique_lock<std::mutex> lock(prepared_file_task_mutex_);
            prepared_file_cv_.wait(lock, [this, &stop_token]
            {
                return stop_token.stop_requested() || !prepared_file_running_ || !prepared_file_tasklist_.empty();
            });
            if ((stop_token.stop_requested() || !prepared_file_running_) && prepared_file_tasklist_.empty())
                break;
            task = std::move(prepared_file_tasklist_.front());
            prepared_file_tasklist_.pop_front();
        }

        if (task)
        {
            try
            {
                task();
            }
            catch (...)
            {
                LOG("prepared file worker task failed");
            }
        }
    }
}

void AppRuntime::canvas_async_worker_main(std::stop_token stop_token)
{
    for (;;)
    {
        std::function<void()> task;
        {
            std::unique_lock<std::mutex> lock(canvas_async_task_mutex_);
            canvas_async_cv_.wait(lock, [this, &stop_token]
            {
                return stop_token.stop_requested() || !canvas_async_running_ || !canvas_async_tasklist_.empty();
            });
            if ((stop_token.stop_requested() || !canvas_async_running_) && canvas_async_tasklist_.empty())
                break;
            task = std::move(canvas_async_tasklist_.front());
            canvas_async_tasklist_.pop_front();
        }

        if (task)
        {
            try
            {
                task();
            }
            catch (...)
            {
                LOG("canvas async worker task failed");
            }
        }
    }
}

void AppRuntime::prepared_file_worker_stop()
{
    {
        std::lock_guard<std::mutex> lock(prepared_file_task_mutex_);
        prepared_file_running_ = false;
    }
    prepared_file_cv_.notify_all();
    if (prepared_file_worker_.joinable())
    {
        prepared_file_worker_.request_stop();
        prepared_file_worker_.join();
    }
}

void AppRuntime::canvas_async_worker_stop()
{
    {
        std::lock_guard<std::mutex> lock(canvas_async_task_mutex_);
        canvas_async_running_ = false;
    }
    canvas_async_cv_.notify_all();
    if (canvas_async_worker_.joinable())
    {
        canvas_async_worker_.request_stop();
        canvas_async_worker_.join();
    }
}

void AppRuntime::render_thread_tick(App& app)
{
    static uint32_t count = 0;
    render_thread_id_ = std::this_thread::get_id();
    std::deque<AppTask> working_list;
    pp::app::AppQueueDrainPlan drain_plan;

    {
        std::unique_lock<std::mutex> lock(render_task_mutex_);
        drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist_.size());
        render_running_ = drain_plan.mark_running;
        if (!drain_plan.drain_tasks)
            return;
        working_list = std::move(render_tasklist_);
    }

    if (drain_plan.wrap_in_render_context)
    {
        app.async_start();
        while (!working_list.empty())
        {
            count++;
            working_list.front()();
            working_list.pop_front();
        }
        app.async_end();
    }
}

void AppRuntime::render_thread_main(App& app, std::stop_token stop_token)
{
    BT_SetTerminate();

    render_thread_id_ = std::this_thread::get_id();
    render_running_ = pp::app::plan_app_thread_start().mark_running;
    while (render_running_ && !stop_token.stop_requested())
    {
        std::deque<AppTask> working_list;
        pp::app::AppQueueDrainPlan drain_plan;

        {
            std::unique_lock<std::mutex> lock(render_task_mutex_);
            render_cv_.wait(lock, [this, &stop_token]
            {
                return stop_token.stop_requested() || !render_running_ || !render_tasklist_.empty();
            });
            drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist_.size());
            working_list = std::move(render_tasklist_);
        }

        if (drain_plan.wrap_in_render_context)
        {
            app.async_start();
            while (!working_list.empty())
            {
                working_list.front()();
                working_list.pop_front();
            }
            app.async_end();
        }
    }
}

void AppRuntime::ui_thread_tick(App& app)
{
    ui_thread_id_ = std::this_thread::get_id();

    std::deque<AppTask> working_list;
    pp::app::AppUiTickPlan tick_plan;

    {
        std::unique_lock<std::mutex> lock(ui_task_mutex_);
        tick_plan = pp::app::plan_app_ui_thread_tick(ui_tasklist_.size(), app.redraw);
        ui_running_ = tick_plan.mark_running;
        working_list = std::move(ui_tasklist_);
    }

    if (tick_plan.execute_tasks)
    {
        while (!working_list.empty())
        {
            working_list.front()();
            working_list.pop_front();
        }
    }

    if (tick_plan.tick_app)
        app.tick(0);

    const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(app.redraw, 0);
    if (redraw_plan.enqueue_render_frame)
    {
        if (redraw_plan.update_before_render)
            app.update(0);
        app.render_task([&app]
        {
            app.bind_default_render_target();
            app.clear();
            app.draw(0);
            app.async_swap();
        });
    }
}

void AppRuntime::ui_thread_main(App& app, std::stop_token stop_token)
{
    BT_SetTerminate();

    ui_thread_id_ = std::this_thread::get_id();
    ui_running_ = pp::app::plan_app_thread_start().mark_running;

    app.attach_ui_thread();

    LOG("ui thread init()");
    app.init();

    auto t_start = std::chrono::high_resolution_clock::now();
    float t_frame = 0;
    float t_fps_counter = 0;
    float t_reloader = 0;
    int rendered_frames = 0;
    while (ui_running_ && !stop_token.stop_requested())
    {
        std::deque<AppTask> working_list;

        {
            std::unique_lock<std::mutex> lock(ui_task_mutex_);
            ui_cv_.wait_for(lock, std::chrono::milliseconds(app.idle_ms), [this, &stop_token]
            {
                return stop_token.stop_requested() || !ui_running_ || !ui_tasklist_.empty();
            });
            working_list = std::move(ui_tasklist_);
        }

        if (!working_list.empty())
        {
            while (!working_list.empty())
            {
                working_list.front()();
                working_list.pop_front();
            }
        }

        auto t_now = std::chrono::high_resolution_clock::now();
        float dt = std::chrono::duration<float>(t_now - t_start).count();
        t_start = t_now;

        const auto timer_plan = pp::app::plan_app_ui_loop_timers(
            dt,
            t_frame,
            t_fps_counter,
            t_reloader,
            rendered_frames,
            app.platform_enables_live_asset_reloading());
        if (timer_plan) {
            if (timer_plan.value().update_platform_frame)
                app.update_platform_frame(dt);
            t_frame = timer_plan.value().frame_accumulator;
            t_fps_counter = timer_plan.value().fps_accumulator;
            t_reloader = timer_plan.value().reload_accumulator;
            rendered_frames = timer_plan.value().rendered_frames_after_report;

            if (timer_plan.value().report_rendered_frames)
                app.report_rendered_frames(timer_plan.value().reported_frame_count);

            if (timer_plan.value().check_live_asset_reload) {
                if (ShaderManager::reload())
                {
                    app.stroke->update_controls();
                    app.redraw = true;
                }
                if (app.layout.reload())
                    app.redraw = true;
                if (app.layout_designer.reload())
                    app.redraw = true;
            }
        }

        const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(app.redraw, rendered_frames);
        if (redraw_plan.tick_app)
            app.tick(dt);

        if (redraw_plan.enqueue_render_frame)
        {
            if (redraw_plan.update_before_render)
                app.update(t_frame);
            app.render_task([&app, t_frame]
            {
                app.bind_default_render_target();
                app.clear();
                app.draw(t_frame);
                app.async_swap();
            });
            if (redraw_plan.reset_frame_accumulator)
                t_frame = 0;
            rendered_frames = redraw_plan.rendered_frames;
        }
    }
    app.detach_ui_thread();
}

void AppRuntime::render_thread_start(App& app)
{
    const auto plan = pp::app::plan_app_thread_start();
    if (plan.start_thread)
        render_thread_ = std::jthread([this, &app](std::stop_token stop_token)
        {
            render_thread_main(app, stop_token);
        });
    render_running_ = plan.mark_running;
}

void AppRuntime::render_thread_stop()
{
    const auto plan = pp::app::plan_app_thread_stop(render_thread_.joinable());
    if (plan.mark_not_running)
        render_running_ = false;
    if (plan.join_thread)
        render_thread_.request_stop();
    if (plan.notify_worker)
        render_cv_.notify_all();
    if (plan.join_thread)
        render_thread_.join();
}

void AppRuntime::ui_thread_start(App& app)
{
    const auto plan = pp::app::plan_app_thread_start();
    if (plan.start_thread)
        ui_thread_ = std::jthread([this, &app](std::stop_token stop_token)
        {
            ui_thread_main(app, stop_token);
        });
    ui_running_ = plan.mark_running;
}

void AppRuntime::ui_thread_stop()
{
    const auto plan = pp::app::plan_app_thread_stop(ui_thread_.joinable());
    if (plan.mark_not_running)
        ui_running_ = false;
    if (plan.join_thread)
        ui_thread_.request_stop();
    if (plan.notify_worker)
        ui_cv_.notify_all();
    if (plan.join_thread)
        ui_thread_.join();
}
