/*
 * Code for simulating pthreads API on Windows.  This is Git-specific,
 * but it is enough for Numexpr needs too.
 *
 * Copyright (C) 2009 Andrzej K. Haczewski <ahaczewski@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * DISCLAIMER: The implementation is Git-specific, it is subset of original
 * Pthreads API, without lots of other features that Git doesn't use.
 * Git also makes sure that the passed arguments are valid, so there's
 * no need for double-checking.
 */

#include "pthread.h"

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <process.h>


void die(const char *err, ...)
{
    printf("%s", err);
    exit(-1);
}

static unsigned __stdcall win32_start_routine(void *arg)
{
    pthread_t *thread = arg;
    thread->arg = thread->start_routine(thread->arg);
    return 0;
}

int pthread_create(pthread_t *thread, const void *unused,
           void *(*start_routine)(void*), void *arg)
{
    thread->arg = arg;
    thread->start_routine = start_routine;
    thread->handle = (HANDLE)
        _beginthreadex(NULL, 0, win32_start_routine, thread, 0, NULL);

    if (!thread->handle)
        return errno;
    else
        return 0;
}

int win32_pthread_join(pthread_t *thread, void **value_ptr)
{
    DWORD result = WaitForSingleObject(thread->handle, INFINITE);
    switch (result) {
        case WAIT_OBJECT_0:
            if (value_ptr)
                *value_ptr = thread->arg;
            return 0;
        case WAIT_ABANDONED:
            return EINVAL;
        default:
            return GetLastError();
    }
}

int pthread_cond_init(pthread_cond_t *cond, const void *unused)
{
    cond->waiters = 0;
    cond->was_broadcast = 0;
    InitializeCriticalSection(&cond->waiters_lock);

    cond->sema = CreateSemaphore(NULL, 0, LONG_MAX, NULL);
    if (!cond->sema)
        die("CreateSemaphore() failed");

    cond->continue_broadcast = CreateEvent(NULL,    /* security */
                FALSE,            /* auto-reset */
                FALSE,            /* not signaled */
                NULL);            /* name */
    if (!cond->continue_broadcast)
        die("CreateEvent() failed");

    return 0;
}

int pthread_cond_destroy(pthread_cond_t *cond)
{
    CloseHandle(cond->sema);
    CloseHandle(cond->continue_broadcast);
    DeleteCriticalSection(&cond->waiters_lock);
    return 0;
}

int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex)
{
    int last_waiter;

    EnterCriticalSection(&cond->waiters_lock);
    cond->waiters++;
    LeaveCriticalSection(&cond->waiters_lock);

    /*
     * Unlock external mutex and wait for signal.
     * NOTE: we've held mutex locked long enough to increment
     * waiters count above, so there's no problem with
     * leaving mutex unlocked before we wait on semaphore.
     */
    LeaveCriticalSection(mutex);

    /* let's wait - ignore return value */
    WaitForSingleObject(cond->sema, INFINITE);

    /*
     * Decrease waiters count. If we are the last waiter, then we must
     * notify the broadcasting thread that it can continue.
     * But if we continued due to cond_signal, we do not have to do that
     * because the signaling thread knows that only one waiter continued.
     */
    EnterCriticalSection(&cond->waiters_lock);
    cond->waiters--;
    last_waiter = cond->was_broadcast && cond->waiters == 0;
    LeaveCriticalSection(&cond->waiters_lock);

    if (last_waiter) {
        /*
         * cond_broadcast was issued while mutex was held. This means
         * that all other waiters have continued, but are contending
         * for the mutex at the end of this function because the
         * broadcasting thread did not leave cond_broadcast, yet.
         * (This is so that it can be sure that each waiter has
         * consumed exactly one slice of the semaphor.)
         * The last waiter must tell the broadcasting thread that it
         * can go on.
         */
        SetEvent(cond->continue_broadcast);
        /*
         * Now we go on to contend with all other waiters for
         * the mutex. Auf in den Kampf!
         */
    }
    /* lock external mutex again */
    EnterCriticalSection(mutex);

    return 0;
}

/*
 * IMPORTANT: This implementation requires that pthread_cond_signal
 * is called while the mutex is held that is used in the corresponding
 * pthread_cond_wait calls!
 */
int pthread_cond_signal(pthread_cond_t *cond)
{
    int have_waiters;

    EnterCriticalSection(&cond->waiters_lock);
    have_waiters = cond->waiters > 0;
    LeaveCriticalSection(&cond->waiters_lock);

    /*
     * Signal only when there are waiters
     */
    if (have_waiters)
        return ReleaseSemaphore(cond->sema, 1, NULL) ?
            0 : GetLastError();
    else
        return 0;
}

/*
 * DOUBLY IMPORTANT: This implementation requires that pthread_cond_broadcast
 * is called while the mutex is held that is used in the corresponding
 * pthread_cond_wait calls!
 */
int pthread_cond_broadcast(pthread_cond_t *cond)
{
    EnterCriticalSection(&cond->waiters_lock);

    if ((cond->was_broadcast = cond->waiters > 0)) {
        /* wake up all waiters */
        ReleaseSemaphore(cond->sema, cond->waiters, NULL);
        LeaveCriticalSection(&cond->waiters_lock);
        /*
         * At this point all waiters continue. Each one takes its
         * slice of the semaphor. Now it's our turn to wait: Since
         * the external mutex is held, no thread can leave cond_wait,
         * yet. For this reason, we can be sure that no thread gets
         * a chance to eat *more* than one slice. OTOH, it means
         * that the last waiter must send us a wake-up.
         */
        WaitForSingleObject(cond->continue_broadcast, INFINITE);
        /*
         * Since the external mutex is held, no thread can enter
         * cond_wait, and, hence, it is safe to reset this flag
         * without cond->waiters_lock held.
         */
        cond->was_broadcast = 0;
    } else {
        LeaveCriticalSection(&cond->waiters_lock);
    }
    return 0;
}
