/* ===========================================================================
	File:		TASKER.C

	Notes:		Cooperative multitasking

	Author:		G Robert Liddon @ 73b

	Created:	Wednesday 27th March 1996

	Copyright (C) 1996 DCI Ltd All rights reserved. 
  ============================================================================ */

/* ---------------------------------------------------------------------------
	Standard Lib Includes
	--------------------- */

/* ---------------------------------------------------------------------------
	Glib Includes
	------------- */
#include "tasker.h"
#include "gsys.h"
#include "gdebug.h"

#include "gtimsys.h"

/* ---------------------------------------------------------------------------
	My Includes
	----------- */

/* ---------------------------------------------------------------------------
	Defines
	------- */

/* ---------------------------------------------------------------------------
	Function Prototypes
	------------------- */
static void		ReturnToSchedulerIfCurrentTask(TASK *T);
static void		ExecuteTask(TASK *T);
static void 	AddToList(TASK **Head,TASK *ThisObj);
static void 	DetachFromList(TASK **Head,TASK *ThisObj);
static void 	LoTskKill(TASK *T);
static void		DoEpi(TASK * T);
static void		DoPro(TASK * T);
static void		ExtraMarkStack(u32 * Stack,int SizeLongs);
static int		CheckExtraStack(u32 * Stack,int LongsToCheck);

/* ---------------------------------------------------------------------------
	Defines
	------- */
#define NUM_OF_TASKS 100
#define DEFAULT_XTRA_PRO_STACK_LONGS 1024

/* ---------------------------------------------------------------------------
	Variables
	--------- */
static TASK	*	ActiveTasks;
static TASK	*	CurrentTask;
static TASK	*	T;

static U32		MemTypeForTasker;
static jmp_buf	SchEnv;			/* The Scheduler's enviroment */
static U32		ExecId;
static U32		ExecMask;
static int		TasksActive;


/*	Vars for epi pro stuff
	---------------------- */
static TSK_CBACK	EpiFunc;			/* Func to call before execing task of correct class */
static TSK_CBACK	ProFunc;			/* Func to call after execing task of correct class */
static U32			EpiProId;			
static U32			EpiProMask;

static DOTSK_CBACK	DoTasksPrologue;
static DOTSK_CBACK	DoTasksEpilogue;

/*	Vars for Xtra Stack Protection
	------------------------------ */
static TSK_CBACK	StackFloodCallback;
static BOOL			ExtraStackProtection;
static int			ExtraStackSizeLongs;

/*	---------------------------------------------------------------------------
	Function:		static void DoEpi(TASK * T)
	Purpose:		Do epilogue route if appropriate
	--------------------------------------------------------------------------- */
static void DoEpi(TASK * T)
{
	if (EpiFunc)
		if ((T->Id&EpiProMask)==EpiProId)
			EpiFunc(T);
}

/*	---------------------------------------------------------------------------
	Function:		static void DoPro(TASK * T)
	Purpose:		Do prologue route if appropriate
	--------------------------------------------------------------------------- */
static void DoPro(TASK * T)
{
	if (ProFunc)
		if ((T->Id&EpiProMask)==EpiProId)
			ProFunc(T);
}

/*	---------------------------------------------------------------------------
	Function:		BOOL TSK_OpenModule(U32 MemType)

	Purpose:		Init the tasker module

	Params:			MemType				=	Mem tasker uses for workspace

	Returns:		FALSE if an error
	--------------------------------------------------------------------------- */
BOOL TSK_OpenModule(U32 MemType)
{
	
	TasksActive=0;
	ActiveTasks=NULL;
	MemTypeForTasker=MemType;
	CurrentTask=NULL;
	TSK_ClearExecFilter();
	TSK_ClearEpiProFilter();
	TSK_SetDoTasksEpilogue(NULL);
	TSK_SetDoTasksPrologue(NULL);
	TSK_SetExtraStackProtection(FALSE);
	TSK_SetStackFloodCallback(NULL);
	TSK_SetExtraStackSize(DEFAULT_XTRA_PRO_STACK_LONGS*sizeof(u32));

	return TRUE;
}	

