/**********************************************************************
 
	Copyright (C) 2003-2004
	Hirohisa MORI <joshua@nichibun.ac.jp>
	Tomoki SEKIYAMA <sekiyama@yahoo.co.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

**********************************************************************/


// ===========================================================================
//	LThreadLow.cp				PowerPlant 2.2		©1994-1999 Metrowerks Inc.
// ===========================================================================
//	Original Author: Paul Lalonde

#ifdef PowerPlant_PCH
	#include PowerPlant_PCH
#endif

#include <LThread.h>
#include <UDebugging.h>
#include <UEnvironment.h>
#include <UException.h>
#include <UThread.h>

#include "CYieldRepeater.h"
#include "VApplication.h"
#include "AppConstants.h"

#if THREAD_DEBUG
	#include <cstdio>
	//#define THREAD_DEBUG_MSG
#endif

#include <Gestalt.h>
#include <Power.h>
#include <Processes.h>

#if !TARGET_RT_MAC_MACHO
	#include <SegLoad.h>
#endif

#include <Threads.h>

#if TARGET_RT_MAC_CFM
	#include <CodeFragments.h>
#endif

#if THREAD_PROFILE
	#include <Profiler.h>
#endif


// turn off profiling in this module
#pragma profile off


QHdr** LThread::sReadyQueue = NULL;
LThread* suggestedThread = NULL;

PP_Begin_Namespace_PowerPlant

// ===========================================================================
//	¥ Constants
// ===========================================================================


#define	DEBUG_COMPLETION_PROC	0		// set non-zero to break on entry


// define debugging macro, if needed
#if DEBUG_COMPLETION_PROC
#	define BREAK_INTO_DEBUGGER()	::Debugger()
#else
#	define BREAK_INTO_DEBUGGER()
#endif

// register-based arguments & return values
#if TARGET_RT_MAC_CFM || TARGET_RT_MAC_MACHO
#	define A0_PARAM
#	define A1_PARAM
#else
#	define A0_PARAM		: __A0
#	define A1_PARAM		: __A1
#endif
#define A0_RESULT		A0_PARAM
#define A1_RESULT		A1_RESULT


// milliseconds between attempts to wake up a thread
const SInt32		kThreadWakeupDelay	= 10;


// ===========================================================================
//	¥ Local function declarations
// ===========================================================================


#if THREAD_DEBUG
static void	DumpOneThread(LThread& thread, void *arg);
void ThreadDebugMessage(const char*);
void ThreadDebugMessagePrint();
#endif

// ===========================================================================
//	Type coercions from OS structures to our structures.  Ugly.
// ===========================================================================

inline	SThreadParamBlk*
GetParmBlkPtr(ParmBlkPtr pbPtr A0_PARAM) A0_RESULT
{
	PP_Using_Namespace_Std	// Pro 3's MSL offsetof macro isn't
							// namespace std savvy.

	return (reinterpret_cast<SThreadParamBlk *>(
			reinterpret_cast<char *>(pbPtr) -
			offsetof(SThreadParamBlk, ioPB)));
}

inline SThreadTMTask*
GetTimeMgrPtr(TMTaskPtr tmTaskPtr A1_PARAM) A0_RESULT
{
	PP_Using_Namespace_Std	// Pro 3's MSL offsetof macro isn't
							// namespace std savvy.

	return (reinterpret_cast<SThreadTMTask *>(
			reinterpret_cast<char *>(tmTaskPtr) -
			offsetof(SThreadTMTask, ioTask)));
}


// ===========================================================================
//	¥ Callbacks and Completion Routines
// ===========================================================================


// ---------------------------------------------------------------------------
//	¥ DoEntry	[static]
// ---------------------------------------------------------------------------
//	Callback to the Thread Manager.
//
//	This function is called when a thread begins execution.  It simply
//	calls the thread's Run() member function.  If Run() returns (either
//	normally or through the exception mechanism), the thread object is
//	deallocated.

pascal void*
LThread::DoEntry(void* arg)
{
	LThread* volatile	thread	= reinterpret_cast<LThread *>(arg);
	void* volatile		result	= NULL;

//	::DebugStr("\pEntering DoEntry");

	try
	{
		// run the thread
		result = thread->Run();
	}
	catch (...)
	{
		// all we want is to stop error propagation
	}

	// destroy thread
	thread->DeleteThread(result);

	return (result);
}


// ---------------------------------------------------------------------------
//	¥ DoExit	[static]
// ---------------------------------------------------------------------------
//	"Fake" termination function.  Does nothing.

#if THREAD_DEBUG

