// Copyright 2018 plutoo
#pragma once
#include "kernel/svc.h"
#include "kernel/mutex.h"
#include "kernel/wait.h"

typedef struct WaiterNode WaiterNode;

struct WaiterNode {
    WaitableNode node;
    Waitable* parent;
    Handle thread;
    s32* idx_out;
    s32 idx;
};

struct WaitableMethods {
    bool (* beginWait)(Waitable* ww, WaiterNode* w, u64 cur_tick, u64* next_tick);
    Result (* onTimeout)(Waitable* ww, u64 old_tick);
    Result (* onSignal)(Waitable* ww);
};

static inline void _waitableInitialize(Waitable* ww, const WaitableMethods* vt)
{
    mutexInit(&ww->mutex);
    ww->vt = vt;
    ww->list.next = &ww->list;
    ww->list.prev = &ww->list;
}

static inline void _waitableSignalAllListeners(Waitable* ww)
{
    WaitableNode* node = &ww->list;
    WaitableNode* end = node;

    while (node->next != end) {
        node = node->next;
        WaiterNode* w = (WaiterNode*) node;

        // Try to swap -1 => idx on the waiter thread.
        // If another waitable signals simultaneously only one will win the race and insert its own idx.
        s32 minus_one = -1;
        bool sent_idx = __atomic_compare_exchange_n(
            w->idx_out, &minus_one, w->idx, true, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);

        if (sent_idx)
            svcCancelSynchronization(w->thread);
    }
}

static inline void _waiterNodeInitialize(
    WaiterNode* w, Waitable* parent, Handle thread,
    s32 idx, s32* idx_out)
{
    // Initialize WaiterNode fields
    w->parent = parent;
    w->thread = thread;
    w->idx = idx;
    w->idx_out = idx_out;
}

static inline void _waiterNodeAdd(WaiterNode* w)
{
    // Add WaiterNode to the parent's linked list
    w->node.next = w->parent->list.next;
    w->parent->list.next = &w->node;
    w->node.prev = &w->parent->list;
}

static inline void _waiterNodeRemove(WaiterNode* w)
{
    mutexLock(&w->parent->mutex);
    w->node.prev->next = w->node.next;
    w->node.next->prev = w->node.prev;
    mutexUnlock(&w->parent->mutex);
}