/*	XGThread.cpp
 *
 *		Thread support. This allows me to encapsulate simple thread
 *	semantics behind a simple object declaration. And this allows me
 *	to use the same semantics on both Windows, the MacOS and MkLinux
 *	X Windows.
 *
 *		If OPT_THREADED == 0, this will be built without thread
 *	support. This should work, so long as you don't call new XGThread.
 *	Of course things are pretty boring...
 */

/*  YAAF - Yet another application framework
 *  Copyright (C) 1997, 1998 William Edward Woody
 *  
 *  This library is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU Library
 *  General Public License as published by the Free Software
 *  Foundation; either version 2 of the License, or 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 MERCHANTABIILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE. See the GNU Library General Public License for
 *  more details.
 *  
 *  You should have received a copy of the GNU Library 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.
 *  
 *  To contact the author, either e-mail me at woody@pandawave.com, 
 *	or write to
 *  
 *          William Edward Woody
 *          In Phase Consulting
 *          1545 Ard Eevin Avenue
 *          Glendale, CA 91202
 *
 *	Or visit us on the web at http://www.pandawave.com
 */

#include <XConfig.h>			// must be first to optimize compile on MSVC++
#include <XError.h>
#include <XThread.h>

#if OPT_WINOS
	#ifdef __MWERKS__
	#define _MT
	#endif
#include <process.h>			// _beginthreadex/_endthreadex
#endif

/************************************************************************/
/*																		*/
/*	Globals																*/
/*																		*/
/************************************************************************/

long XGThread::gCount = 0;

#if OPT_THREADED == 1

static XGSemaphore GMutex;
XGThread *XGThread::gList = NULL;

#if OPT_WINOS == 1
extern DWORD GAppThreadID = -1;
#endif // OPT_WINOS

#if OPT_MACOS
long XGThread::gStackSize = 0;
bool XGThread::gThreadPool = false;
bool GYieldThread;					/* Yield rest of slice? */
#endif

#if OPT_XWIN
static pthread_t GAppThreadID;
#endif

#endif // OPT_THREADED

static XGThread GAppThread;			/* Application thread object		*/

/************************************************************************/
/*																		*/
/*	Thread Support														*/
/*																		*/
/************************************************************************/

// @G"Construction/Destruction"

/*	@F"XGThread::XGThread" @I
 *
 *		Create this thread. Calls the procudure specified inside the
 *	thread. The 'arg' field gives the four-byte argument which is used
 *	to invoke the provided procedure. The 'stack' object gives the
 *	size of the stack; if zero, the stack is set to the default size.
 *	And 'suspend', if true, creates this thread in the suspended state.
 */

XGThread::XGThread(XGThreadProcPtr proc, long arg, long stack, bool suspend)
{
	gCount++;
	
#if OPT_THREADED == 1
	/*
	 *	Threaded support
	 */
	
	fArg = arg;
	
#if OPT_MACOS == 1
	/*
	 *	Mac version
	 */

	OSErr err;
	
	/*
	 *	Create this thread. The thread is built suspended so I can
	 *	have a chance to do some initialization before it runs.
	 */
	
	if (stack == 0) stack = gStackSize;
	fThreadID = kNoThreadID;
	fProc = proc;
	fAttach = 1;					// One owner--me
	if (0 != (err = NewThread(kCooperativeThread,	// only type on MacOS
							  ThreadEntry,			// my entry point
							  (void *)this,			// and my argument to entry
							  stack,				// stack size default
							  (!gThreadPool ?		// suspended at first
							  	kNewSuspend :
							  	(kNewSuspend | kUsePremadeThread)),
							  NULL,					// Thread result--who cares?
							  &fThreadID)))			// Thread ID of me
		throw XGPostError(KXGThreadCreateError);
#endif

#if OPT_WINOS == 1
	/*
	 *	Windows 95/Windows NT version: what's the default thread ID?
	 */
	
	if (GAppThreadID == -1) {
		GAppThreadID = GetCurrentThreadId();
	}
	
	/*
	 *	Create this thread. The thread is built suspended so I can
	 *	have a chance to do some initialization before it runs
	 */
	
	fProc = proc;
	fAttach = 1;
	fThreadHandle = (HANDLE)_beginthreadex(NULL,	// No security attributes
								   stack,			// default stack size
								   ThreadEntry,		// my entry point
								   (LPVOID)this,	// and my args
								   CREATE_SUSPENDED,// create me suspended
								   &fThreadID);		// and what am I?
	if (fThreadHandle == NULL) {
		throw XGPostError(KXGThreadCreateError);
	}
	
	fThreadSuspend = true;
#endif

#if OPT_XWIN == 1
	/*
	 *	<Pthreads> version
	 */
	
	if (pthread_create(&fThreadID,NULL,ThreadEntry,(void *)this)) {
		throw XGPostError(KXGThreadCreateError);
	}
#endif
	
	/*
	 *	Insert thread into linked list
	 */
	
	GMutex.EnterCritical();
	fNext = gList;
	gList = this;
	GMutex.LeaveCritical();

	/*
	 *	And resume the thread if it's what the user wants
	 */
	
	if (!suspend) Resume();

#else // OPT_THREADED == 0
	#pragma unused(proc)
	#pragma unused(arg)
	#pragma unused(stack)
	#pragma unused(suspend)
	
	throw XGPostError(KXGThreadNotSupported);
#endif
}