/*	---------------------------------------------------------------------------
	Function:	TASK *	TSK_AddTask(U32 Id,void (*Main)(TASK *T))

	Purpose:	Add a task to the list of those to do

	Returns:	-> to task to run
				NULL if no tasks available
	--------------------------------------------------------------------------- */
TASK *	TSK_AddTask(U32 Id,void (*Main)(TASK *T),int StackSize,int DataSize)
{
	TASK *			RetTask;
	MHANDLE			hndTask;

	GAL_STRUCT		G[4];

	G[0].OriginalSize=sizeof(TASK);
	G[1].OriginalSize=DataSize;

	if (ExtraStackProtection)
		G[2].OriginalSize=StackSize+ExtraStackSizeLongs*sizeof(u32);
	else
		G[2].OriginalSize=StackSize;


	G[3].OriginalSize=-1;

	hndTask=GAL_AllocMultiStruct(G,MemTypeForTasker,"TASK");

	if (hndTask==NULL_HANDLE)
		return(NULL);

	RetTask=GAL_Lock(hndTask);

	if (RetTask)
		{
		RetTask->Id = Id;

		RetTask->fToInit=1;
		RetTask->fToDie=0;
		RetTask->fKillable=1;
		RetTask->fActive=1;

		RetTask->Main=Main;

		RetTask->hndTask=hndTask;

		RetTask->Stack=(void *)(((U32) RetTask)+G[2].Offset);
		RetTask->StackSize=G[3].Offset-G[2].Offset;

		if (DataSize)
			RetTask->Data=(void *)(((U32) RetTask)+G[1].Offset);
		else
			RetTask->Data=NULL;

		RetTask->SleepTime=1;
		RetTask->fXtraStack=ExtraStackProtection;
		RetTask->XtraLongs=ExtraStackSizeLongs;

		if (RetTask->fXtraStack)
			ExtraMarkStack(RetTask->Stack,RetTask->StackSize/sizeof(u32));
		else
			GSYS_MarkStack(RetTask->Stack,RetTask->StackSize);

		/* if a task running then add as next in list or at beggining */

		if (CurrentTask)
			{
			AddToList(&CurrentTask->Next,RetTask);
			RetTask->Prev=CurrentTask;
			}
		else
			AddToList(&ActiveTasks,RetTask);

		TasksActive++;

		}

	return RetTask;
	
}	

/*	---------------------------------------------------------------------------
	Function:		void TSK_DoTasks(void)
	Purpose:			Run All the tasks
	--------------------------------------------------------------------------- */
void TSK_DoTasks(void)
{
	T=ActiveTasks;

	if (DoTasksPrologue)
		DoTasksPrologue();

	while (T)
		{
		if (T->fActive && (T->Id&ExecMask)==ExecId)
			{
			T->SleepTime--;

			if (!T->SleepTime)
				{

				if (!setjmp(SchEnv))
					{

					/* See if this task needs initing or not */
					CurrentTask=T;

					DoPro(T);

					if (T->fToInit)
						{
						T->fToInit=0;
						GSYS_SetStackAndJump((void *)((U32)T->Stack+T->StackSize-sizeof(void *)*4),(void *)ExecuteTask,T);
						}
					else
						longjmp(T->TskEnv,1);
					}
				else
					{

					/*	Back from the previous task	*/
					TASK *		NextT;


					NextT=T->Next;

					/*	Top this task if it was intended to die */

					if (T->fToDie)
						LoTskKill(T);

					T=NextT;
					}
				}
			else
				T=T->Next;
			}
		else
			T=T->Next;
		}

	if (DoTasksEpilogue)
		DoTasksEpilogue();

	CurrentTask=NULL;
}	


/*	---------------------------------------------------------------------------
	Function:		void TSK_Sleep(int Frames)
	Purpose:		This Task to sleep for an amount of frames
	Params:			T -> Task
					Frames = num of frames to sleep
	--------------------------------------------------------------------------- */