pascal void
LThread::DoExit(ThreadID /* inThread */, void* /* arg */)
{
//	::DebugStr("\pWarning: Thread Mgr thread destroyed before thread object; g");
}

#endif


// ---------------------------------------------------------------------------
//	¥ DoSwapIn	[static]
// ---------------------------------------------------------------------------
//	Callback to the Thread Manager.
//
//	Sets up global variables when a thread is switched in.  It also calls
//	the thread's swap function, if it has one.

pascal void
LThread::DoSwapIn(ThreadID, void* arg)
{
	try
	{
		LThread*	thread	= reinterpret_cast<LThread *>(arg);

	//	::DebugStr("\pentering DoSwapIn");

#if !TARGET_RT_MAC_CFM
		// get access to globals
		SInt32 savedA5 = SetA5(thread->mTimer.ioGlobals);
#endif

#if THREAD_PROFILE
		::ProfilerSwitchToThread(thread->mProfilerRef);
#endif

		// call custom swap function
		thread->SwapContext(true);

#if !TARGET_RT_MAC_CFM
		// restore globals ptr
		SetA5(savedA5);
#endif
	}
	catch (...)
	{
		// all we want is to stop error propagation
	}
}


// ---------------------------------------------------------------------------
//	¥ DoSwapOut	[static]
// ---------------------------------------------------------------------------
//	Callback to the Thread Manager.
//
//	Sets up global variables when a thread is switched out.  It also calls
//	the thread's swap function, if it has one.

pascal void
LThread::DoSwapOut(ThreadID, void* arg)
{
	try
	{
		LThread*	thread	= reinterpret_cast<LThread *>(arg);

	//	::DebugStr("\pentering DoSwapOut");

#if !TARGET_RT_MAC_CFM
		// get access to globals
		SInt32 savedA5 = SetA5(thread->mTimer.ioGlobals);
#endif

		// call custom swap function
		thread->SwapContext(false);

#if !TARGET_RT_MAC_CFM
		// restore globals ptr
		SetA5(savedA5);
#endif
	}
	catch (...)
	{
		// all we want is to stop error propagation
	}
}


// ---------------------------------------------------------------------------
//	¥ ThreadComplProc	[static]
// ---------------------------------------------------------------------------
//	I/O completion routine.  Converts the given parameter block into a
//	SThreadParamBlk, then resumes the given thread.

pascal void
LThread::ThreadComplProc(ParmBlkPtr pbPtr A0_PARAM)
{
	SThreadParamBlk*	tpb = GetParmBlkPtr(pbPtr);

	// break if desired
	BREAK_INTO_DEBUGGER();

#if !TARGET_RT_MAC_CFM
	long savedA5 = SetA5(tpb->ioGlobals);
#endif

	ThreadAsynchronousResume(tpb->ioThread);

#if !TARGET_RT_MAC_CFM
	SetA5(savedA5);
#endif
}


// ---------------------------------------------------------------------------
//	¥ ThreadTimerProc	[static]
// ---------------------------------------------------------------------------
//	Time Manager completion routine.  Converts the given Time Manager task
//	into a SThreadTMTask, then resumes the given thread.

pascal void
LThread::ThreadTimerProc(TMTaskPtr tmTaskPtr A1_PARAM)
{
	SThreadTMTask*	tpb = GetTimeMgrPtr(tmTaskPtr);

	// break if desired
	BREAK_INTO_DEBUGGER();

#if !TARGET_RT_MAC_CFM
	long savedA5 = SetA5(tpb->ioGlobals);
#endif

	ThreadAsynchronousResume(tpb->ioThread);

#if !TARGET_RT_MAC_CFM
	SetA5(savedA5);
#endif
}



// ===========================================================================
//	¥ Non-static member functions
// ===========================================================================


// ---------------------------------------------------------------------------
//	¥ SwapContext
// ---------------------------------------------------------------------------
//	Override this function to provide custom thread-swapping behavior

void 
LThread::SwapContext(Boolean swappingIn)
{
	if (swappingIn) {
		// new current thread
		sPrevThread = sThread;
		mTick = ::TickCount();
		sThread = this;

#ifdef THREAD_DEBUG_MSG
		char msg[1000];
		sprintf(msg, "[%d] %s -> %s\n",
			TickCount()%1000, sPrevThread?sPrevThread->mName:"no sPrevThread",
			sThread?sThread->mName:"no sThread");
		ThreadDebugMessage(msg);
#endif

#ifdef THREAD_READY_FLAG
		if ( ! mReadyFlag ) {
			RemoveFromReadyQueue(this);
/*
			ThreadState state;
			OSErr err = ::GetThreadState(mThread, &state);
			::DebugStr("\pnot ready thread is started");
*/
		}
#endif
				
		// if this thread was in ready state, adjust ready thread count
		if (mState == threadState_Ready)
			MakeUnready();

		// this thread is becoming the current thread
		mState = threadState_Current;

	} else {
		// If this thread is in the current state, it means the thread is
		// yielding (ie, going into the ready state).  So we have to adjust
		// the thread's state as well as the ready thread count.

		if (mState == threadState_Current) {
			MakeReady();
		}
	}
}


