/**
 * \file
 * \brief Thread synchronisation primitives.
 */

/*
 * Copyright (c) 2007, 2008, 2009, 2010, 2012, ETH Zurich.
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached LICENSE file.
 * If you do not find this file, copies can be found by writing to:
 * ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
 */

#include <aos/aos.h>
#include <aos/dispatch.h>
#include <aos/dispatcher_arch.h>
#include "threads_priv.h"

/**
 * \brief Initialise a condition variable
 *
 * \param cond Condition variable pointer
 */
void thread_cond_init(struct thread_cond *cond)
{
    cond->queue = NULL;
    cond->lock = 0;
}

/**
 * \brief Wait for a condition variable
 *
 * This function waits for the given condition variable to be signalled
 * (through thread_cond_signal() or thread_cond_broadcast()) before
 * returning, while atomically unlocking the mutex pointed to by
 * 'mutex'.
 *
 * \param cond  Condition variable pointer
 * \param mutex Optional pointer to mutex to unlock.
 */
void thread_cond_wait(struct thread_cond *cond, struct thread_mutex *mutex)
{
    dispatcher_handle_t disp = disp_disable();

    acquire_spinlock(&cond->lock);

    // Release the lock
    if (mutex != NULL) {
        struct thread *wakeup = thread_mutex_unlock_disabled(disp, mutex);

        if(wakeup != NULL) {
            assert(disp == wakeup->disp);
            thread_resume(wakeup);
        }
    }

    // Block on the condition variable and release spinlock
    thread_block_and_release_spinlock_disabled(disp, &cond->queue, &cond->lock);

    // Re-acquire the mutex
    if (mutex != NULL) {
        thread_mutex_lock(mutex);
    }

}

/**
 * \brief Signal a condition variable
 *
 * This function signals the condition variable, and wakes up one
 * thread waiting on it.
 *
 * \param cond Condition variable pointer
 */
void thread_cond_signal(struct thread_cond *cond)
{
    struct thread *wakeup = NULL;
    errval_t err = SYS_ERR_OK;

    // Wakeup one waiting thread
    dispatcher_handle_t disp = disp_disable();
    acquire_spinlock(&cond->lock);
    if (cond->queue != NULL) {
        wakeup = thread_unblock_one_disabled(disp, &cond->queue, NULL);
        if(wakeup != NULL) {
            thread_resume(wakeup);
        }
    }
    release_spinlock(&cond->lock);
    disp_enable(disp);

    if(err_is_fail(err)) {
        USER_PANIC_ERR(err, "remote wakeup from condition signal");
    }

    if(wakeup != NULL) {
        // XXX: Need directed yield to inter-disp thread
        thread_yield();
    }
}

/**
 * \brief Broadcast signal a condition variable
 *
 * This function signals the condition variable, and wakes up all
 * threads waiting on it.
 *
 * \param cond Condition variable pointer
 */
void thread_cond_broadcast(struct thread_cond *cond)
{
    struct thread *wakeupq = NULL;
    bool foreignwakeup = false;

    // Wakeup all waiting threads
    dispatcher_handle_t disp = disp_disable();
    acquire_spinlock(&cond->lock);
    wakeupq = thread_unblock_all_disabled(disp, &cond->queue, NULL);
    release_spinlock(&cond->lock);
    disp_enable(disp);

    foreignwakeup = (wakeupq != NULL);
    // Now, wakeup all on foreign dispatchers
    while (wakeupq != NULL) {
        struct thread *wakeup = wakeupq;
        wakeupq = wakeupq->next;
        thread_resume(wakeup);
    }

    if(foreignwakeup) {
        // XXX: Need directed yield to inter-disp thread
        thread_yield();
    }
}

/**
 * \brief Initialise a mutex
 *
 * \param mutex Mutex pointer
 */
void thread_mutex_init(struct thread_mutex *mutex)
{
    mutex->locked = 0;
    mutex->holder = NULL;
    mutex->queue = NULL;
    mutex->lock = 0;
}

/**
 * \brief Lock a mutex
 *
 * This blocks until the given mutex is unlocked, and then atomically locks it.
 *
 * \param mutex Mutex pointer
 */
void thread_mutex_lock(struct thread_mutex *mutex)
{
    dispatcher_handle_t handle = disp_disable();
    struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);

    acquire_spinlock(&mutex->lock);
    if (mutex->locked > 0) {
        thread_block_and_release_spinlock_disabled(handle, &mutex->queue,
                                                   &mutex->lock);
    } else {
        mutex->locked = 1;
        mutex->holder = disp_gen->current;
        release_spinlock(&mutex->lock);
        disp_enable(handle);
    }
}

/**
 * \brief Lock a mutex
 *
 * This blocks until the given mutex is unlocked, and then atomically locks it.
 *
 * \param mutex Mutex pointer
 */
