#pragma once

#include "app_core/app_thread.h"

#include <algorithm>
#include <condition_variable>
#include <cstddef>
#include <deque>
#include <functional>
#include <future>
#include <mutex>
#include <string>
#include <typeinfo>
#include <utility>
#include <thread>

class App;

struct AppTask : public std::packaged_task<void()>
{
    size_t task_id;
#ifdef _DEBUG
    std::string name;
#endif
    template<typename F> AppTask(F f) : std::packaged_task<void()>(f)
    {
        task_id = typeid(f).hash_code();
#ifdef _DEBUG
        name = typeid(f).name();
#endif
    }
};

class AppRuntime
{
public:
    AppRuntime();
    ~AppRuntime();

    [[nodiscard]] bool is_render_thread() const noexcept;
    [[nodiscard]] bool is_ui_thread() const noexcept;
    [[nodiscard]] bool request_redraw() const noexcept { return request_redraw_; }
    void clear_request_redraw() noexcept { request_redraw_ = false; }

    void notify_render_worker() noexcept;
    void notify_ui_worker() noexcept;
    void prepared_file_task(std::function<void()> task);
    void canvas_async_task(std::function<void()> task);
    void main_thread_task(std::packaged_task<void()> task);
    void drain_main_thread_tasks();

    void render_thread_tick(App& app);
    void render_thread_main(App& app, std::stop_token stop_token);
    void render_thread_start(App& app);
    void render_thread_stop();

    void ui_thread_tick(App& app);
    void ui_thread_main(App& app, std::stop_token stop_token);
    void ui_thread_start(App& app);
    void ui_thread_stop();

    template<typename T>
    std::future<void> render_task_async(T task, bool unique = false)
    {
        AppTask pt(task);
        auto f = pt.get_future();
        const auto dispatch = pp::app::plan_app_task_dispatch(
            is_render_thread(),
            unique,
            0U,
            render_running_,
            false,
            false);
        if (dispatch.execute_immediately)
        {
            pt();
        }
        else if (dispatch.queue_task)
        {
            {
                std::lock_guard<std::mutex> lock(render_task_mutex_);
                const auto queue_dispatch = pp::app::plan_app_task_dispatch(
                    false,
                    unique,
                    render_tasklist_.size(),
                    render_running_,
                    false,
                    false);
                if (queue_dispatch.remove_matching_unique_task)
                    render_tasklist_.erase(std::remove_if(render_tasklist_.begin(), render_tasklist_.end(),
                        [id = pt.task_id](AppTask const& t){ return t.task_id == id; }), render_tasklist_.end());
                render_tasklist_.push_back(std::move(pt));
            }
            if (dispatch.notify_worker)
                render_cv_.notify_all();
        }
        return f;
    }

    template<typename T>
    void render_task(T task)
    {
        AppTask pt(task);
        auto f = pt.get_future();
        const auto dispatch = pp::app::plan_app_task_dispatch(
            is_render_thread(),
            false,
            0U,
            render_running_,
            true,
            false);
        if (dispatch.execute_immediately)
        {
            pt();
        }
        else if (dispatch.queue_task)
        {
            {
                std::lock_guard<std::mutex> lock(render_task_mutex_);
                render_tasklist_.push_back(std::move(pt));
            }
            if (dispatch.notify_worker)
                render_cv_.notify_all();
        }
        if (dispatch.wait_for_completion)
            f.get();
    }

    void render_sync()
    {
        render_task([] {});
    }

    template<typename T>
    std::future<void> ui_task_async(T task, bool unique = false)
    {
        AppTask pt(task);
        auto f = pt.get_future();
        const auto dispatch = pp::app::plan_app_task_dispatch(
            is_ui_thread(),
            unique,
            0U,
            ui_running_,
            false,
            false);
        if (dispatch.execute_immediately)
        {
            pt();
        }
        else if (dispatch.queue_task)
        {
            {
                std::lock_guard<std::mutex> lock(ui_task_mutex_);
                const auto queue_dispatch = pp::app::plan_app_task_dispatch(
                    false,
                    unique,
                    ui_tasklist_.size(),
                    ui_running_,
                    false,
                    false);
                if (queue_dispatch.remove_matching_unique_task)
                    ui_tasklist_.erase(std::remove_if(ui_tasklist_.begin(), ui_tasklist_.end(), 
                        [id = pt.task_id](AppTask const& t){ return t.task_id == id; }), ui_tasklist_.end());
                ui_tasklist_.push_back(std::move(pt));
            }
            if (dispatch.notify_worker)
                ui_cv_.notify_all();
        }
        return f;
    }

    template<typename T>
    void ui_task(T task)
    {
        AppTask pt(task);
        auto f = pt.get_future();
        const auto dispatch = pp::app::plan_app_task_dispatch(
            is_ui_thread(),
            false,
            0U,
            ui_running_,
            true,
            true);
        if (dispatch.execute_immediately)
        {
            pt();
        }
        else if (dispatch.queue_task)
        {
            {
                std::lock_guard<std::mutex> lock(ui_task_mutex_);
                ui_tasklist_.push_back(std::move(pt));
            }
            if (dispatch.notify_worker)
                ui_cv_.notify_all();
        }
        if (dispatch.wait_for_completion)
            f.get();
        if (dispatch.request_redraw)
            request_redraw_ = true;
    }

    void ui_sync()
    {
        ui_task([] {});
    }

private:
    void prepared_file_worker_main(std::stop_token stop_token);
    void prepared_file_worker_stop();
    void canvas_async_worker_main(std::stop_token stop_token);
    void canvas_async_worker_stop();

    std::deque<std::function<void()>> prepared_file_tasklist_;
    std::mutex prepared_file_task_mutex_;
    std::condition_variable prepared_file_cv_;
    std::jthread prepared_file_worker_;
    bool prepared_file_running_ = true;

    std::deque<std::function<void()>> canvas_async_tasklist_;
    std::mutex canvas_async_task_mutex_;
    std::condition_variable canvas_async_cv_;
    std::jthread canvas_async_worker_;
    bool canvas_async_running_ = true;

    std::deque<std::packaged_task<void()>> main_thread_tasklist_;
    std::mutex main_thread_task_mutex_;

    std::deque<AppTask> render_tasklist_;
    std::mutex render_task_mutex_;
    std::condition_variable render_cv_;
    std::jthread render_thread_;
    std::thread::id render_thread_id_;
    bool render_running_ = false;

    std::deque<AppTask> ui_tasklist_;
    std::mutex ui_task_mutex_;
    std::condition_variable ui_cv_;
    std::jthread ui_thread_;
    std::thread::id ui_thread_id_;
    bool ui_running_ = false;
    bool request_redraw_ = false;
};
