/*
 * Copyright (C) by Argonne National Laboratory
 *     See COPYRIGHT in top-level directory
 */

#include "mpl.h"

MPL_SUPPRESS_OSX_HAS_NO_SYMBOLS_WARNING;

/* This file currently implements these as a preprocessor if/elif/else sequence.
 * This has the upside of not doing #includes for .c files or (poorly
 * named) .i files.  It has the downside of making this file large-ish
 * and a little harder to read in some cases.  If this becomes
 * unmanagable at some point these should be separated back out into
 * header files and included as needed. [goodell@ 2009-06-24] */

/* Implementation specific function definitions (usually in the form of macros) */

#if MPL_THREAD_PACKAGE_NAME == MPL_THREAD_PACKAGE_WIN

/*
 * struct MPLI_thread_info
 *
 * Structure used to pass the user function and data to the intermediate function, MPLI_thread_start.  See comment in
 * MPLI_thread_start() header for more information.
 */
struct MPLI_thread_info {
    MPL_thread_func_t func;
    void *data;
};


DWORD WINAPI MPLI_thread_start(LPVOID arg);

/*
 * MPL_thread_create()
 */
void MPL_thread_create(MPL_thread_func_t func, void *data, MPL_thread_id_t * idp, int *errp)
{
    struct MPLI_thread_info *thread_info;
    int err = MPL_SUCCESS;

    thread_info =
        (struct MPLI_thread_info *) MPL_malloc(sizeof(struct MPLI_thread_info), MPL_MEM_THREAD);
    if (thread_info != NULL) {
        thread_info->func = func;
        thread_info->data = data;
        *idp = CreateThread(NULL, 0, MPLI_thread_start, thread_info, 0, NULL);
        if (*idp == NULL) {
            err = GetLastError();
        }
    } else {
        err = 1000000000;
    }

    if (errp != NULL) {
        *errp = err;
    }
}


/*
 * MPLI_thread_start()
 *
 * Start functions in Windows are expected to return a DWORD.  Since our start functions do not return a value we must
 * use an intermediate function to perform the call to the user's start function and then return a value of 0.
 */
DWORD WINAPI MPLI_thread_start(LPVOID arg)
{
    struct MPLI_thread_info *thread_info = (struct MPLI_thread_info *) arg;
    MPL_thread_func_t func = thread_info->func;
    void *data = thread_info->data;

    MPL_free(arg);

    func(data);

    return 0;
}

void MPL_thread_exit()
{
    ExitThread(0);
}

void MPL_thread_self(MPL_thread_id_t * id)
{
    *id = GetCurrentThread();
}

void MPL_thread_join(MPL_thread_id_t * id)
{
    WaitForSingleObject(id, INFINITE);
}

void MPL_thread_same(MPL_thread_id_t * id1, MPL_thread_id_t * id2, int *same)
{
    *same = (*id1 == *id2) ? TRUE : FALSE;
}

void MPL_thread_yield(void)
{
    Sleep(0);
}

/*
 *    Mutexes
 */

void MPL_thread_mutex_create(MPL_thread_mutex_t * mutex, int *err)
{
    *mutex = CreateMutex(NULL, FALSE, NULL);
    if (err != NULL) {
        if (*mutex == NULL) {
            *err = GetLastError();
        } else {
            *err = MPL_SUCCESS;
        }
    }
}

void MPL_thread_mutex_destroy(MPL_thread_mutex_t * mutex, int *err)
{
    BOOL result;

    result = CloseHandle(*mutex);
    if (err != NULL) {
        if (result) {
            *err = MPL_SUCCESS;
        } else {
            *err = GetLastError();
        }
    }
}

void MPL_thread_mutex_lock(MPL_thread_mutex_t * mutex, int *err, int prio __attribute__ ((unused)))
{
    DWORD result;

    result = WaitForSingleObject(*mutex, INFINITE);
    if (err != NULL) {
        if (result == WAIT_OBJECT_0) {
            *err = MPL_SUCCESS;
        } else {
            if (result == WAIT_FAILED) {
                *err = GetLastError();
            } else {
                *err = result;
            }
        }
    }
}

void MPL_thread_mutex_unlock(MPL_thread_mutex_t * mutex, int *err)
{
    BOOL result;

    result = ReleaseMutex(*mutex);
    if (err != NULL) {
        if (result) {
            *err = MPL_SUCCESS;
        } else {
            *err = GetLastError();
        }
    }
}


/*
 * Condition Variables
 */

void MPL_thread_cond_create(MPL_thread_cond_t * cond, int *err)
{
    /* Create a tls slot to store the events used to wakeup each thread in cond_bcast or cond_signal */
    MPL_thread_tls_create(NULL, &cond->tls, err);
    if (err != NULL && *err != MPL_SUCCESS) {
        return;
    }
    /* Create a mutex to protect the fifo queue.  This is required because the mutex passed in to the
     * cond functions need not be the same in each thread. */
    MPL_thread_mutex_create(&cond->fifo_mutex, err);
    if (err != NULL && *err != MPL_SUCCESS) {
        return;
    }
    cond->fifo_head = NULL;
    cond->fifo_tail = NULL;
    if (err != NULL) {
        *err = MPL_SUCCESS;
    }
}

