#pragma once
#ifndef SLED_TIMER_TIMER_H
#define SLED_TIMER_TIMER_H

#include "timeout.h"
#include <atomic>
#include <map>
#include <memory>
#include <sled/optional.h>
#include <sled/task_queue/task_queue_base.h>
#include <stdint.h>

namespace sled {
typedef uint64_t TimerID;
typedef uint32_t TimerGeneration;

class Timer;

struct TimerThreadDeleter {
    TimerThreadDeleter() = default;
    TimerThreadDeleter(TaskQueueBase *owner);
    void operator()(Timer *timer);

private:
    TaskQueueBase *owner_ = nullptr;
};

class Timer {
public:
    using Ptr = std::unique_ptr<Timer, TimerThreadDeleter>;

    using OnExpired                 = std::function<sled::optional<DurationMs>()>;
    using OnVoidExpired             = std::function<void()>;
    Timer(const Timer &)            = delete;
    Timer &operator=(const Timer &) = delete;
    ~Timer();
    void Start();
    void Stop();

    void StartOn(TaskQueueBase *owner);
    void StopOn(TaskQueueBase *owner);

    void set_duration(DurationMs duration) { duration_ = duration; }

    const DurationMs duration() const { return duration_; }

    int expiration_count() const { return expiration_count_; }

    bool is_running() const { return is_running_; }

private:
    friend class TimerManager;
    using UnregisterHandler = std::function<void()>;
    Timer(TimerID id,
          const std::string &name,
          OnExpired on_expired,
          UnregisterHandler unregister_handler,
          std::unique_ptr<Timeout> timeout);
    void Trigger(TimerGeneration generation);

    const TimerID id_;
    const std::string name_;
    const OnExpired on_expired_;
    const UnregisterHandler unregister_handler_;
    std::unique_ptr<Timeout> timeout_;

    std::atomic<DurationMs> duration_;

    TimerGeneration generation_ = TimerGeneration(0);
    bool is_running_            = false;
    int expiration_count_       = 0;
};

class TimerManager {
    using TimeoutCreator = std::function<std::unique_ptr<Timeout>(sled::TaskQueueBase::DelayPrecision)>;

public:
    explicit TimerManager(TimeoutCreator timeout_creator) : timeout_creator_(timeout_creator) {}

    std::unique_ptr<Timer, TimerThreadDeleter>
    CreateTimer(const std::string &name, Timer::OnExpired on_expired, TaskQueueBase *owner = nullptr);
    std::unique_ptr<Timer, TimerThreadDeleter>
    CreateTimer(const std::string &name, Timer::OnVoidExpired on_void_expired, TaskQueueBase *owner = nullptr);
    void HandleTimeout(TimeoutID timeout_id);

private:
    const TimeoutCreator timeout_creator_;
    std::map<TimerID, Timer *> timers_;
    TimerID next_id_ = TimerID(0);
};
}// namespace sled
#endif//  SLED_TIMER_TIMER_H