void thread_mutex_lock_nested(struct thread_mutex *mutex)
{
    dispatcher_handle_t handle = disp_disable();
    struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);

    acquire_spinlock(&mutex->lock);
    if (mutex->locked > 0
        && mutex->holder != disp_gen->current) {
        thread_block_and_release_spinlock_disabled(handle, &mutex->queue,
                                                   &mutex->lock);
    } else {
        mutex->locked++;
        mutex->holder = disp_gen->current;
        release_spinlock(&mutex->lock);
        disp_enable(handle);
    }
}

/**
 * \brief Try to lock a mutex
 *
 * If the given mutex is unlocked, this atomically locks it and returns true,
 * otherwise it returns false immediately.
 *
 * \param mutex Mutex pointer
 *
 * \returns true if lock acquired, false otherwise
 */
bool thread_mutex_trylock(struct thread_mutex *mutex)
{

    // Try first to avoid contention
    if (mutex->locked > 0) {
        return false;
    }

    dispatcher_handle_t handle = disp_disable();
    struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
    bool ret;

    acquire_spinlock(&mutex->lock);
    if (mutex->locked > 0) {
        ret = false;
    } else {
        ret = true;
        mutex->locked = 1;
        mutex->holder = disp_gen->current;
    }
    release_spinlock(&mutex->lock);

    disp_enable(handle);
    return ret;
}

/**
 * \brief Unlock a mutex, while disabled
 *
 * This function unlocks the given mutex. It may only be called while disabled.
 *
 * \param disp Dispatcher pointer
 * \param mutex Mutex pointer
 *
 * \return Pointer to thread to be woken on foreign dispatcher
 */
struct thread *thread_mutex_unlock_disabled(dispatcher_handle_t handle,
                                            struct thread_mutex *mutex)
{
    struct thread *ft = NULL;


    acquire_spinlock(&mutex->lock);
    assert_disabled(mutex->locked > 0);

    if(mutex->locked == 1) {
        // Wakeup one waiting thread
        if (mutex->queue != NULL) {
            // XXX: This assumes dequeueing is off the top of the queue
            mutex->holder = mutex->queue;
            ft = thread_unblock_one_disabled(handle, &mutex->queue, NULL);
        } else {
            mutex->holder = NULL;
            mutex->locked = 0;
        }
    } else {
        mutex->locked--;
    }

    release_spinlock(&mutex->lock);
    return ft;
}

/**
 * \brief Unlock a mutex
 *
 * This unlocks the given mutex.
 *
 * \param mutex Mutex pointer
 */
void thread_mutex_unlock(struct thread_mutex *mutex)
{
    dispatcher_handle_t disp = disp_disable();
    struct thread *wakeup = thread_mutex_unlock_disabled(disp, mutex);
    errval_t err = SYS_ERR_OK;

    if (wakeup != NULL) {
        thread_resume(wakeup);
    }
    disp_enable(disp);

    if(err_is_fail(err)) {
        USER_PANIC_ERR(err, "remote wakeup from mutex unlock");
    }

    if(wakeup != NULL) {
        // XXX: Need directed yield to inter-disp thread
        thread_yield();
    }
}

void thread_sem_init(struct thread_sem *sem, unsigned int value)
{
    assert(sem != NULL);

    sem->value = value;
    sem->queue = NULL;
    sem->lock = 0;
}

void thread_sem_wait(struct thread_sem *sem)
{
    assert(sem != NULL);


    dispatcher_handle_t disp = disp_disable();
    acquire_spinlock(&sem->lock);

    if(sem->value < 1) {
        // Not possible to decrement -- wait!
        thread_block_and_release_spinlock_disabled(disp, &sem->queue, &sem->lock);
    } else {
        // Decrement possible
        sem->value--;
        release_spinlock(&sem->lock);
        disp_enable(disp);
    }

}

bool thread_sem_trywait(struct thread_sem *sem)
{
    assert(sem != NULL);
    bool ret = false;


    dispatcher_handle_t disp = disp_disable();
    acquire_spinlock(&sem->lock);

    if(sem->value >= 1) {
        // Decrement possible
        sem->value--;
        ret = true;
    }

    release_spinlock(&sem->lock);
    disp_enable(disp);

    return ret;
}

void thread_sem_post(struct thread_sem *sem)
{
    assert(sem != NULL);

    dispatcher_handle_t disp = disp_disable();
    struct thread *wakeup = NULL;
    errval_t err = SYS_ERR_OK;
    acquire_spinlock(&sem->lock);

    // Wakeup one?
    if(sem->value == 0 && sem->queue != NULL) {
        wakeup = thread_unblock_one_disabled(disp, &sem->queue, NULL);
    } else {
        sem->value++;
    }

    if(wakeup != NULL) {
        thread_resume(wakeup);
    }

    release_spinlock(&sem->lock);
    disp_enable(disp);

    if(err_is_fail(err)) {
        USER_PANIC_ERR(err, "remote wakeup from semaphore post");
    }

    if(wakeup != NULL) {
        // XXX: Need directed yield to inter-disp thread
        thread_yield();
    }
}