// ---------------------------------------------------------------------------
//	¥ DumpAllThreads	[static]
// ---------------------------------------------------------------------------
//	Prints information about all of the known threads to stdout.

#if THREAD_DEBUG

void
LThread::DumpAllThreads()
{
	PP_STD::printf("\n %-3s %-9s  %5s %-10s Name",
				   "ID ",
				   "State",
				   "Flags",
				   "Wait Sem");

	LThread::DoForEach(DumpOneThread, NULL);

	PP_STD::printf("\nNumber of ready threads: %ld\n", (long) CountReadyThreads());
}


// ---------------------------------------------------------------------------
//	¥ Dump
// ---------------------------------------------------------------------------
//	Prints information about the thread to stdout.

void
LThread::Dump() const
{
	static char	*states[] = {
		"Current",
		"Ready",
		"Suspended",
		"Sleeping",
		"Waiting",
		"Blocked"
	};

	PP_STD::printf("\n#%-3lu %-9s  %c%c%c%-2c 0x%08.8p %s",
				   mThread,
				   states[mState],
				   (this == sMainThread) ? 'M' : ' ',
				   mPreemptive ? 'P' : 'C',
				   mRecycle ? 'R' : ' ',
				   mFPU ? 'F' : ' ',
				   mSemaphore,
				   mName);
}

#endif // THREAD_DEBUG


// ===========================================================================
//	¥ Utility functions
// ===========================================================================


// ---------------------------------------------------------------------------
//	¥ SetupAsynchronousResume
// ---------------------------------------------------------------------------
//	Set up the fields of a standard parameter block (SThreadParamBlk) so
//	that the thread may be resumed at interrupt time.

void
LThread::SetupAsynchronousResume(
	SThreadPBPtr	threadPB,
	IOCompletionUPP	callbackUPP)
{
	if (callbackUPP == NULL)
		callbackUPP = sThreadComplUPP;

	threadPB->ioThread						= mTimer.ioThread;
#if !TARGET_RT_MAC_CFM
	threadPB->ioGlobals						= mTimer.ioGlobals;
#endif
	threadPB->ioPB.F.ioParam.ioCompletion	= callbackUPP;
}


// ---------------------------------------------------------------------------
//	¥ SuspendUntilAsyncResume
// ---------------------------------------------------------------------------
//	Check the ioResult field of the given standard parameter block
//	(SThreadPBPtr);  if it's noErr, suspend the thread.

SInt16
LThread::SuspendUntilAsyncResume(SThreadPBPtr threadPB, SInt16 error)
{
	if (error == noErr)
	{
		Block();
		error = threadPB->ioPB.F.ioParam.ioResult;
	}

	return (error);
}


// ---------------------------------------------------------------------------
//	¥ SuspendUntilAsyncResume
// ---------------------------------------------------------------------------
//	Alternate form of I/O blocking.  This function will only block the thread
//	if the thread hasn't already completed.  This is useful in calls that
//	may complete before returning to their caller;  in that case a context
//	switch is avoided.

SInt16
LThread::SuspendUntilAsyncResume(SThreadPBPtr threadPB)
{
	SInt16	error	= threadPB->ioPB.F.ioParam.ioResult;

	if (error == 1)
	{
		Block();
		error = threadPB->ioPB.F.ioParam.ioResult;
	}
	else
	{
		RemoveTimeTask(mTimer);
		mAsyncCompleted = false;
	}

	return (error);
}


// ---------------------------------------------------------------------------
//	¥ ThreadAsynchronousResume	[static]
// ---------------------------------------------------------------------------
//	Resume a thread at interrupt time.

