#include "tile/base/internal/time_keeper.h"

#include "tile/base/chrono.h"
#include "tile/base/internal/background_task_host.h"
#include "tile/base/likely.h"
#include "tile/base/thread/latch.h"
#include "tile/base/thread/scoped_lock.h"
#include "tile/base/thread/unique_lock.h"

namespace tile {

namespace internal {

std::atomic<bool> TimeKeeper::exited_{false};

TimeKeeper *
TimeKeeper::Instance()
{
    static NeverDestroyedSingleton<TimeKeeper> time_keeper;
    return time_keeper.Get();
}

void
TimeKeeper::Start()
{
    worker_ = std::thread([&] { WorkerProc(); });
}

void
TimeKeeper::Stop()
{
    exited_.store(true, std::memory_order_relaxed);
    ScopedLock _(lock_);
    cv_.NotifyAll();
}

void
TimeKeeper::Join()
{
    worker_.join();
}

TimerID
TimeKeeper::AddTimer(std::chrono::steady_clock::time_point expires_at,
                     std::chrono::nanoseconds interval,
                     std::function<void()> cb,
                     bool is_slow_cb)
{
    return AddTimer(expires_at, interval, [cb](TimerID) { cb(); }, is_slow_cb);
}

TimerID
TimeKeeper::AddTimer(std::chrono::steady_clock::time_point expires_at,
                     std::chrono::nanoseconds interval,
                     TimerCallback cb,
                     bool is_slow_cb)
{
    if (TILE_UNLIKELY(exited_.load(std::memory_order_relaxed))) {
        TILE_LOG_WARNING("`AddTime()` after `Stop()`");
        return -1;
    }
    EntryPtr ptr = MakeRefCounted<Entry>();

    // TODO: Add GetEntryTimerID()
    auto timer_id = reinterpret_cast<TimerID>(ptr.Get());
    ptr->cb       = std::make_shared<TimerCallback>(std::move(cb));
    ptr->cancelled.store(false, std::memory_order_relaxed);
    ptr->is_slow_cb = is_slow_cb;
    ptr->expires_at = std::max(ReadSteadyClock(), expires_at);
    ptr->interval   = interval;

    {
        ScopedLock _(lock_);
        timers_.push(ptr);
        cv_.NotifyAll();
    }
    return timer_id;
}

void
TimeKeeper::KillTimer(TimerID timer_id)
{
    Entry *timer = reinterpret_cast<Entry *>(timer_id);
    ScopedLock _(timer->lock);
    timer->cb = nullptr;
    timer->cancelled.store(true, std::memory_order_relaxed);
}

TimeKeeper::TimeKeeper() = default;

void
TimeKeeper::WorkerProc()
{
    while (!exited_.load(std::memory_order_relaxed)) {
        UniqueLock<Mutex> lk(lock_);
        auto pred = [&] { return !timers_.empty() && timers_.top()->expires_at <= ReadCoarseSteadyClock(); };

        if (exited_.load(std::memory_order_relaxed)) { break; }

        auto next = timers_.empty() ? ReadCoarseSteadyClock() + std::chrono::seconds(100) : timers_.top()->expires_at;
        if (!cv_.WaitUntil(lk, next, pred)) { continue; }

        auto p = timers_.top();
        timers_.pop();
        lk.Unlock();
        if (!p->is_slow_cb.load(std::memory_order_relaxed)) {
            FireFastTimer(std::move(p));
        } else {
            FireSlowTimer(std::move(p));
        }
    }
}

void
TimeKeeper::FireFastTimer(EntryPtr ptr)
{
    std::shared_ptr<TimerCallback> cb;
    {
        ScopedLock _(ptr->lock);
        cb = ptr->cb;
        if (ptr->cancelled.load(std::memory_order_relaxed)) { return; }
    }

    (*cb)(reinterpret_cast<TimerID>(ptr.Get()));
    ScopedLock _(lock_);
    ScopedLock __(ptr->lock);
    if (!ptr->cancelled.load(std::memory_order_relaxed)) {
        ptr->expires_at += ptr->interval;
        timers_.push(std::move(ptr));
    }
}

void
TimeKeeper::FireSlowTimer(EntryPtr ptr)
{
    BackgroundTaskHost::Instance()->Queue([this, ptr] {
        std::shared_ptr<TimerCallback> cb;
        {
            ScopedLock _(ptr->lock);
            cb = ptr->cb;
            if (ptr->cancelled.load(std::memory_order_relaxed)) { return; }
        }

        (*cb)(reinterpret_cast<TimerID>(ptr.Get()));
        ScopedLock _(lock_);
        ScopedLock __(ptr->lock);
        if (!ptr->cancelled.load(std::memory_order_relaxed)) {
            ptr->expires_at += ptr->interval;
            timers_.push(ptr);
            cv_.NotifyAll();
        }
    });
}

}// namespace internal
}// namespace tile