void TSK_Sleep(int Frames)
{
	TASK *	T;

	T=CurrentTask;


	ASSERT(T);

	if (TSK_IsStackCorrupted(T))
		{
		if (StackFloodCallback)
			StackFloodCallback(T);
		else
			ASSERT(!"TSK STACK CORRUPTION");
		}

//	ASSERT(!GSYS_IsStackOutOfBounds(T->Stack,T->StackSize));

	DoEpi(T);

	if (!setjmp(T->TskEnv))
		{
		/* Saving enviro so go back to scheduler */
		T->SleepTime=Frames;
		ReturnToSchedulerIfCurrentTask(CurrentTask);
		}
}

/*	---------------------------------------------------------------------------
	Function:		static void ReturnToScheduler(TASK *T)
	Purpose:		Return to scheduler
	Params:			T -> Current Task
	--------------------------------------------------------------------------- */
static void ReturnToSchedulerIfCurrentTask(TASK *T)
{
	if (TSK_IsStackCorrupted(T))
		{
		if (StackFloodCallback)
			StackFloodCallback(T);
		else
			ASSERT(!"TSK STACK CORRUPTION");
		}

	longjmp(SchEnv,1);
}	

/*	---------------------------------------------------------------------------
	Function:		void TSK_Die(void)
	Purpose:		Kill off current task
	--------------------------------------------------------------------------- */
void TSK_Die(void)
{
	if (CurrentTask)
		TSK_Kill(CurrentTask);
}
	
/*	---------------------------------------------------------------------------
	Function:		void TSK_Kill(TASK *T)
	Purpose:		Kill off a task
					If this is the current task then return to scheduler
	Params:			T -> Task to kill
	--------------------------------------------------------------------------- */
void TSK_Kill(TASK *T)
{
	if (T==CurrentTask && CurrentTask)
		{
		T->fToDie=TRUE;
		ReturnToSchedulerIfCurrentTask(T);
		}
	else
		LoTskKill(T);
}


/*	---------------------------------------------------------------------------
	Function:		TASK *	TSK_GetFirstActive(void);

	Purpose:		Get the first in the chain of active tasks

	Params:			T -> Task block to check

	Returns:		True if it is
	--------------------------------------------------------------------------- */
TASK *	TSK_GetFirstActive(void)
{
	return(ActiveTasks);
}	

/*	---------------------------------------------------------------------------
	Function:		BOOL TSK_IsStackCorrupted(TASK *T)

	Purpose:		Check to see if this task's stack has flooded

	Params:			T -> Task block to check

	Returns:		True if it is
	--------------------------------------------------------------------------- */
BOOL TSK_IsStackCorrupted(TASK *T)
{
	if (T->fXtraStack)
		{
		int		LongsOk;
		LongsOk=CheckExtraStack(T->Stack,T->StackSize/4);
		T->MaxStackSizeBytes=(u16)T->StackSize-(LongsOk*sizeof(u32));
		return (LongsOk < T->XtraLongs);
		}
	else
		return(GSYS_IsStackCorrupted(T->Stack,T->StackSize));
}

/*	---------------------------------------------------------------------------
	Function:		void TSK_JumpAndResetStack(void (*RunFunc)(TASK *))

	Purpose:		Current running task stack is reset and jumped off to
					another routine

	Params:			T -> Task block to check

	Returns:		True if it is
	--------------------------------------------------------------------------- */
void TSK_JumpAndResetStack(void (*RunFunc)(TASK *))
{
	TASK *	T;

	T=CurrentTask;

	if (T)
		{
		T->Main=RunFunc;
		GSYS_SetStackAndJump((void *)((U32)T->Stack+T->StackSize-sizeof(void *)*4),(void *)ExecuteTask,T);
		}
}

/*	---------------------------------------------------------------------------
	Function:		void TSK_SetStackAndJump(void (*RunFunc)(TASK *))

	Purpose:		Current running task

	Params:			T -> Task block to repoint

	Returns:		True if it is
	--------------------------------------------------------------------------- */