void MPL_thread_cond_destroy(MPL_thread_cond_t * cond, int *err)
{
    MPLI_win_thread_cond_fifo_t *iter;

    while (cond->fifo_head) {
        iter = cond->fifo_head;
        cond->fifo_head = cond->fifo_head->next;
        MPL_free(iter);
    }
    MPL_thread_mutex_destroy(&cond->fifo_mutex, err);
    if (err != NULL && *err != MPL_SUCCESS) {
        return;
    }
    MPL_thread_tls_destroy(&cond->tls, err);
    /*
     * if (err != NULL)
     * {
     * *err = MPL_SUCCESS;
     * }
     */
}

void MPL_thread_cond_wait(MPL_thread_cond_t * cond, MPL_thread_mutex_t * mutex, int *err)
{
    HANDLE event;
    DWORD result;
    MPL_thread_tls_get(&cond->tls, &event, err);
    if (err != NULL && *err != MPL_SUCCESS) {
        return;
    }
    if (event == NULL) {
        event = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (event == NULL) {
            if (err != NULL) {
                *err = GetLastError();
            }
            return;
        }
        MPL_thread_tls_set(&cond->tls, event, err);
        if (err != NULL && *err != MPL_SUCCESS) {
            return;
        }
    }
    MPL_thread_mutex_lock(&cond->fifo_mutex, err, MPL_THREAD_PRIO_HIGH);
    if (err != NULL && *err != MPL_SUCCESS) {
        return;
    }
    if (cond->fifo_tail == NULL) {
        cond->fifo_tail =
            (MPLI_win_thread_cond_fifo_t *) MPL_malloc(sizeof(MPLI_win_thread_cond_fifo_t),
                                                       MPL_MEM_THREAD);
        cond->fifo_head = cond->fifo_tail;
    } else {
        cond->fifo_tail->next =
            (MPLI_win_thread_cond_fifo_t *) MPL_malloc(sizeof(MPLI_win_thread_cond_fifo_t),
                                                       MPL_MEM_THREAD);
        cond->fifo_tail = cond->fifo_tail->next;
    }
    if (cond->fifo_tail == NULL) {
        if (err != NULL) {
            *err = -1;
        }
        return;
    }
    cond->fifo_tail->event = event;
    cond->fifo_tail->next = NULL;
    MPL_thread_mutex_unlock(&cond->fifo_mutex, err);
    if (err != NULL && *err != MPL_SUCCESS) {
        return;
    }
    MPL_thread_mutex_unlock(mutex, err);
    if (err != NULL && *err != MPL_SUCCESS) {
        return;
    }
    result = WaitForSingleObject(event, INFINITE);
    if (err != NULL) {
        if (result != WAIT_OBJECT_0) {
            if (result == WAIT_FAILED) {
                *err = GetLastError();
            } else {
                *err = result;
            }
            return;
        }
    }
    result = ResetEvent(event);
    if (!result && err != NULL) {
        *err = GetLastError();
        return;
    }
    MPL_thread_mutex_lock(mutex, err, MPL_THREAD_PRIO_HIGH);
    /*
     * if (err != NULL)
     * {
     * *err = MPL_SUCCESS;
     * }
     */
}

void MPL_thread_cond_broadcast(MPL_thread_cond_t * cond, int *err)
{
    MPLI_win_thread_cond_fifo_t *fifo, *temp;
    MPL_thread_mutex_lock(&cond->fifo_mutex, err, MPL_THREAD_PRIO_HIGH);
    if (err != NULL && *err != MPL_SUCCESS) {
        return;
    }
    /* remove the fifo queue from the cond variable */
    fifo = cond->fifo_head;
    cond->fifo_head = cond->fifo_tail = NULL;
    MPL_thread_mutex_unlock(&cond->fifo_mutex, err);
    if (err != NULL && *err != MPL_SUCCESS) {
        return;
    }
    /* signal each event in the fifo queue */
    while (fifo) {
        if (!SetEvent(fifo->event) && err != NULL) {
            *err = GetLastError();
            /* lost memory */
            return;
        }
        temp = fifo;
        fifo = fifo->next;
        MPL_free(temp);
    }
    if (err != NULL) {
        *err = MPL_SUCCESS;
    }
}

void MPL_thread_cond_signal(MPL_thread_cond_t * cond, int *err)
{
    MPLI_win_thread_cond_fifo_t *fifo;
    MPL_thread_mutex_lock(&cond->fifo_mutex, err, MPL_THREAD_PRIO_HIGH);
    if (err != NULL && *err != MPL_SUCCESS) {
        return;
    }
    fifo = cond->fifo_head;
    if (fifo) {
        cond->fifo_head = cond->fifo_head->next;
        if (cond->fifo_head == NULL)
            cond->fifo_tail = NULL;
    }
    MPL_thread_mutex_unlock(&cond->fifo_mutex, err);
    if (err != NULL && *err != MPL_SUCCESS) {
        return;
    }
    if (fifo) {
        if (!SetEvent(fifo->event) && err != NULL) {
            *err = GetLastError();
            MPL_free(fifo);
            return;
        }
        MPL_free(fifo);
    }
    if (err != NULL) {
        *err = MPL_SUCCESS;
    }
}

void MPL_thread_set_affinity(MPL_thread_id_t thread, int *affinity_arr, int affinity_size, int *err)
{
    /* stub implementation */
    if (err)
        *err = MPL_ERR_THREAD;
}

#endif
