/**
 * `Time` provides shortcuts for GLib timeout functions.
 */
public class AstalIO.Time : Object {
    private Cancellable cancellable;
    private uint timeout_id;
    private bool fulfilled = false;

    /**
     * Emitted when the timer ticks.
     */
    public signal void now ();

    /**
     * Emitted when the timere is cancelled.
     */
    public signal void cancelled ();

    construct {
        cancellable = new Cancellable();
        cancellable.cancelled.connect(() => {
            if (!fulfilled) {
                Source.remove(timeout_id);
                cancelled();
                dispose();
            }
        });
    }

    private void connect_closure(Closure? closure) {
        if (closure == null)
            return;

        now.connect(() => {
            Value ret = Value(Type.POINTER); // void
            closure.invoke(ref ret, {});
        });
    }

    /**
     * Start an interval timer with default Priority.
     */
    public Time.interval_prio(uint interval, int prio = Priority.DEFAULT, Closure? fn) {
        connect_closure(fn);
        Idle.add_once(() => now());
        timeout_id = Timeout.add(interval, () => {
            now();
            return Source.CONTINUE;
        }, prio);
    }

    /**
     * Start a timeout timer with default Priority.
     */
    public Time.timeout_prio(uint timeout, int prio = Priority.DEFAULT, Closure? fn) {
        connect_closure(fn);
        timeout_id = Timeout.add(timeout, () => {
            now();
            fulfilled = true;
            return Source.REMOVE;
        }, prio);
    }

    /**
     * Start an idle timer with default priority.
     */
    public Time.idle_prio(int prio = Priority.DEFAULT_IDLE, Closure? fn) {
        connect_closure(fn);
        timeout_id = Idle.add(() => {
            now();
            fulfilled = true;
            return Source.REMOVE;
        }, prio);
    }

    /**
     * Start an interval timer. Ticks immediately then every `interval` milliseconds.
     *
     * @param interval Tick every milliseconds.
     * @param fn Optional callback.
     */
    public static Time interval(uint interval, Closure? fn) {
        return new Time.interval_prio(interval, Priority.DEFAULT, fn);
    }

    /**
     * Start a timeout timer which ticks after `timeout` milliseconds.
     *
     * @param timeout Tick after milliseconds.
     * @param fn Optional callback.
     */
    public static Time timeout(uint timeout, Closure? fn) {
        return new Time.timeout_prio(timeout, Priority.DEFAULT, fn);
    }

    /**
     * Start a timer which will tick when there are no higher priority tasks pending.
     *
     * @param fn Optional callback.
     */
    public static Time idle(Closure? fn) {
        return new Time.idle_prio(Priority.DEFAULT_IDLE, fn);
    }

    /**
     * Cancel timer and emit [signal@AstalIO.Time::cancelled]
     */
    public void cancel() {
        cancellable.cancel();
    }
}