void
LThread::ThreadAsynchronousResume(LThread* thread)
{
	ThreadID	id	= thread->mThread;
	ThreadState	state;
	SInt16		err;

	THREAD_ASSERT(thread != NULL);

	// application may be about to quit
	if ( ! sInited )
		return;

	// get the thread's state
	err = ::GetThreadStateGivenTaskRef(sThreadTaskRef, id, &state);

	// could we get its state?
	if (err == noErr)
	{
		SThreadTMTask	&timer = thread->mTimer;

		// remove any time manager element from its queue
		RemoveTimeTask(timer);

		// is the thread stopped ?
		if (state == kStoppedThreadState)
		{
#ifdef THREAD_READY_FLAG
			thread->mReadyFlag = 2;
#endif
			thread->MakeReady();
			err = ::SetThreadReadyGivenTaskRef(sThreadTaskRef, id);

			// wake up this process (so thread can get a chance to run)
			thread->mAsyncCompleted = false;
			::WakeUpProcess(&sPSN);
		}
		else
		{
			// add task to timer queue, then prime it
			thread->mAsyncCompleted = true;
			InsertTimeTask(timer);
			PrimeTimeTask(timer, kThreadWakeupDelay);
		}
	}
	else
	{
		// Else, there's not much we can do.  Perhaps the thread was killed
		// while an asynchronous I/O operation was pending (ick).
	}
}


#if THREAD_DEBUG

// ---------------------------------------------------------------------------
//	¥ ThreadAssert
// ---------------------------------------------------------------------------
//	Called in response to an assertion failure.  Prints a message in
//	the debugger, then quits the application.

int		ThreadAssert(char* test, char* file, int line)
{
	extern char	*ThreadStrCat(const char* src, char* dest);

	static char	buff[256];
	char		*buffp	= buff + 1;
	long		base, digit;

	buffp = ThreadStrCat("Thread assert: ",	buffp);
	buffp = ThreadStrCat(test,				buffp);
	buffp = ThreadStrCat(", file ",			buffp);
	buffp = ThreadStrCat(file,				buffp);
	buffp = ThreadStrCat(", line ",			buffp);

	// convert line number
	if (line == 0)
		*buffp++ = '0';
	else
	{
		for (base = 100000; base >= 1; base /= 10)
		{
			digit = line / base;
			*buffp++ = (char) (digit + '0');
			line -= (int) (digit * base);
		}
	}

	buff[0]	= (char) (buffp - buff - 1);

	::DebugStr((StringPtr) buff);

	::ExitToShell();

	return (0);
}


static char*
ThreadStrCat(const char* src, char* dest)
{
	while (*src != '\0')
	{
		*dest++ = *src++;
	}

	return (dest);
}


// ---------------------------------------------------------------------------
//	¥ DumpOneThread
// ---------------------------------------------------------------------------

int td_test = 0;

static void
DumpOneThread(LThread& thread, void */* arg */)
{
	thread.Dump();
}

#endif	// THREAD_DEBUG


#pragma mark -
// ---------------------------------------------------------------------------
//	¥ ReadyQueue Implement
// ---------------------------------------------------------------------------


pascal ThreadID
LThread::DoThreadSchedule(SchedulerInfoRecPtr schedulerInfo)
{
	if ( schedulerInfo->SuggestedThreadID ) {
#ifdef THREAD_DEBUG_MSG
		ThreadDebugMessage("(");
		ThreadDebugMessage("*SUGGESTED*");
		ThreadDebugMessage(")");
#endif
		return schedulerInfo->SuggestedThreadID;
	}
		
	if ( theApp && theApp->GetState() == programState_Quitting )
		return sMainThread->mThread;

	suggestedThread = TopOfReadyThread();

#if THREAD_DEBUG
if ( td_test ) {
ThreadDebugDump();
printf("=> %s\n",suggestedThread?suggestedThread->mName:"*DEFAULT*");
}
#endif
#ifdef THREAD_DEBUG_MSG
	ThreadDebugMessage("(");
	ThreadDebugMessage(suggestedThread?suggestedThread->mName:"*DEFAULT*");
	ThreadDebugMessage(")");
#endif
	if ( suggestedThread == NULL )
		return sMainThread->mThread;
	return suggestedThread->mThread;
}

#if THREAD_DEBUG

int this_prev;

//inline void

