
// win32compat.h

#ifndef WIN32COMPAT_H_INCLUDED
#define WIN32COMPAT_H_INCLUDED

#include <pthread.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#include <cstdio>

#ifndef FAR
#define FAR
#endif

#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

typedef int BOOL;
typedef unsigned int DWORD;

#define INFINITE   0xFFFFFFFFu
#define WAIT_OBJECT_0 0u
#define WAIT_TIMEOUT  258u

struct Win32Event {
    pthread_mutex_t mutex;
    pthread_cond_t  cond;
    bool manualReset;
    bool signaled;
};

typedef Win32Event* HANDLE;

inline HANDLE CreateEvent(void* /*lpEventAttributes*/, BOOL bManualReset,
                           BOOL bInitialState, const char* /*lpName*/)
{
    Win32Event* e = new Win32Event();
    pthread_mutex_init(&e->mutex, NULL);
    pthread_cond_init(&e->cond, NULL);
    e->manualReset = (bManualReset != 0);
    e->signaled = (bInitialState != 0);
    return e;
}

inline void SetEvent(HANDLE h)
{
    pthread_mutex_lock(&h->mutex);
    h->signaled = true;
    pthread_mutex_unlock(&h->mutex);
    if (h->manualReset)
        pthread_cond_broadcast(&h->cond);
    else
        pthread_cond_signal(&h->cond);
}

inline void ResetEvent(HANDLE h)
{
    pthread_mutex_lock(&h->mutex);
    h->signaled = false;
    pthread_mutex_unlock(&h->mutex);
}

inline DWORD WaitForSingleObject(HANDLE h, DWORD dwMilliseconds)
{
    DWORD result = WAIT_OBJECT_0;
    pthread_mutex_lock(&h->mutex);

    if (dwMilliseconds == INFINITE) {
        while (!h->signaled)
            pthread_cond_wait(&h->cond, &h->mutex);
    } else {
        struct timespec ts;
        clock_gettime(CLOCK_REALTIME, &ts);
        ts.tv_sec  += dwMilliseconds / 1000;
        ts.tv_nsec += (long) (dwMilliseconds % 1000) * 1000000L;
        if (ts.tv_nsec >= 1000000000L) {
            ts.tv_sec  += 1;
            ts.tv_nsec -= 1000000000L;
        }
        while (!h->signaled) {
            int rc = pthread_cond_timedwait(&h->cond, &h->mutex, &ts);
            if (rc == ETIMEDOUT) { result = WAIT_TIMEOUT; break; }
        }
    }
    
    if (result == WAIT_OBJECT_0 && !h->manualReset)
        h->signaled = false;

    pthread_mutex_unlock(&h->mutex);
    return result;
}

struct ThreadTrampolineArgs {
    void (*func)(void*);
    void* arg;
};

inline void* ThreadTrampoline(void* p)
{
    ThreadTrampolineArgs* args = (ThreadTrampolineArgs*) p;
    void (*func)(void*) = args->func;
    void* arg = args->arg;
    delete args;
    func(arg);
    return NULL;
}

inline unsigned long _beginthread(void (*func)(void*), unsigned /*stackSize*/, void* arg)
{
    ThreadTrampolineArgs* args = new ThreadTrampolineArgs();
    args->func = func;
    args->arg = arg;
    pthread_t tid;
    pthread_create(&tid, NULL, ThreadTrampoline, args);
    pthread_detach(tid);
    return (unsigned long) tid;
}

inline void _endthread()
{
    pthread_exit(NULL);
}

inline void Sleep(unsigned int ms)
{
    struct timespec ts;
    ts.tv_sec = ms / 1000;
    ts.tv_nsec = (long) (ms % 1000) * 1000000L;
    nanosleep(&ts, NULL);
}

#define ZeroMemory(p, sz) memset((p), 0, (sz))

inline int DeleteFile(const char* path)
{
    return remove(path) == 0;
}

inline DWORD GetCurrentDirectory(DWORD nBufferLength, char* lpBuffer)
{
    if (getcwd(lpBuffer, nBufferLength) == NULL)
        return 0;
    return (DWORD) strlen(lpBuffer);
}

inline BOOL SetCurrentDirectory(const char* lpPathName)
{
    return chdir(lpPathName) == 0;
}

#endif // WIN32COMPAT_H_INCLUDED
