/*
 *
 *
 *    tthread.h
 *
 *    Version 0.96
 *
 *    Header file for 'thread' class for implementing a C++ interface to
 *    UNIX-style threads.  This class currently only implements LinuxThreads
 *    library functions.
 *
 *    =========================================================================
 *    
 *    Copyright (C) 2000-2013 by Matt Roberts, KK5JY.
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 2.1 of the License, or (at your option) any later version.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *    =========================================================================
 *
 *    TODO
 *       +  Improve TCondition to integrate the TMutex into the class, and
 *          use a predicate supplied by the user to decide the predicate,
 *          instead of having the TMutex and condition code external to the
 *          object.
 *       +  Improve all sync classes to have their core component (mutex,
 *          cond, etc.) as a base class instead of a private member.
 *
 *
 */


#ifndef __THREAD_H
#define __THREAD_H
	#include <pthread.h>
	#include <semaphore.h>
	#include <sched.h>
	#include <signal.h>
	#include <errno.h>


	namespace my {
		/* predeclare a class */
		class condition;


		//
		//
		//   class mutex
		//
		//   Encapsulates a mutex for thread synchronization.
		//
		//

		class mutex {
			private:
				pthread_mutex_t m_mutex;
				pthread_mutexattr_t m_attr;
				friend class condition;

			public:
				enum types {
					#ifdef PTHREAD_MUTEX_NORMAL
						normal         = PTHREAD_MUTEX_NORMAL,
						error_checking = PTHREAD_MUTEX_ERRORCHECK,
						resursive      = PTHREAD_MUTEX_RECURSIVE,
						default_type   = PTHREAD_MUTEX_DEFAULT
					#else
						fast           = PTHREAD_MUTEX_FAST_NP,
						recursive      = PTHREAD_MUTEX_RECURSIVE_NP,
						error_checking = PTHREAD_MUTEX_ERRORCHECK_NP,
						default_type   = fast
					#endif
				};

			public:
				mutex (types type = default_type);
				~mutex (void);

				bool lock    (void);
				bool tryLock (void);
				bool unlock  (void);
		};
	}


	inline my::mutex::mutex (mutex::types kind) {
		pthread_mutexattr_init (&m_attr);
		// GLIBC 2.1 doesn't like this.
		// pthread_mutexattr_settype (&m_attr, kind);
		pthread_mutex_init (&m_mutex, &m_attr);
	}


	inline my::mutex::~mutex (void) {
		pthread_mutexattr_destroy (&m_attr);
		pthread_mutex_destroy (&m_mutex);
	}


	inline bool my::mutex::lock (void) {
		if (pthread_mutex_lock (&m_mutex))
			return false;
		return true;
	}


	inline bool my::mutex::tryLock (void) {
		int rtn = pthread_mutex_trylock (&m_mutex);
		if (rtn == EBUSY)
			return false;
		return true;
	}


	inline bool my::mutex::unlock (void) {
		if (pthread_mutex_unlock (&m_mutex))
			return false;
		return true;
	}


	namespace my {
		/*
		 *
		 *   class semaphore
		 *
		 *   Encapsulates a semaphore for thread synchronization
		 *
		 */

		class semaphore {
			private:
				sem_t sem;

			public:
				semaphore (unsigned int ivalue = 0);
				~semaphore (void);

				bool post (void);
				bool wait (void);
				bool tryWait (void);
				unsigned int getValue (void);
		};
	}

	inline my::semaphore::semaphore (unsigned int initial_value) {
		sem_init (&sem, 0, initial_value);
	}


	inline my::semaphore::~semaphore (void) {
		sem_destroy (&sem);
	}


	inline bool my::semaphore::post (void) {
		if (sem_post (&sem))
			return false;
		return true;
	}


	inline bool my::semaphore::wait (void) {
		if (sem_wait (&sem))
			return false;
		return true;
	}


	inline bool my::semaphore::tryWait (void) {
		if (sem_trywait (&sem))
			return false;
		return true;
	}


	inline unsigned int my::semaphore::getValue (void) {
		unsigned int value;
		sem_getvalue (&sem, (int *)&value);
		return value;
	}


	namespace my {
		/*
		 *
		 *   class condition
		 *
		 *   Encapsulates a condition variable.
		 *
		 */

		class condition {
			private:
				pthread_cond_t cond;

			public:
				condition (void) { pthread_cond_init (&cond, 0); };
				~condition (void) { pthread_cond_destroy (&cond); };

				// a thread that calls this function will wait on this condition variable
				// with a specific mutex
				bool wait (mutex &);

				// a thread that calls this function will wait on this condition variable
				bool wait (void);

				// a thread that calls signal() will signal one thread waiting on this
				// condition variable
				bool signal (void);

				// a thread that cals broadcast() will broadcast a signal to all threads
				// waiting on this condition variable
				bool broadcast (void);

				// this thread is like 'wait()' but with a timeout
				bool timedWait (mutex &m, const timespec *abstime);

		};
	}

	inline bool my::condition::wait (mutex &m) {
		if (pthread_cond_wait (&cond, &m.m_mutex))
			return false;
		return true;
	}

	inline bool my::condition::timedWait (mutex &m, const timespec *abstime) {
		if (pthread_cond_timedwait (&cond, &m.m_mutex, abstime))
			return false;
		return true;
	}

	inline bool my::condition::signal (void) {
		if (pthread_cond_signal (&cond))
			return false;
		return true;
	}

	inline bool my::condition::broadcast (void) {
		if (pthread_cond_broadcast (&cond))
			return false;
		return true;
	}


	namespace my {
		/*
		 *
		 *   class thread
		 *
		 *   Encapsulates a single thread of execution.
		 *
		 */

		class thread {
			private:
				//   This structure is used internally to thread to call the function associated with
				//   a particular thread object, and pass parameters to it.
				struct TTHREAD_ARG {
					thread *t_thread;
					void *args;
				};

				pthread_t tid;         // holds the thread ID
				pthread_attr_t attr;   // an attribute struct for making threads
				TTHREAD_ARG real_args; // object for passing arguments to threads

				/* This function is called by every thread encapsulated by a thread object. */
				/* It must be reentrant-safe                                                 */
				static void *execute (void *thread_arg) {
					my::thread *thrd = ((TTHREAD_ARG*)(thread_arg))->t_thread;
					void   *args = ((TTHREAD_ARG*)(thread_arg))->args;

					args = thrd->run (args);
					::pthread_exit (args);
					return 0;  // satisfy GCC's need for return value
				}


				//
				//  Common code for constructors
				//

				void do_init (void *args) {
					real_args.t_thread = this;
					real_args.args = args;
					pthread_attr_init (&attr);
				}

			protected:

				/* These functions should only be called by a derived class's run() function */
				/* Some of these will probably be deleted later                              */
				void exit   (void *stat) { return pthread_exit (stat);  };
				int detach (void)        { return pthread_detach (tid); };
				int yield  (void)        { return sched_yield ();       };

			public:

				/* [con|de]structors */
				thread (void *args = 0) { do_init (args); };
				virtual ~thread (void) { pthread_attr_destroy (&attr); };

				/* this function MUST be overridden -- it is the working function of the thread */
				virtual void *run (void *arg = NULL) = 0;

				/* returns the TID */
				pthread_t id (void) { return tid; };

				// if another thread calls the kill() function of this thread object, that signal
				// will be passed to this thread
				int kill (int sig) { return pthread_kill (tid, sig); };

				// if another thread calls the join() function of this thread object, that thread
				// will wait for this thread's return
				bool join (void **rtn_value = 0);

				// start thread
				bool start (void);

		};
	}

	inline bool my::thread::join (void **rtn_value) {
		if (pthread_join (tid, rtn_value))
			return false;
		return true;
	}

#endif
