#include "runtime.h"
#include "threading.h"

#ifdef __linux__

#include <pthread.h>
struct rt_condition_var_s {
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    ptrdiff_t next_reusable;
};

#define MAX_CONDS 4096
rt_condition_var _conds[MAX_CONDS];
static ptrdiff_t _first_reusable = MAX_CONDS;
static ptrdiff_t _next           = 0;
static pthread_mutex_t _guard    = PTHREAD_MUTEX_INITIALIZER;

RT_DLLEXPORT rt_condition_var *rtCreateConditionVar(void) {
    pthread_mutex_lock(&_guard);
    if (_first_reusable < MAX_CONDS) {
        rt_condition_var *cond = &_conds[_first_reusable];
        _first_reusable        = cond->next_reusable;
        pthread_mutex_unlock(&_guard);
        return cond;
    } else if (_next < MAX_CONDS) {
        rt_condition_var *cond = &_conds[_next];
        if (pthread_mutex_init(&cond->mutex, NULL) != 0) {
            rtLog("core", "Condition variable creation failed");
            pthread_mutex_unlock(&_guard);
            return NULL;
        }
        if (pthread_cond_init(&cond->cond, NULL) != 0) {
            rtLog("core", "Condition variable creation failed");
        }
        cond->next_reusable = MAX_CONDS;
        ++_next;
        pthread_mutex_unlock(&_guard);
        return cond;
    }
    rtReportError("core", "Ran out of condition variable objects");
    pthread_mutex_unlock(&_guard);
    return NULL;
}

RT_DLLEXPORT void rtDestroyConditionVar(rt_condition_var *var) {
    ptrdiff_t index = var - &_conds[0];
    pthread_mutex_lock(&_guard);
    var->next_reusable = _first_reusable;
    _first_reusable    = index;
    pthread_mutex_unlock(&_guard);
}

RT_DLLEXPORT void rtLockConditionVar(rt_condition_var *var) {
    pthread_mutex_lock(&var->mutex);
}

RT_DLLEXPORT void rtUnlockConditionVar(rt_condition_var *var, bool signal) {
    if (signal)
        pthread_cond_signal(&var->cond);
    pthread_mutex_unlock(&var->mutex);
}

RT_DLLEXPORT void rtWaitOnConditionVar(rt_condition_var *var) {
    pthread_cond_wait(&var->cond, &var->mutex);
}

#elif defined(_WIN32)

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
struct rt_condition_var_s {
    CRITICAL_SECTION critical_section;
    CONDITION_VARIABLE cond;
    ptrdiff_t next_reusable;
};

#define MAX_CONDS 4096
rt_condition_var _conds[MAX_CONDS];
static ptrdiff_t _first_reusable = MAX_CONDS;
static ptrdiff_t _next           = 0;
static HANDLE _guard;
static INIT_ONCE _guard_init = INIT_ONCE_STATIC_INIT;

static BOOL CALLBACK InitGuardFn(PINIT_ONCE initOnce, PVOID parameter, PVOID *context) {
    RT_UNUSED(initOnce);
    RT_UNUSED(parameter);
    RT_UNUSED(context);

    _guard = CreateMutexW(NULL, FALSE, NULL);
    return _guard != NULL;
}

RT_DLLEXPORT rt_condition_var *rtCreateConditionVar(void) {
    if (!InitOnceExecuteOnce(&_guard_init, InitGuardFn, NULL, NULL)) {
        rtReportError("core", "Failed to initialize the guard mutex.");
        return NULL;
    }

    if (WaitForSingleObjectEx(_guard, INFINITE, TRUE) != WAIT_OBJECT_0) {
        rtLog("core", "Failed to lock the guard variable: %u", GetLastError());
        return NULL;
    }
    if (_first_reusable < MAX_CONDS) {
        rt_condition_var *cond = &_conds[_first_reusable];
        _first_reusable        = cond->next_reusable;
        ReleaseMutex(_guard);
        return cond;
    } else if (_next < MAX_CONDS) {
        rt_condition_var *cond = &_conds[_next];
        if (!InitializeCriticalSectionAndSpinCount(&cond->critical_section, 4000)) {
            rtLog("core", "Condition variable creation failed");
            ReleaseMutex(_guard);
            return NULL;
        }
        InitializeConditionVariable(&cond->cond);
        cond->next_reusable = MAX_CONDS;
        ++_next;
        ReleaseMutex(_guard);
        return cond;
    }
    rtReportError("core", "Ran out of condition variable objects");
    ReleaseMutex(_guard);
    return NULL;
}

RT_DLLEXPORT void rtDestroyConditionVar(rt_condition_var *var) {
    ptrdiff_t index = var - &_conds[0];
    if (WaitForSingleObjectEx(_guard, INFINITE, TRUE) != WAIT_OBJECT_0) {
        rtLog("core", "Failed to lock the guard variable: %u", GetLastError());
        return;
    }
    var->next_reusable = _first_reusable;
    _first_reusable    = index;
    ReleaseMutex(_guard);
}

RT_DLLEXPORT void rtLockConditionVar(rt_condition_var *var) {
    EnterCriticalSection(&var->critical_section);
}

RT_DLLEXPORT void rtUnlockConditionVar(rt_condition_var *var, bool signal) {
    LeaveCriticalSection(&var->critical_section);
    if (signal)
        WakeAllConditionVariable(&var->cond);
}

RT_DLLEXPORT void rtWaitOnConditionVar(rt_condition_var *var) {
    SleepConditionVariableCS(&var->cond, &var->critical_section, INFINITE);
}

#endif