/*	@F"XGThread::XGThread" @I
 *
 *		This is a special constructor, which should never be called by
 *	another procedure, which creates the thread which represents the main
 *	task.
 */

XGThread::XGThread()
{
	gCount++;

#if OPT_THREADED == 1

	/*
	 *	Threaded support
	 */
	
	GMutex.EnterCritical();
	
	fProc = NULL;
	fNext = gList;
	gList = this;

#if OPT_MACOS == 1
	fThreadID = kApplicationThreadID;
#endif

#if OPT_WINOS == 1
	if (GAppThreadID == -1) GAppThreadID = GetCurrentThreadId();
	fThreadID = GAppThreadID;
	fThreadHandle = NULL;
#endif

#if OPT_XWIN == 1
	fThreadID = pthread_self();
	GAppThreadID = fThreadID;
#endif
	
	GMutex.LeaveCritical();

#endif // OPT_THREADED
}

/*	@F"XGThread::~XGThread" @I
 *
 *		Stop this thing if it's running
 */

XGThread::~XGThread()
{
#if OPT_THREADED == 1

#if OPT_MACOS == 1
	ThreadID t;
	
	if ((fThreadID != kApplicationThreadID) && (fThreadID != kNoThreadID)) {
		/*
		 *	We've got to kill this first
		 */
		
		t = fThreadID;
		fThreadID = kNoThreadID;
		DisposeThread(t,NULL,(gThreadPool == NULL));
	}
#endif

#if OPT_WINOS == 1
	if (fThreadHandle) Kill();
#endif

#if OPT_XWIN == 1
	if (!pthread_equal(fThreadID,GAppThreadID)) Kill();
#endif
	
	/*
	 *	Remove thread from linked list
	 */
	
	XGThread *thread;

	GMutex.EnterCritical();
	if (gList == this) gList = fNext;
	else {
		for (thread = gList; thread != NULL; thread = thread->fNext) {
			if (thread->fNext == this) {
				thread->fNext = fNext;
				break;
			}
		}
	}
	GMutex.LeaveCritical();
	
#endif // OPT_THREADED

	gCount--;
}

/************************************************************************/
/*																		*/
/*	Thread Management													*/
/*																		*/
/************************************************************************/

// @G"Thread Management"

/*	@F"XGThread::YieldThread" @I
 *
 *		Yield to any thread (If no thread support, return immediately)
 *
 *		'true' if I should yield the rest of my processor timeslice
 */

void XGThread::YieldThread(bool f)
{
#if OPT_THREADED == 1
	/*
	 *	Threads are supported
	 */
	
#if OPT_MACOS == 1
	if (!f) GYieldThread = false;	// true if in processor-intensive loop
	YieldToAnyThread();				/* Always yield computational time */
#endif

#if OPT_WINOS == 1
	if (f) Sleep(0);				/* Yield rest of my timeslice */
#endif

#if OPT_XWIN == 1
	if (f) sched_yield();			// yield rest of my timeslice
#endif

#else // OPT_THREADED == 0
	#pragma unused(f)
#endif // OPT_THREADED
}

