#pragma once
#ifdef _WIN32
#include <conio.h>
#include <windows.h>
#include <process.h>
#include <tlhelp32.h>
#include <sys/utime.h>
#include <io.h>
#include <direct.h>
#else
#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#endif


class Locker
{
public:
	enum LOCK_STATUS {
		LOCK_OK = 0,
		LOCK_TIMEOUT = -1,
		LOCK_FAILED = -2
	};

	Locker(){
#ifdef _WIN32
		_lock = NULL;
#endif
		init();
	}

	~Locker(){
		release();
	}

	Locker::LOCK_STATUS lock(unsigned long timeout = 0xFFFFFFFF) {
#ifdef _WIN32
		switch (WaitForSingleObject(_lock, timeout==0xFFFFFFF?INFINITE:(DWORD)timeout)) {
		case WAIT_ABANDONED:
			return LOCK_FAILED;
		case WAIT_OBJECT_0:
			return LOCK_OK;
		case WAIT_TIMEOUT:
			return LOCK_TIMEOUT;
		}

#else
#ifdef _MACOS
		if (timeout !=0 ) {
			if (pthread_mutex_lock(&_lock) == 0) return LOCK_OK;
		}
#else
		if (timeout == 0xFFFFFFFF){
			if (pthread_mutex_lock(&_lock) == 0) return LOCK_OK;
		}
#endif
		else if (timeout == 0) {
			if (pthread_mutex_trylock(&_lock) == 0) return LOCK_OK;
		}
#ifndef _MACOS
		else{
			timespec wait_time;
			timeval now;
			gettimeofday(&now,NULL);

			wait_time.tv_sec = timeout/1000 + now.tv_sec;
			wait_time.tv_nsec = (timeout%1000)*1000000 + now.tv_usec*1000;

			if (wait_time.tv_nsec >= 1000000000){
				++wait_time.tv_sec;
				wait_time.tv_nsec -= 1000000000;
			}

#if !defined(__ANDROID__)

			switch (pthread_mutex_timedlock(&_lock,&wait_time)){
			case 0:
				return LOCK_OK;
			case ETIMEDOUT:
				return LOCK_TIMEOUT;
			}

#else
			struct timeval timenow;
			struct timespec sleepytime;
			/* This is just to avoid a completely busy wait */
			sleepytime.tv_sec = 0;
			sleepytime.tv_nsec = 10000000; /* 10ms */

			while (pthread_mutex_trylock (&_lock) == EBUSY) {
				gettimeofday (&timenow, NULL);

				if (timenow.tv_sec >= wait_time.tv_sec &&
					(timenow.tv_usec * 1000) >= wait_time.tv_nsec) {
						return LOCK_TIMEOUT;
				}

				nanosleep (&sleepytime, NULL);
			}

			return LOCK_OK;

#endif

		}
#endif
#endif

		return LOCK_FAILED;
	}


	void unlock(){
#ifdef _WIN32
		ReleaseMutex(_lock);
#else
		pthread_mutex_unlock(&_lock);
#endif
	}

#ifdef _WIN32
	HANDLE getLockHandle(){
		return _lock;
	}
#else
	pthread_mutex_t *getLockHandle(){
		return &_lock;
	}
#endif



protected:
	void init(){
#ifdef _WIN32
		_lock = CreateMutex(NULL,FALSE,NULL);
#else
		pthread_mutex_init(&_lock, NULL);
#endif
	}

	void release(){
		unlock();
#ifdef _WIN32

		if(_lock){ 
			CloseHandle(_lock);
		}
		_lock = NULL;
#else
		pthread_mutex_destroy(&_lock);
#endif
	}

#ifdef _WIN32
	HANDLE  _lock;
#else
	pthread_mutex_t _lock;
#endif
};


class Event
{
public:

	enum {
		EVENT_OK = 1,
		EVENT_TIMEOUT = 2,
		EVENT_FAILED = 0,
	};

	Event(bool isAutoReset = true, bool isSignal = false)
#ifdef _WIN32
		: _event(NULL)
#else
		: _is_signalled(isSignal)
		, _isAutoReset(isAutoReset)
#endif
	{
#ifdef _WIN32
		_event = CreateEvent(NULL, isAutoReset?FALSE:TRUE, isSignal?TRUE:FALSE, NULL); 
#else
		pthread_mutex_init(&_cond_locker, NULL);
		pthread_cond_init(&_cond_var, NULL);
#endif
	}

	~ Event(){
		release();
	}

	void set( bool isSignal = true ){
		if (isSignal){
#ifdef _WIN32
			SetEvent(_event);
#else
			pthread_mutex_lock(&_cond_locker);

			if ( _is_signalled == false ){
				_is_signalled = true;
				pthread_cond_signal(&_cond_var);
			}
			pthread_mutex_unlock(&_cond_locker);
#endif
		}else{
#ifdef _WIN32
			ResetEvent(_event);
#else
			pthread_mutex_lock(&_cond_locker);
			_is_signalled = false;
			pthread_mutex_unlock(&_cond_locker);
#endif
		}
	}

	unsigned long wait( unsigned long timeout = 0xFFFFFFFF ){
#ifdef _WIN32
		switch (WaitForSingleObject(_event, timeout==0xFFFFFFF?INFINITE:(DWORD)timeout)){
		case WAIT_FAILED:
			return EVENT_FAILED;
		case WAIT_OBJECT_0:
			return EVENT_OK;
		case WAIT_TIMEOUT:
			return EVENT_TIMEOUT;
		}
		return EVENT_OK;
#else
		unsigned long ans = EVENT_OK;
		pthread_mutex_lock( &_cond_locker );

		if (!_is_signalled){
			if (timeout == 0xFFFFFFFF){
				pthread_cond_wait(&_cond_var,&_cond_locker);
			}else{
				timespec wait_time;
				timeval now;
				gettimeofday(&now,NULL);

				wait_time.tv_sec = timeout/1000 + now.tv_sec;
				wait_time.tv_nsec = (timeout%1000)*1000000ULL + now.tv_usec*1000;

				if (wait_time.tv_nsec >= 1000000000){
					++wait_time.tv_sec;
					wait_time.tv_nsec -= 1000000000;
				}
				switch (pthread_cond_timedwait(&_cond_var,&_cond_locker,&wait_time)){
				case 0:
					// signalled
					break;
				case ETIMEDOUT:
					// time up
					ans = EVENT_TIMEOUT;
					goto _final;
					break;
				default:
					ans = EVENT_FAILED;
					goto _final;
				}

			}
		}

		assert(_is_signalled);

		if (_isAutoReset){
			_is_signalled = false;
		}
_final:
		pthread_mutex_unlock( &_cond_locker );

		return ans;
#endif

	}
protected:

	void release() {
#ifdef _WIN32
		CloseHandle(_event);
#else
		pthread_mutex_destroy(&_cond_locker);
		pthread_cond_destroy(&_cond_var);
#endif
	}

#ifdef _WIN32
	HANDLE _event;
#else
	pthread_cond_t         _cond_var;
	pthread_mutex_t        _cond_locker;
	bool                   _is_signalled;
	bool                   _isAutoReset;
#endif
};

class ScopedLocker
{
public :
	ScopedLocker(Locker &l): _binded(l) {
		_binded.lock();
	}

	void forceUnlock() {
		_binded.unlock();
	}
	~ScopedLocker() {
		_binded.unlock();
	}
	Locker & _binded;
};