void
displayReadyQueue(QHdr ** sReadyQueue);
void
displayReadyQueue(QHdr ** sReadyQueue)
{
	QElemPtr elem;
	struct ReadyQueueElem {
		struct ReadyQueueElem *	qLink;
		short					qType;
		LThread *				qThread;
		Boolean				qd;
		UInt32				qdTime;
	};
	printf("\n-----");
	for ( int pri = maxPriority-1;  pri >= 0;  pri-- ) {
		printf("\n%d",pri);
		for ( elem = sReadyQueue[pri]->qHead;  elem;  elem = elem->qLink ) {
			printf(" - %s[%d]%s",reinterpret_cast<struct ReadyQueueElem *>(elem)->qThread->mName,
						reinterpret_cast<struct ReadyQueueElem *>(elem)->qdTime%1000,
						reinterpret_cast<struct ReadyQueueElem *>(elem)->qd?"":"Q_ER!!");
#ifdef THREAD_DEBUG
			if ( ((int)reinterpret_cast<struct ReadyQueueElem *>(elem)->qThread) == td_test ) {
				printf("*THIS*");
				if ( this_prev == 0 )
					printf("*THIS-PREV*");
				this_prev = 1;
			}
			else {
				this_prev = 0;
			}
#endif
		}
	}
	printf("\n");
}
#endif

void
LThread::EnqueueReadyThread(LThread *inThread)
{
	StCritical critical;
	
	if ( ! inThread || inThread->mReadyLink.qd )
		return;
#ifdef THREAD_READY_FLAG
	if ( ! inThread->mReadyFlag ) {
//		::DebugStr("\pnot ready thread enqueue");
		return;
	}
#endif
	inThread->mReadyLink.qd = true;
	inThread->mReadyLink.qdTime = TickCount();
	inThread->mReadyLink.qThread = inThread;
	inThread->mReadyLink.qType = 122; // ReadyQueueType
	::Enqueue(reinterpret_cast<QElemPtr>(&inThread->mReadyLink), sReadyQueue[inThread->mPriority]);
#ifdef THREAD_DEBUG_MSG
//	displayReadyQueue(sReadyQueue);
#endif
}

void
LThread::RemoveFromReadyQueue(LThread *inThread)
{
	StCritical critical;

	if ( !inThread || ! inThread->mReadyLink.qd )
		return;
	::Dequeue(reinterpret_cast<QElemPtr>(&inThread->mReadyLink), sReadyQueue[inThread->mPriority]);
	inThread->mReadyLink.qd = false;
#ifdef THREAD_DEBUG_MSG
//	displayReadyQueue(sReadyQueue);
#endif
}

LThread *
LThread::TopOfReadyThread()
{
	StCritical critical;

	// CYieldRepeater patch
	extern CYieldRepeater *sCYieldRepeater;

	if ( sThread != sMainThread &&
		sCYieldRepeater && (sCYieldRepeater->mQuantum < 0 || sCYieldRepeater->mNextTicks < ::TickCount() )) {
#if THREAD_DEBUG
		if ( td_test && td_test == (int)GetCurrentThread() )
			printf("++td_test++ sCYieldRepeater->mQuantum over\n");
#endif
		if ( GetMainThread()->mState == threadState_Ready )
			return GetMainThread();
	}
	
	QElemPtr elem;
	int pri = maxPriority-1, i;
	
	// choose which priority queue to go through
#if 0
	UInt32 top = 0xffffffff, time;
	for ( i = 0;  i < maxPriority;  i++ ) {
		if ( (elem = (sReadyQueue[i]->qHead) ) != NULL ) {
			time = reinterpret_cast<struct ReadyQueueElem *>(elem)->qdTime - thTicksPriorityWeight * i;
			if ( top > time ) {
				top = time;
				pri = i;
			}
		}
	}
#else
	for ( i = maxPriority-1;  i >= 0 ;  i-- ) {
		if ( (elem = (sReadyQueue[i]->qHead) ) != NULL ) {
			pri = i;
			break;
		}
	}
#endif

	elem = sReadyQueue[pri]->qHead;
	if ( elem ) {
		LThread *thread = reinterpret_cast<struct ReadyQueueElem *>(elem)->qThread;
		return thread;
	}	
	// no available threads are queued...
	return NULL;
}

LThread *
LThread::DequeueReadyThread()
{
	StCritical critical;

	LThread *thread = TopOfReadyThread();
	if ( thread )
		RemoveFromReadyQueue(thread);
	return thread;
}

#if THREAD_DEBUG
void
LThread::ThreadDebugDump()
{
	displayReadyQueue(sReadyQueue);
//	DumpAllThreads();
}

char threadDebugMessage[32768] = "";
int threadDebugMessageLoc = 0;

void
ThreadDebugMessage(const char* msg)
{
	strcpy(threadDebugMessage+threadDebugMessageLoc, msg);
	threadDebugMessageLoc += strlen(msg);
}

void
ThreadDebugMessagePrint()
{
	if ( threadDebugMessageLoc == 0 )
		return;
	printf("-------\n");
	puts(threadDebugMessage);
	threadDebugMessageLoc = 0;
}

#endif

PP_End_Namespace_PowerPlant