#include <xs/unievent/Timer.h>
#include <xs/typemap/expected.h>
#include <xs/unievent/Listener.h>
#include <xs/CallbackDispatcher.h>
#include <xs/unievent/error.h>

using namespace xs;
using namespace xs::unievent;
using namespace panda::unievent;

static PERL_ITHREADS_LOCAL struct {
    Simple on_timer = Simple::shared("on_timer");
} cbn;

struct XSTimerListener : ITimerListener, XSListener {
    void on_timer (const TimerSP& h) override {
        call(cbn.on_timer, xs::out(h));
    }
};

static inline uint64_t s2ms (double s) {
    if (s > 0 && s < 0.001) return 1;
    return s * 1000;
}

MODULE = UniEvent::Timer                PACKAGE = UniEvent::Timer
PROTOTYPES: DISABLE

BOOT {
    Stash stash(__PACKAGE__);
    stash.inherit("UniEvent::Handle");
    stash.add_const_sub("TYPE", Simple(Timer::TYPE.name));
    xs::at_perl_destroy([]() { cbn.on_timer = nullptr; });
    unievent::register_perl_class(Timer::TYPE, stash);
}

TimerSP create (SV* proto, double repeat_s, Timer::timer_fn cb, DLoopSP loop = {}) {
    PROTO = proto;
    RETVAL = make_backref<Timer>(loop);
    RETVAL->start(s2ms(repeat_s), cb);
}

TimerSP create_once (SV* proto, double initial_s, Timer::timer_fn cb, DLoopSP loop = {}) {
    PROTO = proto;
    RETVAL = make_backref<Timer>(loop);
    RETVAL->once(s2ms(initial_s), cb);
}

Timer* Timer::new (DLoopSP loop = {}) {
    RETVAL = make_backref<Timer>(loop);
}

XSCallbackDispatcher* Timer::event () {
    RETVAL = XSCallbackDispatcher::create(THIS->event);
}

void Timer::callback (Timer::timer_fn cb) {
    THIS->event.remove_all();
    if (cb) THIS->event.add(cb);
}

Ref Timer::event_listener (Sv lst = Sv(), bool weak = false) {
    RETVAL = event_listener<XSTimerListener>(THIS, ST(0), lst, weak);
}

# $timer->start($repeat, [$callback])
# $timer->start($repeat, $initial, [$callback])
# DEPRECATED!!! UniEvent::Timer->start($repeat, $cb, [$loop])
TimerSP start (Sv proto, double repeat_s, Sv arg2 = {}, Sv arg3 = {}) {
    uint64_t repeat = s2ms(repeat_s);
    if (proto.is_object_ref()) {
        auto& THIS = xs::in<Timer&>(proto);
        uint64_t initial = repeat;
        Timer::timer_fn cb;
        if (arg3) {
            cb = xs::in<Timer::timer_fn>(arg3);
            initial = s2ms(SvNV(arg2));
        } else if (arg2) {
            if (arg2.is_sub_ref()) cb = xs::in<Timer::timer_fn>(arg2);
            else                   initial = s2ms(SvNV(arg2));
        }
        THIS.start(repeat, initial, cb);
        XSRETURN_UNDEF;
    } else {
        panda_log_warn(panda::unievent::panda_log_module, "UniEvent::Timer->start is deprecated, use UniEvent::Timer->create or UniEvent::timer instead");
        if (!arg2) throw "callback must be passed";
        PROTO = proto;
        auto cb = xs::in<Timer::timer_fn&>(arg2);
        DLoopSP loop;
        if (arg3) loop = xs::in<DLoopSP>(arg3);
        RETVAL = make_backref<Timer>(loop);
        RETVAL->event.add(cb);
        RETVAL->start(repeat);
    }
}

# $timer->once($initial, [$callback])
# DEPRECATED!!! UniEvent::Timer->once($initial, $cb, [$loop])
TimerSP once (Sv proto, double initial_s, Timer::timer_fn callback = {}, DLoopSP loop = {}) {
    uint64_t initial = s2ms(initial_s);
    if (proto.is_object_ref()) {
        auto& THIS = xs::in<Timer&>(proto);
        THIS.once(initial, callback);
        XSRETURN_UNDEF;
    } else {
        panda_log_warn(panda::unievent::panda_log_module, "UniEvent::Timer->once is deprecated, use UniEvent::Timer->create_once or UniEvent::timer_once instead");
        if (!callback) throw "callback must be passed";
        PROTO = proto;
        RETVAL = make_backref<Timer>(loop);
        RETVAL->once(initial, callback);
    }
}

void Timer::call_now () {
    THIS->call_now();
    XSRETURN(1);
}

void Timer::stop ()

void Timer::pause ()

void Timer::again () {
    XSRETURN_EXPECTED(THIS->again());
}

void Timer::resume () {
    XSRETURN_EXPECTED(THIS->resume());
}

double Timer::repeat (double new_repeat = -1) {
    if (new_repeat > 0) {
        THIS->repeat(new_repeat*1000);
        XSRETURN_UNDEF;
    }
    RETVAL = ((double)THIS->repeat())/1000;
}

double Timer::due_in () {
    RETVAL = ((double)THIS->due_in())/1000;
}

MODULE = UniEvent::Timer                PACKAGE = UniEvent
PROTOTYPES: DISABLE

TimerSP timer (double repeat_s, Timer::timer_fn cb, DLoopSP loop = {}) {
    RETVAL = make_backref<Timer>(loop);
    RETVAL->start(s2ms(repeat_s), cb);
}

TimerSP timer_once (double initial_s, Timer::timer_fn cb, DLoopSP loop = {}) {
    RETVAL = make_backref<Timer>(loop);
    RETVAL->once(s2ms(initial_s), cb);
}