void TSK_RepointProc(TASK *T,void (*Func)(TASK *T))
{
	/*	If the current task is this task then just jump there else mark as
		needing initing and -> start to new func */

	if (T==CurrentTask)
		TSK_JumpAndResetStack(Func);
	else
		{
		T->fToInit=1;
		T->Main=Func;
		}
}		



/*	---------------------------------------------------------------------------
	Function:		TASK *	TSK_GetCurrentTask(void);

	Purpose:		Get the current executing task

	Returns:		-> Current execiting task block

	--------------------------------------------------------------------------- */
TASK *	TSK_GetCurrentTask(void)
{
	return(CurrentTask);
}	

/*	---------------------------------------------------------------------------
	Function:		BOOL	TSK_IsCurrentTask(TASK *T)

	Purpose:		Is this task the currently executing one?

	Returns:		TRUE or FALSE

	--------------------------------------------------------------------------- */
BOOL TSK_IsCurrentTask(TASK *T)
{	
	return(T==CurrentTask);
}	

/*	---------------------------------------------------------------------------
	Function:		TASK *TSK_Exist(TASK *T,U32 Id,U32 Mask)

	Purpose:		Is this task the currently executing one?

	Params:			T -> Calling task

	Returns:		-> first task found

	--------------------------------------------------------------------------- */
TASK *TSK_Exist(TASK *T,U32 Id,U32 Mask)
{	
	TASK *	ptrTask;
	TASK *	RetTask;

	ptrTask=ActiveTasks;
	RetTask=NULL;

	while (ptrTask && !RetTask)
		{
		if ((ptrTask != T) && (ptrTask->Id&Mask)==Id)
			RetTask=ptrTask;
		else
			ptrTask=ptrTask->Next;
		}

	return(RetTask);
}

/*	---------------------------------------------------------------------------
	Function:		void TSK_SetExecFilter(U32 Id,U32 Mask)

	Purpose:		Set a filter for tasks that are executed
					If ((Task.Id&Mask) == Id) then task is run

	Params:
					U32 =			Id;
					Mask =			Task class mask

	--------------------------------------------------------------------------- */
void TSK_SetExecFilter(U32 Id,U32 Mask)
{
	ExecId=Id;
	ExecMask=Mask;
}


/*	---------------------------------------------------------------------------
	Function:		void TSK_ClearExecFilter(void)

	Purpose:		Clears the exec filter, everything is run

	--------------------------------------------------------------------------- */
void TSK_ClearExecFilter(void)
{
	TSK_SetExecFilter(0,0);
}


/*	---------------------------------------------------------------------------
	Function:		int	TSK_KillTasks(TASK * CallingT,U32 Id,U32 Mask)

	Purpose:		Mass task killer. Whacks through task list looking for victims.
					If (T->fKillable && (Task.Id&Mask) == Id) then kill it.

	Params:			CallingT ->		Calling task
					U32 =			Id;
					Mask =			Task class mask

	Returns:		# of Tasks Killed
	--------------------------------------------------------------------------- */
int	TSK_KillTasks(TASK * CallingT,U32 Id,U32 Mask)
{
	int			TasksKilled;
	TASK	*	T;
	BOOL		WasCurrentTaskKilled;

	TasksKilled=0;
	WasCurrentTaskKilled=FALSE;
	T=ActiveTasks;

	while (T)
		{
		TASK	*	NextT;

		NextT=T->Next;

		if (T != CallingT)
			{
			if (T->fKillable && (T->Id&Mask)==Id)
				{
				if (T==CurrentTask)
					WasCurrentTaskKilled=TRUE;
				else
					LoTskKill(T);

				TasksKilled++;
				}
			}

		T=NextT;
		}

	/* If Current task was killed then we go on to next task */

	if (WasCurrentTaskKilled)
		{
		CurrentTask->fToDie=TRUE;
		ReturnToSchedulerIfCurrentTask(CurrentTask);
		}

	return(TasksKilled);
}	


