//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//

#ifndef __THREADING_H
#define __THREADING_H

#include <functional>

#if defined(_POSIX_THREADS) || defined(_POSIX_VERSION) || defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))

#include <pthread.h>
#include <semaphore.h>
#include <sched.h>
#include <unistd.h>
#if defined(__APPLE__)
#include "os/darwin/DarwinSpecific.h"
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
#include <pthread_np.h>
#endif

namespace tgvoip{
	class Mutex{
	public:
		Mutex(){
			pthread_mutex_init(&mtx, NULL);
		}

		~Mutex(){
			pthread_mutex_destroy(&mtx);
		}

		void Lock(){
			pthread_mutex_lock(&mtx);
		}

		void Unlock(){
			pthread_mutex_unlock(&mtx);
		}

		pthread_mutex_t* NativeHandle(){
			return &mtx;
		}

	private:
		Mutex(const Mutex& other);
		pthread_mutex_t mtx;
	};

	class Thread{
	public:
		Thread(std::function<void()> entry) : entry(entry){
			name=NULL;
			thread=0;
		}

		virtual ~Thread(){

		}

		void Start(){
			if(pthread_create(&thread, NULL, Thread::ActualEntryPoint, this)==0){
				valid=true;
			}
		}

		void Join(){
			if(valid)
				pthread_join(thread, NULL);
		}

		void SetName(const char* name){
			this->name=name;
		}


		void SetMaxPriority(){
#ifdef __APPLE__
			maxPriority=true;
#endif
		}

		static void Sleep(double seconds){
			usleep((useconds_t)(seconds*1000000.0));
		}

		bool IsCurrent(){
			return pthread_equal(thread, pthread_self())!=0;
		}

	private:
		static void* ActualEntryPoint(void* arg){
			Thread* self=reinterpret_cast<Thread*>(arg);
			if(self->name){
#if defined(__linux__) || defined(__FreeBSD__)
				pthread_setname_np(self->thread, self->name);
#elif defined(__OpenBSD__)
				pthread_set_name_np(self->thread, self->name);
#elif defined(__APPLE__)
				pthread_setname_np(self->name);
				if(self->maxPriority){
					DarwinSpecific::SetCurrentThreadPriority(DarwinSpecific::THREAD_PRIO_USER_INTERACTIVE);
				}
#endif
			}
			self->entry();
			return NULL;
		}
		std::function<void()> entry;
		pthread_t thread;
		const char* name;
#ifdef __APPLE__
		bool maxPriority=false;
#endif
		bool valid=false;
	};
}

#ifdef __APPLE__
#include <dispatch/dispatch.h>
namespace tgvoip{
class Semaphore{
public:
	Semaphore(unsigned int maxCount, unsigned int initValue){
		sem = dispatch_semaphore_create(initValue);
	}

	~Semaphore(){
#if ! __has_feature(objc_arc)
        dispatch_release(sem);
#endif
	}

	void Acquire(){
		dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
	}

	void Release(){
		dispatch_semaphore_signal(sem);
	}

	void Acquire(int count){
		for(int i=0;i<count;i++)
			Acquire();
	}

	void Release(int count){
		for(int i=0;i<count;i++)
			Release();
	}

private:
	dispatch_semaphore_t sem;
};
}
#else
namespace tgvoip{
class Semaphore{
public:
	Semaphore(unsigned int maxCount, unsigned int initValue){
		sem_init(&sem, 0, initValue);
	}

	~Semaphore(){
		sem_destroy(&sem);
	}

	void Acquire(){
		sem_wait(&sem);
	}

	void Release(){
		sem_post(&sem);
	}

	void Acquire(int count){
		for(int i=0;i<count;i++)
			Acquire();
	}

	void Release(int count){
		for(int i=0;i<count;i++)
			Release();
	}

private:
	sem_t sem;
};
}
#endif

#elif defined(_WIN32)
#define TGVOIP_WIN32_THREADING

#include <Windows.h>
#include <assert.h>

