#pragma once

#include <condition_variable>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <queue>
#include <stdexcept>
#include <thread>
#include <type_traits>
#include <utility>

namespace renderive::detail {

class Render_Domain final {
public:
    Render_Domain() : thread_([this] { run(); }) {}

    ~Render_Domain() {
        {
            std::lock_guard lock(mutex_);
            stopping_ = true;
        }
        condition_.notify_one();
        if (thread_.joinable())
            thread_.join();
    }

    Render_Domain(const Render_Domain&) = delete;
    Render_Domain& operator=(const Render_Domain&) = delete;

    template <class Function>
    auto invoke(Function&& function) -> std::invoke_result_t<Function> {
        using Result = std::invoke_result_t<Function>;
        auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<Function>(function));
        auto result = task->get_future();
        {
            std::lock_guard lock(mutex_);
            if (stopping_)
                throw std::runtime_error("Render_3D render domain is stopping");
            tasks_.emplace([task] { (*task)(); });
        }
        condition_.notify_one();
        if constexpr (std::is_void_v<Result>) {
            result.get();
        } else {
            return result.get();
        }
    }

private:
    void run() {
        for (;;) {
            std::function<void()> task;
            {
                std::unique_lock lock(mutex_);
                condition_.wait(lock, [&] { return stopping_ || !tasks_.empty(); });
                if (stopping_ && tasks_.empty())
                    return;
                task = std::move(tasks_.front());
                tasks_.pop();
            }
            task();
        }
    }

    std::mutex mutex_;
    std::condition_variable condition_;
    std::queue<std::function<void()>> tasks_;
    bool stopping_{};
    std::thread thread_;
};

} // namespace renderive::detail