/*	---------------------------------------------------------------------------
	Function:	void TSK_IterateTasks(U32 Id,U32 Mask,void (*CallBack)(TASK *T))
	Purpose:	Go through task list and do a callback for all tasks which match.
				If (Task->Id&Mask)==Id then callback function is invoked.
	Params:		Id = Task Id to match
				Mask = Task class mask
	--------------------------------------------------------------------------- */
void TSK_IterateTasks(U32 Id,U32 Mask,void (*CallBack)(TASK *T))
{
	TASK	*	T;

	T=ActiveTasks;

	while (T)
		{
		TASK	*	NextT;

		NextT=T->Next;

		if ((T->Id&Mask)==Id)
			CallBack(T);

		T=NextT;
		}
}


/*	---------------------------------------------------------------------------
	Function:	void TSK_MakeTaskInactive(TASK *T)
	Purpose:	Make a task temporarily inactive.
	Params:		T -> Task to knock out
	--------------------------------------------------------------------------- */
void	TSK_MakeTaskInactive(TASK *T)
{
	T->fActive=0;
}


/*	---------------------------------------------------------------------------
	Function:	void TSK_MakeTaskActive(TASK *T)
	Purpose:	Make a task active again.
	Params:		T -> Task to reactivate
	--------------------------------------------------------------------------- */
void	TSK_MakeTaskActive(TASK *T)
{
	T->fActive=1;
}


/*	---------------------------------------------------------------------------
	Function:	void TSK_MakeTaskImmortal(TASK *T)
	Purpose:	Make a task impervious to mass killings.
				Note that task can still be killed explicitly with TSK_Kill.
	Params:		T -> Task to protect
	--------------------------------------------------------------------------- */
void	TSK_MakeTaskImmortal(TASK *T)
{
	T->fKillable=0;
}


/*	---------------------------------------------------------------------------
	Function:	void TSK_MakeTaskMortal(TASK *T)
	Purpose:	Make a task vulnerable to mass killings.
	Params:		T -> Task to expose
	--------------------------------------------------------------------------- */
void	TSK_MakeTaskMortal(TASK *T)
{
	T->fKillable=1;
}


/*	---------------------------------------------------------------------------
	Function:	BOOL TSK_IsTaskActive(TASK *T)
	Purpose:	Check if a task is active
	Params:		T -> Task to eheck
	Returns:	0=Inactive, 1=Active
	--------------------------------------------------------------------------- */
BOOL TSK_IsTaskActive(TASK *T)
{
	return (T->fActive);
}


/*	---------------------------------------------------------------------------
	Function:	BOOL TSK_IsTaskMortal(TASK *T)
	Purpose:	Check if a task is easy to kill.
	Params:		T -> Task to check
	Returns:	0=Immortal, 1=Mortal
	--------------------------------------------------------------------------- */
BOOL	TSK_IsTaskMortal(TASK *T)
{
	return (T->fKillable);
}


/*	---------------------------------------------------------------------------
	Function:		void DetachFromList(TASK **Head,TASK *ThisObj)
	Purpose:		Take an object from an obj list
	Params:			Head		->	Head ptr of list
					ThisObj	->	Obj to add
	--------------------------------------------------------------------------- */
static void DetachFromList(TASK **Head,TASK *ThisObj)
{
	if (ThisObj->Prev)
		ThisObj->Prev->Next=ThisObj->Next;
	else
		*Head=ThisObj->Next;

	if (ThisObj->Next)
		ThisObj->Next->Prev=ThisObj->Prev;
}


/*	---------------------------------------------------------------------------
	Function:		void AddToList(TASK **Head,TASK *ThisObj)
	Purpose:		Add an object to a obj list
	Params:			Head		->	Head ptr of list
					ThisObj	->	Obj to add
	--------------------------------------------------------------------------- */
static void AddToList(TASK **Head,TASK *ThisObj)
{
	ThisObj->Prev=NULL;

	if ((ThisObj->Next=*Head))
		ThisObj->Next->Prev=ThisObj;

	*Head=ThisObj;
}

