/////////////////////////////////////////////////////////////
// CINEMA 4D SDK                                           //
/////////////////////////////////////////////////////////////
// (c) MAXON Computer GmbH, all rights reserved            //
/////////////////////////////////////////////////////////////

#ifndef __C4DTHREAD_H
#define __C4DTHREAD_H

#ifndef __API_INTERN__

#include "operatingsystem.h"

//#define ThreadCall(fnc) (this->*C4DOS.Bt->fnc)
///#define ThreadCall(fnc, ...) RE_C4DOS.Bt->fnc(this,##__VA_ARGS__) //Remo: 26.09.2012
#define ThreadCall(fnc, ...) C4DOS.Bt->fnc(this,##__VA_ARGS__)


struct GeSpinlock
{
	GeSpinlock() : state(0) { }

	volatile LONG	state;

	inline void Lock() { ThreadCall(Lock ); }
	inline void Unlock() { ThreadCall(Unlock ); }
	inline Bool AttemptLock() { return ThreadCall(AttemptLock ); }
};

struct GeRWSpinlock
{
	GeRWSpinlock() : state(0) { }

	volatile LONG		state;

	inline void ReadLock() { ThreadCall(ReadLock ); }
	inline void ReadUnlock() { ThreadCall(ReadUnlock ); }
	inline void WriteLock() { ThreadCall(WriteLock ); }
	inline void WriteUnlock() { ThreadCall(WriteUnlock ); }
	inline Bool AttemptWriteLock() { return ThreadCall(AttemptWriteLock ); }
};

class BaseThread
{
	private:
		BaseThread();
		~BaseThread();
	public:
		Bool TestBreak(void) { return C4DOS.Bt->TestBreak(this); }
		void End(Bool wait=TRUE) { C4DOS.Bt->End(this,wait); }
		Bool IsRunning(void) { return C4DOS.Bt->IsRunning(this); }
};

class C4DThread
{
	friend class MPThreadPool;
	friend void XThreadMain(void *data);
	friend Bool XThreadTest(void *data);

	private:
		Bool				weak;
		BaseThread	*bt;
	public:
		C4DThread(void);
		virtual ~C4DThread(void);

		BaseThread *Get(void) const { return bt; }

		Bool Start( THREADMODE mode = THREADMODE_ASYNC, THREADPRIORITY priority=THREADPRIORITY_NORMAL );
		void End(Bool wait=TRUE);
		Bool IsRunning(void) { return C4DOS.Bt->IsRunning(bt); }
		Bool TestBreak(void) { return C4DOS.Bt->TestBreak(bt); }
		void Wait(Bool checkevents);

		// routines for overriding
		virtual Bool TestDBreak(void) { return FALSE; }
		virtual void Main(void)=0;
		virtual const CHAR *GetThreadName(void)=0;
};

class MPThreadPool
{
	private:
		MPBaseThread *mp;
		LONG mpcount;
	public:
		MPThreadPool(void);
		~MPThreadPool(void);

		Bool				Init(BaseThread *parent, LONG count, C4DThread **thread);
		Bool				Init(const C4DThread &parent, LONG count, C4DThread **thread);
		Bool				Start(THREADPRIORITY worker_priority);
		C4DThread*	WaitForNextFree(void);
		void				Wait(void);
		void				End(void);
};

class Semaphore
{
	private:
		Semaphore();
		~Semaphore();
	public:
		Bool AttemptLock(void) { return C4DOS.Bt->SMLock(this); }
		Bool Lock(BaseThread *bt) { return C4DOS.Bt->SMLockAndWait(this,bt); }
		Bool Lock(C4DThread *bt) { return C4DOS.Bt->SMLockAndWait(this,bt?bt->Get():NULL); }
#ifdef _DEBUG
	#define AttemptLockDebug	_AttemptLock(__LINE__, __FILE__)
	#define LockDebug(_x_)		_LockDebug(_x_, __LINE__, __FILE__)
		Bool _AttemptLockDebug(LONG line, const char* file) { return C4DOS.Bt->SMLockDebug(this, line, file); }
		Bool _LockDebug(BaseThread *bt, LONG line, const char* file) { return C4DOS.Bt->SMLockAndWaitDebug(this,bt, line, file); }
		Bool _LockDebug(C4DThread *bt, LONG line, const char* file) { return C4DOS.Bt->SMLockAndWaitDebug(this,bt?bt->Get():NULL, line, file); }
#else
	#define AttemptLockDebug	AttemptLock
	#define LockDebug(_x_)		Lock(_x_)
#endif
		void Unlock(void) { C4DOS.Bt->SMUnlock(this); }

