#ifndef TILE_BASE_INTERNAL_TIME_KEEPER_H
#define TILE_BASE_INTERNAL_TIME_KEEPER_H

#pragma once

#include "tile/base/never_destroyed.h"
#include "tile/base/ref_ptr.h"
#include "tile/base/thread/cond_var.h"
#include "tile/base/thread/mutex.h"

#include <atomic>
#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <queue>
#include <thread>

namespace tile {
namespace internal {
using TimerID       = std::uint64_t;
using TimerCallback = std::function<void(TimerID)>;

class TimeKeeper {
public:
    using Clock = std::chrono::steady_clock;
    static TimeKeeper *Instance();

    // internal interface
    void Start();
    void Stop();
    void Join();

    template<typename Rep, typename Period>
    TimerID AddTimer(Clock::time_point expires_at,
                     std::chrono::duration<Rep, Period> interval,
                     TimerCallback cb,
                     bool is_slow_cb)
    {
        return AddTimer(expires_at, std::chrono::duration_cast<std::chrono::nanoseconds>(interval), std::move(cb),
                        is_slow_cb);
    }

    TimerID AddTimer(std::chrono::steady_clock::time_point expires_at,
                     std::chrono::nanoseconds interval,
                     std::function<void()> cb,
                     bool is_slow_cb);
    TimerID
    AddTimer(Clock::time_point expires_at, std::chrono::nanoseconds interval, TimerCallback cb, bool is_slow_cb);

    void KillTimer(TimerID timer_id);

private:
    friend class TimeKeeper_TimerIDConv_Test;
    friend class NeverDestroyedSingleton<TimeKeeper>;

    struct Entry : public RefCounted<Entry> {
        Mutex lock;
        std::atomic<bool> cancelled{false};
        std::atomic<bool> is_slow_cb;
        std::atomic<bool> running{false};

        std::shared_ptr<TimerCallback> cb;
        std::chrono::steady_clock::time_point expires_at;
        std::chrono::nanoseconds interval;
    };

    using EntryPtr = RefPtr<Entry>;

    struct EntryPtrComp {
        bool operator()(const EntryPtr &x, const EntryPtr &y) const { return x->expires_at > y->expires_at; }
    };

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

    void WorkerProc();
    void FireFastTimer(EntryPtr ptr);
    void FireSlowTimer(EntryPtr ptr);

private:
    static std::atomic<bool> exited_;
    Mutex lock_;
    CondVar cv_;
    std::priority_queue<EntryPtr, std::vector<EntryPtr>, EntryPtrComp> timers_;
    std::thread worker_;
};

}// namespace internal
}// namespace tile

#endif// TILE_BASE_INTERNAL_TIME_KEEPER_H