/*	---------------------------------------------------------------------------
	Function:		static void LoTskKill(TASK *T)
	Purpose:		Low level killing of task helper routine
	Params:			T -> Task to kill
	--------------------------------------------------------------------------- */
static void LoTskKill(TASK *T)
{
	BOOL	GalRet;

	/* Take out of list of active tasks */

	DetachFromList(&ActiveTasks,T);

	/* Dealloc the task block */
	GalRet=GAL_Free(T->hndTask);
	TasksActive--;

	ASSERT(GalRet);
}	


/*	---------------------------------------------------------------------------
	Function:		static void ExecuteTask(TASK *T)
	Purpose:		Low level task executor, protects from tasks that return
					without a TSK_Kill
	Params:			T -> Task to execute
	--------------------------------------------------------------------------- */
static void ExecuteTask(TASK *T)
{
	T->Main(T);
	DoEpi(T);
	T->fToDie=TRUE;
	ReturnToSchedulerIfCurrentTask(T);
}	


/*	---------------------------------------------------------------------------
	Epilogue / Prologue Function stuff
	--------------------------------------------------------------------------- */
DOTSK_CBACK TSK_SetDoTasksPrologue(DOTSK_CBACK Func)
{
	DOTSK_CBACK		Old;

	Old=DoTasksPrologue;
	DoTasksPrologue=Func;
	return(Old);
}

DOTSK_CBACK TSK_SetDoTasksEpilogue(DOTSK_CBACK Func)
{
	DOTSK_CBACK		Old;

	Old=DoTasksEpilogue;
	DoTasksEpilogue=Func;
	return(Old);
}


TSK_CBACK 	TSK_SetTaskPrologue(TSK_CBACK Pro)
{
	TSK_CBACK 	Old;

	Old=ProFunc;
	ProFunc=Pro;
	return(Old);
}

TSK_CBACK 	TSK_SetTaskEpilogue(TSK_CBACK Epi)
{
	TSK_CBACK 	Old;

	Old=EpiFunc;
	EpiFunc=Epi;
	return(Old);
}

void TSK_SetEpiProFilter(U32 Id,U32 Mask)
{
	EpiProId=Id;
	EpiProMask=Id;
}

void TSK_ClearEpiProFilter(void)
{
	TSK_SetEpiProFilter(1,0);
	TSK_SetTaskEpilogue(NULL);
	TSK_SetTaskPrologue(NULL);
}

/*	---------------------------------------------------------------------------
	Function:		void TSK_SetExtraStackProtection(BOOL OnOff)
	Purpose:		Say if we want the slow, memory heavy xtra stack protection
	--------------------------------------------------------------------------- */
void TSK_SetExtraStackProtection(BOOL OnOff)
{
	ExtraStackProtection=OnOff;
}

/*	---------------------------------------------------------------------------
	Function:		TSK_CBACK TSK_SetStackFloodCallback(TSK_CBACK Func);
	Purpose:		This gets called if stack is detected as broken
	--------------------------------------------------------------------------- */
TSK_CBACK TSK_SetStackFloodCallback(TSK_CBACK Func)
{
	TSK_CBACK	OldFunc;

	OldFunc=StackFloodCallback;

	StackFloodCallback=Func;
	return(OldFunc);
}

/*	---------------------------------------------------------------------------
	Function:		int	TSK_SetExtraStackSize(int Size)
	Purpose:		Set the xtra stack size for safety
	--------------------------------------------------------------------------- */
int	TSK_SetExtraStackSize(int Size)
{
	int	OldSize=ExtraStackSizeLongs*sizeof(u32);
	ExtraStackSizeLongs=Size/4;
	return(OldSize);
	
}

void ExtraMarkStack(u32 * Stack,int SizeLongs)
{
	int		f;
	for (f=0;f<SizeLongs;f++)
		Stack[f]=f;
}

int CheckExtraStack(u32 * Stack,int LongsToCheck)
{
	u32		f;

	for (f=0;f<(u32) LongsToCheck;f++)
		{
		if (Stack[f] != f)
			return(f);
		}
	return(f);
}

/* ---------------------------------------------------------------------------
	ends */