		static Semaphore *Alloc(void);
		static void Free(Semaphore *&sm);
};

class GeSignal
{
private:
	GeSignal();
	~GeSignal();
public:
	Bool Init( SIGNALMODE mode ) { return ThreadCall(SIGInit,  mode ); }
	void Set(void) { ThreadCall(SIGSet ); }
	void Clear(void) { ThreadCall(SIGClear ); }
	Bool Wait(LONG timeout) { return ThreadCall(SIGWait, timeout); }

	static GeSignal *Alloc(void);
	static void Free(GeSignal *&sm);
};

inline LONG GeGetCPUCount(void) { return C4DOS.Bt->GetCPUCount(); }
inline void GeThreadLock(void) { C4DOS.Bt->ThreadLock(); }
inline void GeThreadUnlock(void) { C4DOS.Bt->ThreadUnlock(); }

inline THREADTYPE IdentifyThread(BaseThread *bt) { return C4DOS.Bt->Identify(bt); }

inline ULONG GeGetCurrentThreadId() { return C4DOS.Bt->GetCurrentThreadId(); }
inline BaseThread* GeGetCurrentThread() { return C4DOS.Bt->GetCurrentThread(); }

#endif
class AutoSpinLock
{
private:
	GeSpinlock *l;
	AutoSpinLock &operator = (const AutoSpinLock &d);
	AutoSpinLock(AutoSpinLock &data);
public:
	AutoSpinLock(GeSpinlock *lock = NULL) 
	{
		l = NULL;
		if (!lock) return;
		DoLock(lock);
	}

	~AutoSpinLock() 
	{ 
		Unlock();
	}

	void DoLock(GeSpinlock *lock)
	{
		if (l || !lock) return;
		lock->Lock();
		l = lock;
	}

	void Unlock(void)
	{
		if (!l) return;
		l->Unlock();
		l = NULL;
	}
};

class AutoLocker;

class AutoLock
{
private:
	friend class AutoLocker;
	GeSpinlock lock;
	volatile ULONG threadid;
	AutoLock(const AutoLock &al);
	AutoLock(const AutoLocker &al);
public:
	AutoLock()
	{
		threadid = NOTOK;
	}
};

class AutoLocker
{
private:
	GeSpinlock *l;
	volatile ULONG *ct;
	AutoLocker &operator = (const AutoLocker &d);
	AutoLocker(AutoLocker &data);
public:
	AutoLocker()
	{
		l = NULL;
		ct = NULL;
	}
	AutoLocker(AutoLock &data) 
	{
		l = NULL;
		ct = NULL;
		DoLock(data);
	}
	~AutoLocker() 
	{ 
		Unlock();
	}

	void DoLock(AutoLock &data)
	{
		if (l) return;

		ULONG id = GeGetCurrentThreadId();
		if (data.threadid == id) return;

		data.lock.Lock();
		l = &data.lock;
		ct = &data.threadid;
		if (ct) *ct = id;
	}

	void Unlock(void)
	{
		if (!l) return;
		*ct = NOTOK;
		l->Unlock();
		l = NULL;
		ct = NULL;
	}
};

class AutoRWLock
{
	friend class AutoRWLocker;
	GeRWSpinlock lock;
	volatile ULONG threadid;
public:
	AutoRWLock()
	{
		threadid = NOTOK;
	}
};


class AutoRWLocker
{
private:
	GeRWSpinlock *l;
	volatile ULONG *ct;
	volatile Bool is_write;
	AutoRWLocker &operator = (const AutoRWLocker &d);
	AutoRWLocker(AutoRWLocker &lock);
public:
	AutoRWLocker() 
	{
		l = NULL;
		ct = NULL;
		is_write = FALSE;
	}
	AutoRWLocker(AutoRWLock &lock, Bool write_lock = TRUE) 
	{
		l = NULL;
		ct = NULL;
		is_write = write_lock;
		DoLock(lock, write_lock);
	}
	~AutoRWLocker() 
	{ 
		Unlock();
	}
	void DoLock(AutoRWLock &lock, Bool write_lock = TRUE)
	{
		if (l) return;
		ULONG id = 0;

		id = GeGetCurrentThreadId();
		if (lock.threadid == id) return;

		if (write_lock) lock.lock.WriteLock();
		else lock.lock.ReadLock();

		is_write = write_lock;
		l = &lock.lock;
		ct = &lock.threadid;
		if (ct) *ct = id;
	}

	void Unlock(void)
	{
		if (!l) return;
		*ct = NOTOK;
		if (is_write) l->WriteUnlock();
		else l->ReadUnlock();
		l = NULL;
		ct = NULL;
	}
};

#endif
