#ifndef __MUTEX_H__
#define __MUTEX_H__

#ifndef WIN32
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <rdr/Exception.h>
#include <errno.h>
#include <sys/types.h>
#include <string.h>
#else
#include <pthread.h>
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
#include <processthreadsapi.h>
#define pthread_t HANDLE
#define pthread_self GetCurrentThread
#endif
#include <sys/time.h>

#ifdef WIN32
#define THREAD_FUNC DWORD WINAPI
#define THREAD_CREATE(FUNC, T_ID, PARAM) T_ID = CreateThread(0, 0, FUNC, PARAM, 0, NULL)
#define THREAD_SELF GetCurrentThread
#define THREAD_JOIN(T_ID) WaitForSingleObject(T_ID, INFINITE)
#define THREAD_ID HANDLE
#define THREAD_CANCEL(T_ID) TerminateThread(T_ID, 0)
#define THREAD_EXIT(RETVAL) ExitThread(RETVAL)
#define THREAD_NULL 0
inline HRESULT THREAD_SET_NAME(HANDLE hThread, const char * lpThreadDescription) {
#ifdef SetThreadDescription
  int lenA = lstrlenA(lpThreadDescription);
  int lenW = ::MultiByteToWideChar(CP_ACP, 0, lpThreadDescription, lenA, NULL, 0);
  if (lenW > 0) {
    output = new wchar_t[lenW];
    ::MultiByteToWideChar(CP_ACP, 0, input, lenA, output, lenW);
    return SetThreadDescription(hThread, output);
  }
#else
  return 0;
#endif
}
inline int THREAD_GET_NAME(HANDLE id, char * name, unsigned len) {
  if (len < 1)
    return -1;
  snprintf(name, len, "%lu", GetCurrentThreadId());
  name[len - 1] = 0;
  return 0;
}

#else

#define THREAD_FUNC void*
#define THREAD_CREATE(FUNC, T_ID, PARAM) pthread_create(&T_ID, NULL, FUNC, PARAM)
#define THREAD_SELF pthread_self
#define THREAD_JOIN(T_ID) pthread_join(T_ID, NULL)
#define THREAD_ID pthread_t
#define THREAD_CANCEL(T_ID) pthread_cancel(T_ID)
#define THREAD_EXIT(RETVAL) pthread_exit(RETVAL)
#define THREAD_NULL NULL
inline int THREAD_SET_NAME(pthread_t thread, const char *name) {
#ifdef pthread_setname_np
  if (strlen(name) > 15) {
    char tmp[16];

    strncpy(tmp, name, 15);
    tmp[15] = 0;
    return pthread_setname_np(thread, tmp);
  } else {
    return pthread_setname_np(thread, name);
  }
#else
  return 0;
#endif
}
#define THREAD_GET_NAME(ID, NAME, LEN) pthread_getname_np(ID, NAME, LEN)
#ifndef gettid
#ifndef SYS_gettid
#define SYS_gettid 178
#endif
#define gettid() syscall(SYS_gettid)
#endif
#endif

/* MUTEX_TRYLOCK() returns 1, of locked, 0, if not */

#ifdef WIN32
#define MUTEX_TYPE CRITICAL_SECTION
#define MUTEX_INIT(MUT) InitializeCriticalSection(MUT)
#define MUTEX_INIT_NOCHECK(MUT) InitializeCriticalSection(MUT)
#define MUTEX_DESTROY(MUT) DeleteCriticalSection(MUT)
#define MUTEX_LOCK(MUT) EnterCriticalSection(MUT)
#define MUTEX_TRYLOCK(MUT) TryEnterCriticalSection(MUT)
#define MUTEX_UNLOCK(MUT) LeaveCriticalSection(MUT)

#else

