#include "sled/timer/timer.h"
#include "sled/log/log.h"

namespace sled {
namespace {
TimeoutID
MakeTimeoutId(TimerID timer_id, TimerGeneration generation)
{
    return TimeoutID(static_cast<uint64_t>((timer_id << 32) | generation));
}
}// namespace

TimerThreadDeleter::TimerThreadDeleter(TaskQueueBase *owner) : owner_(owner) {}

void
TimerThreadDeleter::operator()(Timer *timer)
{
    if (!timer) { return; }
    if (owner_) {
        owner_->PostTask([timer]() {
            timer->Stop();
            delete timer;
        });
    } else {
        delete timer;
    }
}

Timer::Timer(TimerID id,
             const std::string &name,
             OnExpired on_expired,
             UnregisterHandler unregister_handler,
             std::unique_ptr<Timeout> timeout)
    : id_(id),
      name_(name),
      on_expired_(on_expired),
      unregister_handler_(unregister_handler),
      timeout_(std::move(timeout))
{}

Timer::~Timer()
{
    Stop();
    unregister_handler_();
}

void
Timer::Start()
{
    expiration_count_ = 0;

    if (!is_running()) {
        is_running_ = true;
        generation_ = TimerGeneration(generation_ + 1);
        timeout_->Start(duration_, MakeTimeoutId(id_, generation_));
    } else {
        generation_ = TimerGeneration(generation_ + 1);
        timeout_->Restart(duration_, MakeTimeoutId(id_, generation_));
    }
}

void
Timer::Stop()
{
    if (is_running()) {
        timeout_->Stop();
        generation_       = TimerGeneration(generation_ + 1);
        expiration_count_ = 0;
        is_running_       = false;
    }
}

void
Timer::StartOn(TaskQueueBase *owner)
{
    SLED_ASSERT(owner != nullptr, "owner must not be nullptr");
    owner->BlockingCall([this]() { Start(); });
}

void
Timer::StopOn(TaskQueueBase *owner)
{
    SLED_ASSERT(owner != nullptr, "owner must not be nullptr");
    owner->BlockingCall([this]() { Stop(); });
}

void
Timer::Trigger(TimerGeneration generation)
{
    if (!is_running_ || generation != generation_) { return; }
    ++expiration_count_;
    is_running_ = false;

    // if max_restarts > exppiration_count_ then restart
    {
        is_running_ = true;
        generation_ = TimerGeneration(generation_ + 1);
        timeout_->Start(duration_, MakeTimeoutId(id_, generation_));
    }

    sled::optional<DurationMs> new_duration = on_expired_();
    if (new_duration.has_value() && new_duration != duration_) {
        duration_ = new_duration.value();
        if (is_running_) {
            timeout_->Stop();
            generation_ = TimerGeneration(generation_ + 1);
            timeout_->Start(duration_, MakeTimeoutId(id_, generation_));
        }
    }
}

Timer::Ptr
TimerManager::CreateTimer(const std::string &name, Timer::OnExpired on_expired, TaskQueueBase *owner)
{
    next_id_   = TimerID(next_id_ + 1);
    TimerID id = next_id_;

    std::unique_ptr<Timeout> timeout = timeout_creator_(sled::TaskQueueBase::DelayPrecision::kHigh);
    auto timer                       = new Timer(
        id,
        name,
        std::move(on_expired),
        /* ungrgister_handler=*/[this, id]() { timers_.erase(id); },
        std::move(timeout));
    auto unique_timer = std::unique_ptr<Timer, TimerThreadDeleter>(timer, TimerThreadDeleter(owner));

    timers_[id] = unique_timer.get();
    return unique_timer;
}

Timer::Ptr
TimerManager::CreateTimer(const std::string &name, Timer::OnVoidExpired on_void_expired, TaskQueueBase *owner)
{
    Timer::OnExpired on_expired = [on_void_expired]() {
        on_void_expired();
        return sled::nullopt;
    };

    return CreateTimer(name, on_expired, owner);
}

void
TimerManager::HandleTimeout(TimeoutID id)
{
    TimerID timer_id           = id >> 32;
    TimerGeneration generation = id & 0xffffffff;
    auto it                    = timers_.find(timer_id);
    if (it != timers_.end()) { it->second->Trigger(generation); }
}
}// namespace sled