/*	@F"XGThread::SetThreadPool" @I
 *
 *		Macintosh specific; calls the 'CreateThreadPool' routine in
 *	the thread manager
 */

#if OPT_MACOS
void XGThread::SetThreadPool(short pool, long stack)
{
	gStackSize = stack;
	if (CreateThreadPool(kCooperativeThread,pool,stack)) {
		throw XGPostError(KXGOutMemory);
	}
	gThreadPool = true;
}

#endif

/*	@F"XGThread::CurrentThread" @I
 *
 *		Which is the current thread object? Get the thread ID and search
 *	for the current thread object
 */

XGThread *XGThread::CurrentThread()
{
#if OPT_THREADED == 1
	/*
	 *	Threads are supported
	 */
	
	XGThread *t;

#if OPT_MACOS == 1
	ThreadID id;
	
	// Find the thread identifier on the Macintosh
	if (GetCurrentThread(&id)) return &GAppThread;
	if (id == kApplicationThreadID) return &GAppThread;
#endif

#if OPT_WINOS == 1
	DWORD id;
	
	// Find the thread identifier on Windows
	id = GetCurrentThreadId();
	if (id == GAppThreadID) return &GAppThread;
#endif

#if OPT_XWIN == 1
	pthread_t curthread;
	curthread = pthread_self();
	if (pthread_equal(curthread,GAppThreadID)) return &GAppThread;
#endif

	// Search my linked list until I find the XGThread object which
	// corrisponds to this OS thread
	GMutex.EnterCritical();
	for (t = gList; t != NULL; t = t->fNext) {
	
#if OPT_XWIN
		if (pthread_equal(curthread,t->fThreadID)) break;
#else
		if (t->fThreadID == id) break;
#endif
	}
	GMutex.LeaveCritical();
	
	// If not found, punt with a global application thread identifier
	if (t == NULL) return &GAppThread;
	return t;
	
#else // OPT_THREADED == 1
	/*
	 *	No thread support--punt
	 */
	
	return &GAppThread;				// Always return same identifier
#endif /// OPT_THREADED
}

/************************************************************************/
/*																		*/
/*	Thread Management													*/
/*																		*/
/************************************************************************/

/*	@F"XGThread::Suspend" @I
 *
 *		Suspend the specified thread
 */

void XGThread::Suspend(void)
{
#if OPT_THREADED == 1
#if OPT_MACOS == 1
	if (fThreadID != kNoThreadID) {
		::SetThreadState(fThreadID,kStoppedThreadState,kNoThreadID);
	}
#endif

#if OPT_WINOS == 1
	if (!IsSuspend() && (fThreadHandle != NULL)) {
		::SuspendThread(fThreadHandle);
		fThreadSuspend = false;
	}
#endif

#if OPT_XWIN == 1
	// suspend? 
#endif

#endif // OPT_THREADED
}

/*	@F"XGThread::Resume" @I
 *
 *		Resume specified thread
 */

void XGThread::Resume(void)
{
#if OPT_THREADED == 1
#if OPT_MACOS == 1
	if (fThreadID != kNoThreadID) {
		SetThreadState(fThreadID,kReadyThreadState,kNoThreadID);
	}
#endif

#if OPT_WINOS == 1
	if (IsSuspend() && (fThreadHandle != NULL)) {
		::ResumeThread(fThreadHandle);
		fThreadSuspend = true;
	}
#endif

#if OPT_XWIN == 1
	// resume?
#endif

#endif // OPT_THREADED
}

/*	@F"XGThread::IsSuspend" @I
 *
 *		Is this thread suspended?
 */

bool XGThread::IsSuspend(void)
{
#if OPT_THREADED == 1
#if OPT_MACOS == 1
	ThreadState state;
	
	if (fThreadID == kNoThreadID) return true;		/* Like it's not running */
	GetThreadState(fThreadID,&state);
	return (state == kStoppedThreadState) ? true : false;
#endif

#if OPT_WINOS == 1
	return fThreadSuspend;
#endif

#if OPT_XWIN == 1
	return false;				// suspend?
#endif
#else // OPT_THREADED == 0
	/*
	 *	No thread support--simply return false: I never suspend
	 */
	
	return false;
#endif
}

/*	@F"XGThread::Kill" @I
 *
 *		Kill this thread
 */

