// Copyright 2018 plutoo
#include "result.h"
#include "arm/counter.h"
#include "kernel/svc.h"
#include "kernel/utimer.h"
#include "wait.h"

static bool _utimerBeginWait(Waitable* ww, WaiterNode* w, u64 cur_tick, u64* next_tick);
static Result _utimerOnTimeout(Waitable* ww, u64 old_tick);
static Result _utimerOnSignal(Waitable* ww);

static const WaitableMethods g_utimerVt = {
    .beginWait = _utimerBeginWait,
    .onTimeout = _utimerOnTimeout,
    .onSignal = _utimerOnSignal,
};

void utimerCreate(UTimer* t, u64 interval, TimerType type)
{
    _waitableInitialize(&t->waitable, &g_utimerVt);

    t->started = false;
    t->next_tick = 0;
    t->interval = armNsToTicks(interval);
    t->type = type;
}

void utimerStart(UTimer* t)
{
    mutexLock(&t->waitable.mutex);

    if (!t->started) {
        t->started = true;
        t->next_tick = armGetSystemTick() + t->interval;
        _waitableSignalAllListeners(&t->waitable);
    }

    mutexUnlock(&t->waitable.mutex);
}

void utimerStop(UTimer* t)
{
    mutexLock(&t->waitable.mutex);

    if (t->started) {
        t->started = false;
        t->next_tick = 0;
        _waitableSignalAllListeners(&t->waitable);
    }

    mutexUnlock(&t->waitable.mutex);
}

static void _utimerRecalculate(UTimer* t, u64 old_tick)
{
    if (t->started && t->next_tick == old_tick) {
        u64 interval = t->interval;

        switch (t->type) {
            case TimerType_OneShot:
                t->started = false;
                t->next_tick = 0;
                break;
            case TimerType_Repeating:
                t->next_tick = old_tick + ((armGetSystemTick() - old_tick + interval - 1)/interval)*interval;
                break;
        }
    }
}

static bool _utimerBeginWait(Waitable* ww, WaiterNode* w, u64 cur_tick, u64* next_tick)
{
    UTimer* t = (UTimer*)ww;
    mutexLock(&t->waitable.mutex);

    // By default we do want to perform a wait, because if the
    // timer's start/stop state changes we want to detect that.
    u64 timer_tick = t->next_tick;
    bool do_wait = true;

    // Skip timer if stopped.
    if (t->started) {
        s64 diff = timer_tick - cur_tick;

        // If the timer is already signalled, we're done.
        if (diff < 0) {
            _utimerRecalculate(t, timer_tick);
            do_wait = false;
        } else
            *next_tick = diff;
    }

    if (do_wait)
        _waiterNodeAdd(w);

    mutexUnlock(&t->waitable.mutex);
    return do_wait;
}

static Result _utimerOnTimeout(Waitable* ww, u64 old_tick)
{
    UTimer* t = (UTimer*)ww;
    mutexLock(&t->waitable.mutex);
    _utimerRecalculate(t, old_tick);
    mutexUnlock(&t->waitable.mutex);
    return 0;
}

static Result _utimerOnSignal(Waitable* ww)
{
    // Timer state changed, so we need to retry the wait.
    return KERNELRESULT(Cancelled);
}