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

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

struct rt_mutex_s {
    HANDLE handle;
    ptrdiff_t next_reusable;
    DWORD owner;
};

#define MAX_MUTEX 4096
static rt_mutex _mutex[MAX_MUTEX];
static ptrdiff_t _first_reusable = MAX_MUTEX;
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_mutex *rtCreateMutex(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_MUTEX) {
        rt_mutex *mtx   = &_mutex[_first_reusable];
        _first_reusable = mtx->next_reusable;
        mtx->owner      = 0;
        ReleaseMutex(_guard);
        return mtx;
    } else if (_next < MAX_MUTEX) {
        rt_mutex *mtx = &_mutex[_next];
        mtx->handle   = CreateMutexW(NULL, FALSE, NULL);
        if (!mtx->handle) {
            rtLog("core", "Mutex creation failed: %u", GetLastError());
            ReleaseMutex(_guard);
            return NULL;
        }
        mtx->next_reusable = MAX_MUTEX;
        mtx->owner         = 0;
        ++_next;
        ReleaseMutex(_guard);
        return mtx;
    }
    ReleaseMutex(_guard);
    rtReportError("core", "Ran out of mutex objects");
    return NULL;
}

RT_DLLEXPORT void rtDestroyMutex(rt_mutex *mutex) {
    ptrdiff_t index      = mutex - &_mutex[0];
    mutex->next_reusable = _first_reusable;
    _first_reusable      = index;
}

RT_DLLEXPORT bool rtLockMutex(rt_mutex *mutex) {
    DWORD caller = GetCurrentThreadId();
    DWORD result;
    do {
        result = WaitForSingleObjectEx(mutex->handle, INFINITE, TRUE);
        if (result == WAIT_OBJECT_0) {
            mutex->owner = caller;
        } else if (result != WAIT_IO_COMPLETION) {
            rtLog("core",
                  "WaitForSingleObjectEx returned %x (GetLastError(): %u)",
                  result,
                  (result == WAIT_FAILED) ? GetLastError() : 0);
        }
    } while (result == WAIT_IO_COMPLETION);
    return result == WAIT_OBJECT_0;
}

RT_DLLEXPORT bool rtUnlockMutex(rt_mutex *mutex) {
    DWORD caller = GetCurrentThreadId();
    if (caller != mutex->owner) {
        rtReportError("core", "Tried to unlock a mutex held by another thread.");
        return false;
    }
    mutex->owner = 0;
    bool result = ReleaseMutex(mutex->handle) != 0;
    if (!result)
        mutex->owner = caller;
    return result;
}

#elif defined(__linux__)

#include <pthread.h>
struct rt_mutex_s {
    pthread_mutex_t handle;
    ptrdiff_t next_reusable;
};

#define MAX_MUTEX 4096
static rt_mutex _mutex[MAX_MUTEX];
static ptrdiff_t _first_reusable = MAX_MUTEX;
static ptrdiff_t _next           = 0;
static pthread_mutex_t _guard    = PTHREAD_MUTEX_INITIALIZER;

RT_DLLEXPORT rt_mutex *rtCreateMutex(void) {
    pthread_mutex_lock(&_guard);
    if (_first_reusable < MAX_MUTEX) {
        rt_mutex *mtx   = &_mutex[_first_reusable];
        _first_reusable = mtx->next_reusable;
        pthread_mutex_unlock(&_guard);
        return mtx;
    } else if (_next < MAX_MUTEX) {
        rt_mutex *mtx = &_mutex[_next];
        if (pthread_mutex_init(&mtx->handle, NULL) != 0) {
            rtLog("core", "Mutex creation failed");
            pthread_mutex_unlock(&_guard);
            return NULL;
        }
        mtx->next_reusable = MAX_MUTEX;
        ++_next;
        pthread_mutex_unlock(&_guard);
        return mtx;
    }
    rtReportError("core", "Ran out of mutex objects");
    pthread_mutex_unlock(&_guard);
    return NULL;
}

RT_DLLEXPORT void rtDestroyMutex(rt_mutex *mutex) {
    ptrdiff_t index = mutex - &_mutex[0];
    pthread_mutex_lock(&_guard);
    mutex->next_reusable = _first_reusable;
    _first_reusable      = index;
    pthread_mutex_unlock(&_guard);
}

RT_DLLEXPORT bool rtLockMutex(rt_mutex *mutex) {
    return pthread_mutex_lock(&mutex->handle) == 0;
}

RT_DLLEXPORT bool rtUnlockMutex(rt_mutex *mutex) {
    return pthread_mutex_unlock(&mutex->handle) == 0;
}

#endif