void XGThread::Kill(void)
{
#if OPT_THREADED == 1
	/*
	 *	Threads supported
	 */
	
#if OPT_MACOS == 1
	ThreadID t;
	
	if (fThreadID == kNoThreadID) return;
	Assert(fThreadID != kApplicationThreadID);
	
	t = fThreadID;
	fThreadID = kNoThreadID;
	DisposeThread(t,NULL,(gThreadPool == NULL));
#endif

#if OPT_WINOS == 1
	if (fThreadHandle) {
		TerminateThread(fThreadHandle,0);
		CloseHandle(fThreadHandle);
		fThreadHandle = NULL;
		Detach();
	}
#endif

#if OPT_XWIN == 1
	pthread_cancel(fThreadID);
	fThreadValid = false;
#endif
#else // OPT_THREADED == 0
	Assert(false);
#endif
}

/*	@F"XGThread::IsAlive" @I
 *
 *		Is this thread alive?
 */

bool XGThread::IsAlive(void)
{
#if OPT_THREADED == 1
	/*
	 *	Threads supported
	 */
	
#if OPT_MACOS == 1
	return (fThreadID != NULL) ? true : false;
#endif

#if OPT_WINOS == 1
	if (fThreadID == GAppThreadID) return true;
	if (fThreadHandle != NULL) return true;
	return false;
#endif

#if OPT_XWIN == 1
	return fThreadValid;
#endif

#else // OPT_THREADED == 0
	/*
	 *	No threads. Which means that this is my global app. Which
	 *	means I'm alive
	 */
	
	return true;
#endif
}

/************************************************************************/
/*																		*/
/*	Internal thread utilities											*/
/*																		*/
/************************************************************************/

#if OPT_THREADED == 1

#if OPT_MACOS == 1

/*	XGThread::ThreadEntry
 *
 *		This is the actual thread entry point. This wrappers some stuff
 *	for my thread. This contains the throw/catch routines to prevent
 *	tossing this exception across the stack boundary.
 */

pascal void *XGThread::ThreadEntry(void *ptr)
{
	XGThread *tptr = (XGThread *)ptr;
	
	try {
		if (tptr->fProc) (tptr->fProc)(tptr,tptr->fArg);
	}
	catch (XGError &x) {
		// If it's one of mine, do the default error display
		x.DisplayError();
	}

	catch (...) {
		// all the rest get eaten and absorbed.
		XGPostError te(KXGRandomException);
		te.DisplayError();
	}
	
	if (gThreadPool) DisposeThread(tptr->fThreadID,NULL,true);
	tptr->fThreadID = kNoThreadID;
	tptr->Detach();
	return NULL;
}

#endif // OPT_MACOS

#if OPT_WINOS == 1

/*	XGThread::ThreadEntry
 *
 *		The entrypoint for the Windows application
 */

unsigned int WINAPI XGThread::ThreadEntry(LPVOID ptr)
{
	XGThread *tptr = (XGThread *)ptr;
	
	try {
		if (tptr->fProc) (tptr->fProc)(tptr,tptr->fArg);
	}
	catch (XGError &x) {
		// If it's one of mine, do the default error display
		x.DisplayError();
	}
	catch (...) {
		// all the rest get eaten and absorbed.
		XGPostError te(KXGRandomException);
		te.DisplayError();
	}

	tptr->fThreadHandle = NULL;
	tptr->Detach();
	return 0;
}

#endif // OPT_WINOS

#if OPT_XWIN == 1

/*	XGThread::ThreadEntry
 *
 *		The entrypoint for Posix-based threaded application
 */

void *XGThread::ThreadEntry(void *ptr)
{
	XGThread *tptr = (XGThread *)ptr;
	
	tptr->fThreadValid = true;
	try {
		if (tptr->fProc) (tptr->fProc)(tptr,tptr->fArg);
	}
	catch (XGError &x) {
		// If it's one of mine, do the default error display
		x.DisplayError();
	}
	catch (...) {
		// all the rest get eaten and absorbed.
		XGPostError te(KXGRandomException);
		te.DisplayError();
	}

	tptr->fThreadValid = false;
	tptr->Detach();
	return 0;
}

#endif // OPT_WINOS

#endif // OPT_THREADED


