BareGit
#include "timer_service.hpp"
#include <algorithm>

namespace agent_smith::internal
{

TimerService::TimerService()
    : worker_([this](std::stop_token stop) { worker(stop); })
{
}

TimerService::~TimerService()
{
    {
        std::lock_guard lock(mutex_);
        worker_.request_stop();
    }
    condition_.notify_all();
}

TimerService::SleepAwaiter::SleepAwaiter(TimerService& service,
                                         std::chrono::milliseconds duration,
                                         std::stop_token stop)
    : service_(&service), duration_(duration), entry_(std::make_shared<Entry>())
{
    entry_->stop = stop;
    callback_ = std::make_unique<std::stop_callback<std::function<void()>>>(
        stop, [&service]()
        {
            std::lock_guard lock(service.mutex_);
            service.condition_.notify_one();
        });
}

bool TimerService::SleepAwaiter::await_ready() const
{
    return duration_ <= std::chrono::milliseconds::zero() ||
           entry_->stop.stop_requested();
}

bool TimerService::SleepAwaiter::await_suspend(
    std::coroutine_handle<> continuation)
{
    entry_->deadline = std::chrono::steady_clock::now() + duration_;
    service_->schedule(entry_, continuation);
    return true;
}

TimerService::SleepAwaiter
TimerService::sleepFor(std::chrono::milliseconds duration,
                      std::stop_token stop)
{
    return SleepAwaiter(*this, duration, stop);
}

void TimerService::schedule(const std::shared_ptr<Entry>& entry,
                            std::coroutine_handle<> continuation)
{
    {
        std::lock_guard lock(mutex_);
        entry->continuation = continuation;
        entries_.push_back(entry);
    }
    condition_.notify_one();
}

void TimerService::worker(std::stop_token stop)
{
    std::unique_lock lock(mutex_);
    while(!stop.stop_requested())
    {
        if(entries_.empty())
        {
            condition_.wait(
                lock,
                [&]() { return stop.stop_requested() || !entries_.empty(); });
            continue;
        }

        const auto now = std::chrono::steady_clock::now();
        auto ready = std::find_if(entries_.begin(), entries_.end(),
            [now](const auto& entry)
            {
                return entry->stop.stop_requested() || entry->deadline <= now;
            });
        if(ready == entries_.end())
        {
            const auto earliest = std::min_element(
                entries_.begin(), entries_.end(),
                [](const auto& lhs, const auto& rhs)
                { return lhs->deadline < rhs->deadline; });
            condition_.wait_until(lock, (*earliest)->deadline);
            continue;
        }

        auto entry = *ready;
        entries_.erase(ready);
        auto continuation = entry->continuation;
        lock.unlock();
        if(continuation != nullptr)
        {
            continuation.resume();
        }
        lock.lock();
    }
}

} // namespace agent_smith::internal