namespace tgvoip{
	class Mutex{
	public:
		Mutex(){
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY!=WINAPI_FAMILY_PHONE_APP
			InitializeCriticalSection(&section);
#else
			InitializeCriticalSectionEx(&section, 0, 0);
#endif
		}

		~Mutex(){
			DeleteCriticalSection(&section);
		}

		void Lock(){
			EnterCriticalSection(&section);
		}

		void Unlock(){
			LeaveCriticalSection(&section);
		}

	private:
		Mutex(const Mutex& other);
		CRITICAL_SECTION section;
	};

	class Thread{
	public:
		Thread(std::function<void()> entry) : entry(entry){
			name=NULL;
			thread=NULL;
		}

		~Thread(){
		}

		void Start(){
			thread=CreateThread(NULL, 0, Thread::ActualEntryPoint, this, 0, &id);
		}

		void Join(){
			if(!thread)
				return;
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY!=WINAPI_FAMILY_PHONE_APP
			WaitForSingleObject(thread, INFINITE);
#else
			WaitForSingleObjectEx(thread, INFINITE, false);
#endif
			CloseHandle(thread);
		}

		void SetName(const char* name){
			this->name=name;
		}

		void SetMaxPriority(){
			SetThreadPriority(thread, THREAD_PRIORITY_HIGHEST);
		}

		static void Sleep(double seconds){
			::Sleep((DWORD)(seconds*1000));
		}

		bool IsCurrent(){
			return id==GetCurrentThreadId();
		}

	private:
		static const DWORD MS_VC_EXCEPTION=0x406D1388;

		#pragma pack(push,8)
		typedef struct tagTHREADNAME_INFO
		{
		   DWORD dwType; // Must be 0x1000.
		   LPCSTR szName; // Pointer to name (in user addr space).
		   DWORD dwThreadID; // Thread ID (-1=caller thread).
		  DWORD dwFlags; // Reserved for future use, must be zero.
		} THREADNAME_INFO;
		#pragma pack(pop)

		static DWORD WINAPI ActualEntryPoint(void* arg){
			Thread* self=reinterpret_cast<Thread*>(arg);
			if(self->name){
				THREADNAME_INFO info;
				info.dwType=0x1000;
				info.szName=self->name;
				info.dwThreadID=-1;
				info.dwFlags=0;
				__try{
					RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info);
				}__except(EXCEPTION_EXECUTE_HANDLER){}
			}
			self->entry();
			return 0;
		}
		std::function<void()> entry;
		HANDLE thread;
		DWORD id;
		const char* name;
	};

class Semaphore{
public:
	Semaphore(unsigned int maxCount, unsigned int initValue){
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY!=WINAPI_FAMILY_PHONE_APP
		h=CreateSemaphore(NULL, initValue, maxCount, NULL);
#else
		h=CreateSemaphoreEx(NULL, initValue, maxCount, NULL, 0, SEMAPHORE_ALL_ACCESS);
		assert(h);
#endif
	}

	~Semaphore(){
		CloseHandle(h);
	}

	void Acquire(){
#if !defined(WINAPI_FAMILY) || WINAPI_FAMILY!=WINAPI_FAMILY_PHONE_APP
		WaitForSingleObject(h, INFINITE);
#else
		WaitForSingleObjectEx(h, INFINITE, false);
#endif
	}

	void Release(){
		ReleaseSemaphore(h, 1, NULL);
	}

	void Acquire(int count){
		for(int i=0;i<count;i++)
			Acquire();
	}

	void Release(int count){
		ReleaseSemaphore(h, count, NULL);
	}

private:
	HANDLE h;
};
}
#else
#error "No threading implementation for your operating system"
#endif

namespace tgvoip{
class MutexGuard{
public:
    MutexGuard(Mutex &mutex) : mutex(mutex) {
		mutex.Lock();
	}
	~MutexGuard(){
		mutex.Unlock();
	}
private:
	Mutex &mutex;
};
}

#endif //__THREADING_H
