BareGit
#pragma once

#include <condition_variable>
#include <coroutine>
#include <exception>
#include <mutex>
#include <optional>
#include <utility>

#include <mw/task.hpp>

namespace agent_smith
{
namespace internal
{

template <typename T> class SyncWaitTask
{
public:
    struct promise_type
    {
        SyncWaitTask get_return_object()
        {
            return SyncWaitTask(
                std::coroutine_handle<promise_type>::from_promise(*this));
        }

        std::suspend_always initial_suspend() noexcept
        {
            return {};
        }

        auto final_suspend() noexcept
        {
            struct FinalAwaiter
            {
                bool await_ready() noexcept
                {
                    return false;
                }

                void await_suspend(
                    std::coroutine_handle<promise_type> handle) noexcept
                {
                    auto& promise = handle.promise();
                    std::lock_guard lock(promise.mutex);
                    promise.done = true;
                    promise.condition.notify_one();
                }

                void await_resume() noexcept {}
            };

            return FinalAwaiter{};
        }

        template <typename U> void return_value(U&& value)
        {
            value_.emplace(std::forward<U>(value));
        }

        void unhandled_exception()
        {
            exception_ = std::current_exception();
        }

        std::mutex mutex;
        std::condition_variable condition;
        bool done = false;
        std::optional<T> value_;
        std::exception_ptr exception_;
    };

    explicit SyncWaitTask(std::coroutine_handle<promise_type> handle)
        : handle_(handle)
    {
    }

    SyncWaitTask(SyncWaitTask&& other) noexcept
        : handle_(std::exchange(other.handle_, nullptr))
    {
    }

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

    ~SyncWaitTask()
    {
        if(handle_ != nullptr)
        {
            handle_.destroy();
        }
    }

    void start()
    {
        handle_.resume();
    }

    T get()
    {
        auto& promise = handle_.promise();
        std::unique_lock lock(promise.mutex);
        promise.condition.wait(lock, [&]() { return promise.done; });
        lock.unlock();

        if(promise.exception_ != nullptr)
        {
            std::rethrow_exception(promise.exception_);
        }
        return std::move(*promise.value_);
    }

private:
    std::coroutine_handle<promise_type> handle_ = nullptr;
};

template <typename T> SyncWaitTask<T> makeSyncWaiter(mw::Task<T> task)
{
    co_return co_await std::move(task);
}

} // namespace internal

/// Start a task and block this thread until its result is available.
///
/// Use only at a synchronous application boundary or in tests. Do not call
/// this from a thread that must run work required by the task to complete.
template <typename T> T syncWait(mw::Task<T> task)
{
    auto waiter = internal::makeSyncWaiter(std::move(task));
    waiter.start();
    return waiter.get();
}

} // namespace agent_smith