#define MUTEX_TYPE pthread_mutex_t
inline int MUTEX_INIT(pthread_mutex_t *mutex) {
	pthread_mutexattr_t attr;

	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
	return pthread_mutex_init(mutex, &attr);
}
inline int MUTEX_INIT_NOCHECK(pthread_mutex_t *mutex) {
	return pthread_mutex_init(mutex, NULL);
}
inline int MUTEX_LOCK(pthread_mutex_t *mutex) {
	int err;

	err = pthread_mutex_lock(mutex);
	if (err == EDEADLK) {
#if defined(__APPLE__)
		throw rdr::Exception("MUTEX_LOCK: Dieser Thread %u hat das Lock %p bereits, Deadlock!", gettid(), mutex);
#else
		throw rdr::Exception("MUTEX_LOCK: Dieser Thread %lu hat das Lock %p bereits, Deadlock!", gettid(), mutex);
#endif
	}
	if (err == EINVAL) {
#if defined(__APPLE__)
		throw rdr::Exception("MUTEX_LOCK: Thread %u: Das Lock %p ist nicht initialisiert!", gettid(), mutex);
#else
		throw rdr::Exception("MUTEX_LOCK: Thread %lu: Das Lock %p ist nicht initialisiert!", gettid(), mutex);
#endif
	}
	return err;
}
inline int MUTEX_TRYLOCK(pthread_mutex_t *mutex) {
	int err;

	err = pthread_mutex_trylock(mutex);
	if (err == EINVAL) {
#if defined(__APPLE__)
		throw rdr::Exception("MUTEX_TRYLOCK: Thread %u: Das Lock %p ist nicht initialisiert!", gettid(), mutex);
#else
		throw rdr::Exception("MUTEX_TRYLOCK: Thread %lu: Das Lock %p ist nicht initialisiert!", gettid(), mutex);
#endif
	}
	return (err == 0);
}
inline int MUTEX_UNLOCK(pthread_mutex_t *mutex) {
	int err;

	err = pthread_mutex_unlock(mutex);
	if (err == EPERM) {
#if defined(__APPLE__)
		throw rdr::Exception("MUTEX_UNLOCK: Dieser Thread %u besitzt das Lock %p nicht!", gettid(), mutex);
#else
		throw rdr::Exception("MUTEX_UNLOCK: Dieser Thread %lu besitzt das Lock %p nicht!", gettid(), mutex);
#endif
	}
	if (err == EINVAL) {
#if defined(__APPLE__)
		throw rdr::Exception("MUTEX_LOCK: Thread %u: Das Lock %p ist nicht initialisiert!", gettid(), mutex);
#else
		throw rdr::Exception("MUTEX_LOCK: Thread %lu: Das Lock %p ist nicht initialisiert!", gettid(), mutex);
#endif
	}
	return err;
}
inline int MUTEX_DESTROY(pthread_mutex_t *mutex) {
	if (pthread_mutex_destroy(mutex) == EBUSY) {
#if defined(__APPLE__)
		throw rdr::Exception("MUTEX_DESTROY: Thread %u: Das Lock %p ist noch gesperrt!", gettid(), mutex);
#else
		throw rdr::Exception("MUTEX_DESTROY: Thread %lu: Das Lock %p ist noch gesperrt!", gettid(), mutex);
#endif
	}
	return 0;
}
#endif

#ifdef WIN32
#define TGVNC_CONDITION_TYPE CONDITION_VARIABLE
#define TGVNC_CONDITION_INIT(COND) InitializeConditionVariable(COND)
#define TGVNC_CONDITION_DESTROY(COND) (void) COND;
#define TGVNC_CONDITION_SEND_SIG(COND) WakeConditionVariable(COND)
#define TGVNC_CONDITION_BROADCAST(COND) WakeAllConditionVariable(COND)
#define TGVNC_CONDITION_WAIT(COND, MUTEX) SleepConditionVariableCS(COND, MUTEX, INFINITE)
#define TGVNC_CONDITION_TIMED_WAIT(COND, MUTEX, MILLISEC) SleepConditionVariableCS(COND, MUTEX, MILLISEC)

#else

#define TGVNC_CONDITION_TYPE pthread_cond_t
#define TGVNC_CONDITION_INIT(COND) pthread_cond_init(COND, NULL)
#define TGVNC_CONDITION_DESTROY(COND) pthread_cond_destroy(COND)
#define TGVNC_CONDITION_SEND_SIG(COND) pthread_cond_signal(COND)
#define TGVNC_CONDITION_BROADCAST(COND) pthread_cond_broadcast(COND)
#define TGVNC_CONDITION_WAIT(COND, MUTEX) pthread_cond_wait(COND, MUTEX)
inline int TGVNC_CONDITION_TIMED_WAIT(pthread_cond_t *cond, pthread_mutex_t *mutex, unsigned long ms) {
	struct timespec ts;
	struct timeval now;
	struct timeval add;
	struct timeval res;

	gettimeofday(&now, NULL);
	add.tv_sec = ms / 1000;
	add.tv_usec = (ms % 1000) * 1000;
	timeradd(&now, &add, &res);

	ts.tv_sec  = res.tv_sec;
	ts.tv_nsec = res.tv_usec * 1000UL;
	return pthread_cond_timedwait(cond, mutex, &ts);
}
#endif

#endif
