﻿/**
 D Language Synchronization Management

	Contains the monitor structure used by objects to implement the synchronized
	language statement. It also extends the D concept of monitor with support
	for condition variables.

 Authors:
	Jeremie Pelletier
*/
module dlib.Monitor;

import std.c.stdlib : malloc, free;

import dlib.Module;
import dlib.Thread;

version(Windows) {
	import sys.windows.Information;
	import sys.windows.Processes;
}
else version(Posix) {
	import sys.posix.pthread;
}
else static assert(0);

private {
	// Critical objects are allocated by the compiler, we only need to keep
	// track of the ones we do initialize.
	Monitor*	_criticalList;
	Mutex		_criticalMutex;

	version(Windows) {
		bool _hasCondVar;
	}
	else version(Posix) {
		pthread_mutexattr_t	_criticalAttr;
	}
}

/**
 Initialize the runtime monitor manager
*/
package void MonitorInit() {
	version(Windows) {
		MutexInit(&_criticalMutex);

		// Use native condition variables on NT 6.0 (Vista) and up
		OSVERSIONINFO ver = void;
		ver.dwOSVersionInfoSize = OSVERSIONINFO.sizeof;
		.GetVersionEx(&ver);

		_hasCondVar = ver.dwMajorVersion >= 6;

		// Load the condition variable routines
		if(_hasCondVar && !InitializeConditionVariable) {
			Module mod = Module.Load("Kernel32.dll");

			mod.GetProc(cast(void**)&InitializeConditionVariable, "InitializeConditionVariable");
			mod.GetProc(cast(void**)&SleepConditionVariableCS, "SleepConditionVariableCS");
			mod.GetProc(cast(void**)&WakeConditionVariable, "WakeConditionVariable");
			mod.GetProc(cast(void**)&WakeAllConditionVariable, "WakeAllConditionVariable");
		}
	}
	else version(Posix) {
		pthread_mutexattr_init(&_criticalAttr);
		pthread_mutexattr_settype(&_criticalAttr, PTHREAD_MUTEX_RECURSIVE_NP);
		pthread_mutex_init(&_monitorMutex, &_monitorAttr);
	}
	else static assert(0);
}

/**
 Destroy the runtime monitor manager
*/
package void MonitorDestroy() {
	// Destroy the criticals, the last entry is always recursive, this is so
	// the first monitor can start the list chain.
	if(_criticalList) while(_criticalList != _criticalList.next) {
		MutexDestroy(&_criticalList.mutex);
		_criticalList = _criticalList.next;
	}

	MutexDestroy(&_criticalMutex);

	version(Posix) pthread_mutexattr_destroy(&_criticalAttr);
}

version(Windows) {
	alias CRITICAL_SECTION			Mutex;

	union Condition {
		CONDITION_VARIABLE var;
		struct {
			// TODO
			CRITICAL_SECTION lock;
			uint nEntries;
		}
	}

	alias InitializeCriticalSection	MutexInit;
	alias DeleteCriticalSection		MutexDestroy;
	alias EnterCriticalSection		MutexEnter;
	alias LeaveCriticalSection		MutexLeave;
}
else version(Posix) {
	alias pthread_mutex_t			Mutex;
	alias pthread_cond_t			Condition;

	void MutexInit(Mutex* mutex) {
		pthread_mutex_init(mutex, null);
	}
	alias pthread_mutex_destroy		MutexDestroy;
	alias pthread_mutex_lock		MutexEnter;
	alias pthread_mutex_unlock		MutexLeave;
}
else
	static assert(0, "TODO: unsupported host.");

/**
 The monitor structure is the central synchronization object of the D language.
 It is automatically handled using the synchronized statement, or the Object
 methods.

 Oh and please don't make this a class! If you can't figure out why, you
 shouldn't mess with the runtime in the first place! ;)
*/
struct Monitor {
	/**
	 Initialize the mutex part of the monitor
	*/
	void Init() {
		MutexInit(&mutex);
	}

	/**
	 Initialize both the mutex and the condition variable of the monitor
	*/
	void InitCond()
	in {
		assert(!cond);
	}
	body {
		cond = new Condition;

		MutexInit(&mutex);

		version(Windows) {
			if(_hasCondVar) {
				InitializeConditionVariable(&cond.var);
			}
			else {
				assert(0);// TODO
			}
		}
		else version(Posix) {
			pthread_cond_init(cond, null);
		}
		else static assert(0);
	}

	/**
	 Destroy both the mutex and the condition variable of the monitor
	*/
	void Destroy() {
		MutexDestroy(&mutex);

		if(cond) {
			version(Windows) {
				if(!_hasCondVar) assert(0);// TODO
			}
			else version(Posix) {
				pthread_cond_destroy(cond);
			}
			else static assert(0);
		}
	}

	/**
	 Lock the mutex
	*/
	void Enter() {
		MutexEnter(&mutex);
	}

	/**
	 Unlock the mutex
	*/
	void Exit() {
		MutexLeave(&mutex);
	}

	/**
	 Causes the calling thread to wait for the condition variable to be
	 notified.
	*/
	void Wait(uint interval = -1) {
		if(!cond) InitCond();

		version(Windows) {
			if(_hasCondVar) {
				SleepConditionVariableCS(&cond.var, &mutex, interval);
			}
			else {
				assert(0);// TODO
			}
		}
		else version(Posix) {
			if(time != -1) assert("TODO: implement pthread_cond_timedwait");
			pthread_cond_wait(cond, &mutex);
		}
		else static assert(0);
	}

	/**
	 Notify the first waiting thread
	*/
	void Notify() {
		if(!cond) return;

		version(Windows) {
			if(_hasCondVar) {
				WakeConditionVariable(&cond.var);
			}
			else {
				assert(0);// TODO
			}
		}
		else version(Posix) {
			pthread_cond_signal(cond);
		}
		else static assert(0);
	}

	/**
	 Notify all waiting threads
	*/
	void NotifyAll() {
		if(!cond) return;

		version(Windows) {
			if(_hasCondVar) {
				WakeAllConditionVariable(&cond.var);
			}
			else {
				assert(0);// TODO
			}
		}
		else version(Posix) {
			pthread_cond_broadcast(cond);
		}
		else static assert(0);
	}

package:
	Mutex					mutex;	// Also used by memory manager to bootstrap
	union {
		Condition*			cond;
		private Monitor*	next;	// Only used for criticals
	}
}

// Reserved for dlib, do not use.
void CriticalEnter(Monitor* monitor) {
	if(!monitor.next) {
		MutexEnter(&_criticalMutex);

		// if, in the meantime, another thread didn't set it
		if(!monitor.next) {
			monitor.next = _criticalList ? _criticalList : monitor;
			_criticalList = monitor;

			MutexInit(&monitor.mutex);
		}

		MutexLeave(&_criticalMutex);
	}

	MutexEnter(&monitor.mutex);
}

unittest {
	class Foo {}
	Foo foo = new Foo;

	void Run() {
		Trace("wait..");
		foo.Wait();
		Trace("GREAT SUCCESS");
	}

	RuntimeThread myThread = RuntimeThread.Create(&Run);

	.Sleep(100);
	Trace("signal..");

	foo.Notify();

	.Sleep(100);
	Pause;
}

