/*
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License version 2 
	as published by the Free Software Foundation.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


	Copyright (C) 2006  Thierry Berger-Perrin <tbptbp@gmail.com>
*/
#ifndef SYS_THREAD_H
#define SYS_THREAD_H

/*
	How to derive?

	struct foo_t : sys::thread_t {
		bool_t init();
		int run();
		foo_t() : sys::thread_t(this) {}
	};


	How to instanciate?

	foo_t bar;
	sys::thread_t::spawn(&bar);
*/

#include "specifics.h"
#include "sys_cpu.h"

#ifdef LINUX
	#include <pthread.h>
	#include <semaphore.h>
#endif

#include <memory>

#if defined __MSVC__
	#pragma warning(disable : 4355) // : 'this' : used in base member initializer list
#endif

namespace sys {
	// not sure the syntactic sugar is worth the hassle,
	// anyway the semantic of mutex is the same on doze and linux so...
	struct mutex_t {
		#ifdef WINDOWS
			HANDLE h;

			mutex_t() : h(0) {}
			~mutex_t() { CloseHandle(h); h = 0; }
			void acquire() { WaitForSingleObject(h, INFINITE); }
			void release() { ReleaseMutex(h); }

			static bool_t make(mutex_t &mutex) {
				mutex.h = CreateMutex(0, false, 0);
				return mutex.h != 0;
			}
		#else
			pthread_mutex_t m;
			mutex_t() {}
			~mutex_t() { pthread_mutex_destroy(&m); }
			void acquire() { pthread_mutex_lock(&m); }
			void release() { pthread_mutex_unlock(&m); }
			static bool_t make(mutex_t &mutex) { return pthread_mutex_init(&mutex.m, 0) == 0; }
		#endif
	private:
		mutex_t(const mutex_t &m);
		mutex_t &operator =(const mutex_t &m);
	};

	// exceptions...
	class lock_t {
		mutex_t &mutex;
		lock_t();
	public:
		lock_t(mutex_t &m) : mutex(m) { mutex.acquire(); }
		~lock_t() { mutex.release(); }
	};


	class thread_t {
		thread_t(); // plonk

	protected:
		struct params_t {
			#ifdef WINDOWS
				HANDLE	handle, spawnex;
			#else
				pthread_t thread;
				sem_t spawnex;
			#endif

			bool_t	spawned, gone;	// cheesy

			typedef bool_t (*fun_init_t)(void *self);
			typedef int (*fun_run_t)(void *self);
			fun_init_t	fun_init;
			fun_run_t	fun_run;
		} params;


		template <typename T> thread_t(T *p) {
			std::memset(&params, 0, sizeof(params));
			setup(p);
		}


		// allocate thread local stuff etc... (serialized)
		virtual bool_t init() = 0;
		// on return, the thread dies.
		virtual int run() = 0;

		virtual ~thread_t() { }

		// syntactic sugar
		// it's certainly not the most efficient scheme
		// but it doesn't matter at this stage.
		// don't use something like that latter on, eh.
		template <typename T> static inline bool_t do_init(void *self) { return static_cast<T *>(self)->init(); }
		template <typename T> static inline int do_run(void *self) { return static_cast<T *>(self)->run(); }

		template <typename T> static void setup(T *self) {
			thread_t *p = (thread_t *) self;
			p->params.fun_init = do_init<T>;
			p->params.fun_run = do_run<T>;
		}

		#ifdef WINDOWS
			static DWORD WINAPI trampoline(void *p)
		#else
			static void *trampoline(void *p)
		#endif
		{
			thread_t *thread = (thread_t*) p;

			thread->params.spawned = thread->params.fun_init(p);
			#ifdef WINDOWS
				ReleaseSemaphore(thread->params.spawnex, 1, 0);
			#else
				sem_post(&thread->params.spawnex);
			#endif

			if (thread->params.spawned) {

				const int rc = thread->params.fun_run(p);
				thread->params.gone = true;
				#ifdef WINDOWS
					return rc;
				#else
					return (void*)rc;
				#endif
			}
			else
				return 0;
		}


	public:
		static bool_t spawn(thread_t *thread) {
			thread->params.gone = false;
			thread->params.spawned = false;
			#ifdef WINDOWS
				thread->params.spawnex = CreateSemaphore(0, 0, 1, 0);	// really a mutex

				DWORD tid;
				thread->params.handle = (HANDLE) CreateThread(0, 0, thread_t::trampoline, (void *) thread, 0, (LPDWORD)&tid);
				if (thread->params.handle)
					// when we get past this one, the thread is guaranted to be properly ignited.
					WaitForSingleObject(thread->params.spawnex, INFINITE);
			#else
				sem_init(&thread->params.spawnex, false, 0);

                pthread_attr_t ta;
                pthread_attr_init(&ta);
                pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED);
                pthread_attr_setscope(&ta, PTHREAD_SCOPE_SYSTEM);

				if (pthread_create(&thread->params.thread, &ta, thread_t::trampoline, (void*)thread) == 0)
					sem_wait(&thread->params.spawnex);
			#endif

			return thread->params.spawned;
		}

		/*
		static void kill(thread_t *thread) {
			TerminateThread(thread->params.handle, -1);
		}
		*/
	};

}

#endif
