/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/********************************************************************/
 
#include <prtypes.h>
#include <prmem.h>
#include <prlog.h>
#include <prtime.h>
#include <prclist.h>
#include <limits.h>

#include "schedulr.h"

#define SCHEDULER_SLOP	1000000
/*
	If an event is scheduled to happen within this time frame (in milliseconds)
	we'll pull it off the queue rather than entering another wait loop.
*/

#define SCHED_RUNNING     (PR_BIT(0))
#define SCHED_PAUSED      (PR_BIT(1))
#define SCHED_FROZEN      (PR_BIT(2))
#define SCHED_TERMINATED  (PR_BIT(31))
/*
	Status bits for the scheduler
*/

typedef struct  _SchedulerData {
	PRUint32	flags;		
	PRUint32	nextID;
	PRUint32	nextObserver;
	PRTime		nextEventTime;
	PRThread	*pSchedThread;
	PRLock		*pTimerLock;
	PRCondVar	*pTimerCVar;
    PRCList		*pEventQueue;
	PRCList		*pObserverQueue;
	PRCList		eventQ;
} SchedulerData;

/*
	SchedulerPtr resolves internally to this data type, which holds the
	state for a particular scheduler.  Note that pEventQueue points to
	the eventQ struct which is embedded within the data, so pEventQueue
	must never be free'd (since it was never allocated)
*/

typedef struct	_SchedulerEvent {
    PRCList				*next;
    PRCList				*prev;
	PRUint32			eventID;
	SchedulerTime		eventTime;
	PRTime				fireTime;
	SchedFuncPtr		pEventProc;
	SchedFuncDataPtr	pEventData;
	char				*eventName;
} SchedEvent;

typedef struct _EventParameters {
	SchedulerPtr		pScheduler;
	PRUint32			eventID;
	SchedFuncPtr		pEventProc;
	SchedFuncDataPtr	pEventData;
} EventParams;
/*
	EventParams is an encapsulation of the data that's passed to the
	event function.  Because thread creation only takes a single
	parameter, EventParams is necessary to allow all of the data
	to go across.
*/


typedef struct	_SchedulerObserver {
    PRCList				*next;
    PRCList				*prev;
	PRUint32			observerID;
	SchedObsType		typeMask;
	SchedObsPtr			pObserverProc;
	SchedObsDataPtr		pObserverData;
} SchedObserver;

/* Forward Definitions */

void			DoEvent(EventParams *pParms);

void			SchedulerRun(SchedulerData *pData);
SchedulerErr	SchedulerInit(SchedulerData *pData);
void			SchedulerDestroy(SchedulerData *pData);

SchedEvent *	EventFromID(SchedulerData *pData, PRUint32 eventID);
void			InsertEvent(SchedulerData *pData, SchedEvent *pEvent);
void			RescheduleEvent(SchedulerData *pScheduler, SchedEvent *pEvent);

void			NotifyObservers(SchedulerData *pScheduler, SchedObsType type,
								PRUint32 eventID, char *szEventName,
								SchedFuncDataPtr pEventData);
SchedObserver*	ObserverFromID(SchedulerData *pData, PRUint32 observerID);


/* Implementation */

void DoEvent(EventParams *pParms)
{
	pParms->pEventProc(pParms->pScheduler, pParms->eventID, (void *) pParms->pEventData);
}

void SchedulerRun(SchedulerData	*pData)
{
	PRUint32			uSecDifference;
	PRTime				timeDifference;
	PRInt64				scratchVal;
	PRIntervalTime		timeout;
	SchedEvent			*pCurrentEvent;

	pData->flags |= SCHED_RUNNING;

	while (!(pData->flags & SCHED_TERMINATED)) {
		if (pData->flags & SCHED_RUNNING) {
			pCurrentEvent = NULL;

			/* Then, compute the next scheduler interval and wait */
			PR_Lock(pData->pTimerLock);

			if ((PR_CLIST_IS_EMPTY(pData->pEventQueue)) ||
				(LL_IS_ZERO(pData->nextEventTime))) {

				/* if there's nothing on the queue, the timeout is infinite */

				timeout = PR_INTERVAL_NO_TIMEOUT;
			} else {
				/* otherwise, it's the time between now and the next event */

				LL_SUB(timeDifference, pData->nextEventTime, PR_Now());

				LL_I2L(scratchVal, LONG_MAX);
				if (LL_CMP(timeDifference, <, scratchVal)) {

					if (!(LL_GE_ZERO(timeDifference))) {
						timeout = PR_INTERVAL_NO_WAIT;

					} else {
						LL_L2UI(uSecDifference, timeDifference);

						timeout = PR_MicrosecondsToInterval(uSecDifference);
					}
				} else {
					timeout = ULONG_MAX;
				}
			}

			PR_WaitCondVar(pData->pTimerCVar, timeout);

			/* if we've come out of the wait condition, it's because:

				1) we've scheduled a new event before the current one
				2) the current event was deleted
				3) it's time for the current event to fire
				4) the crawler is terminating
				5) the thread was spuriously interrupted

			   all of these conditions will get checked subsequent to
			   here, or the next time the loop is evaluated
			*/

			/* First, check that the stated next event time is accurate
			   (to check for addition/deletion cases) */

			if (PR_CLIST_IS_EMPTY(pData->pEventQueue)) {
				pData->nextEventTime = LL_ZERO;
			} else {
				pData->nextEventTime = ((SchedEvent *) PR_LIST_HEAD(pData->pEventQueue))->fireTime;

				/* Now, see if the nextEventTime is close enough to 
				   pop the event off of the queue and process it */

				LL_SUB(timeDifference, pData->nextEventTime,
					PR_Now());

				LL_I2L(scratchVal, LONG_MAX);
				if (LL_CMP(timeDifference, <, scratchVal)) {

					LL_L2UI(uSecDifference, timeDifference);

					if (!(LL_GE_ZERO(timeDifference)) || (uSecDifference < SCHEDULER_SLOP)) {
						/* yup, take the first item off the queue and use it. */
						pCurrentEvent = (SchedEvent *) PR_LIST_HEAD(pData->pEventQueue);

						PR_REMOVE_LINK(pCurrentEvent);
					}
				}
			}

			PR_Unlock(pData->pTimerLock);

			/* we didn't want to actually process the event while we were in the lock */

			if (pCurrentEvent != NULL) {
				if (pData->flags & SCHED_PAUSED) {
					/* we missed an event */
					/* not really, we only missed it if expires in this time frame */
					/* if we missed it, send out a missed notification */

				} else {
					/* process event */
					EventParams		parms;

					parms.pScheduler = pData;
					parms.pEventProc = pCurrentEvent->pEventProc;
					parms.pEventData = pCurrentEvent->pEventData;
					parms.eventID = pCurrentEvent->eventID;

					PR_CreateThread(PR_USER_THREAD,
						(void (*)(void *arg)) DoEvent,
						&parms,
						PR_PRIORITY_NORMAL,
						PR_LOCAL_THREAD,
						PR_UNJOINABLE_THREAD,
						0);

					/* notify observers */
					NotifyObservers(pData, SCHED_OBSERVE_FIRE, pCurrentEvent->eventID,
						pCurrentEvent->eventName, pCurrentEvent->pEventData);
				}

				/* if the event is a repeating event, reschedule it */
				if (pCurrentEvent->eventTime.repeating != 0) {
					RescheduleEvent(pData, pCurrentEvent);
				} else {
					if (pCurrentEvent->eventName != NULL) {
						PR_Free(pCurrentEvent->eventName);
					}

					PR_DELETE(pCurrentEvent);
				}
			}
		}
	}

	SchedulerDestroy(pData);
}

SchedulerErr SchedulerInit(SchedulerData *pData) {

	pData->nextID = 1;
	pData->nextObserver = 1000;
	pData->pTimerLock = PR_NewLock();
	pData->pTimerCVar = PR_NewCondVar(pData->pTimerLock);

	pData->pSchedThread = PR_CreateThread(PR_USER_THREAD,
			(void (*)(void *)) SchedulerRun,
			(void *) pData, 
			PR_PRIORITY_NORMAL,
			PR_LOCAL_THREAD,
			PR_JOINABLE_THREAD,
			0);

	pData->pEventQueue = &(pData->eventQ);
	PR_INIT_CLIST(pData->pEventQueue);

	return SCHED_ERR_NOERR;
}

void SchedulerDestroy(SchedulerData *pData) {
	SchedObserver *pHead, *pCurrent, *pOld;

	PR_ASSERT(pData != NULL);

	/* First, clear out the observer list */

	if (pData->pObserverQueue != NULL) {

		PR_Lock(pData->pTimerLock);

		if (!PR_CLIST_IS_EMPTY(pData->pObserverQueue)) {
			pHead = pCurrent = (SchedObserver *) PR_LIST_HEAD(pData->pObserverQueue);

			do {
				pOld = pCurrent;
				pCurrent = (SchedObserver *) PR_NEXT_LINK(pCurrent);

				PR_REMOVE_LINK(pOld);
				PR_DELETE(pOld);
			} while (!PR_CLIST_IS_EMPTY(pData->pObserverQueue));
		}

		PR_Free(pData->pObserverQueue);
		pData->pObserverQueue = NULL;

		PR_Unlock(pData->pTimerLock);
	}

	/*	FIXME! Need to remove all events*/


	/* Release the locks and condition variable */

	if (pData->pTimerCVar != NULL) {
		PR_DestroyCondVar(pData->pTimerCVar); 
		pData->pTimerCVar = NULL;
	}

	if (pData->pTimerLock != NULL) {
		PR_DestroyLock(pData->pTimerLock); 
		pData->pTimerLock = NULL;
	}

	/* Finally, delete the instance */

	PR_DELETE(pData);

	return;
}

/* EventFromID should always (and only) be called from inside a lock */

SchedEvent *
EventFromID(SchedulerData *pData, PRUint32 eventID)
{
	SchedEvent	*pReturn = NULL;
	SchedEvent	*pHead, *pCurrent;

	if ((pData != NULL) && (!PR_CLIST_IS_EMPTY(pData->pEventQueue))) {

		pHead = pCurrent = (SchedEvent *) PR_LIST_HEAD(pData->pEventQueue);

		do {
			if (pCurrent->eventID == eventID) {
				pReturn = pCurrent;
			}

			pCurrent = (SchedEvent *) PR_NEXT_LINK(pCurrent);
		} while ((pReturn == NULL) && (pCurrent != pHead));
	}

	return pReturn;
}

/* InsertEvent should always (and only) be called from inside a lock */

/* InsertEvent is really pretty inefficient.  It works by checking to
   see if the event is before the first one, and if so, inserts it there.
   Then, it checks to see if it's after the last one.  Otherwise, it
   walks forward from the first element using an insertion sort.
*/

void
InsertEvent(SchedulerData *pData, SchedEvent *pEvent)
{

	PR_ASSERT(pData != NULL);
	PR_ASSERT(pEvent != NULL);

	if (PR_CLIST_IS_EMPTY(pData->pEventQueue)) {
		PR_INSERT_LINK((PRCList *) pEvent, pData->pEventQueue);

		/* update the scheduler's nextEventTime */
		pData->nextEventTime = pEvent ->fireTime;
	} else {
		SchedEvent	*pCurrent;

		pCurrent = (SchedEvent *) PR_LIST_HEAD(pData->pEventQueue);

		if (LL_CMP(pEvent->fireTime, <, pCurrent->fireTime)) {
			PR_INSERT_LINK((PRCList *) pEvent, pData->pEventQueue);

		 	/* update the scheduler's nextEventTime */
			pData->nextEventTime = pEvent ->fireTime;
	
		} else if (!(LL_CMP(pEvent->fireTime, <, 
			((SchedEvent *) PR_LIST_TAIL(pData->pEventQueue))->fireTime))) {
			PR_APPEND_LINK((PRCList *) pEvent, pData->pEventQueue);
		} else {
			/* it's not the first, or the last item, so do the painful
			   insertion thang. */
			int		done = 0;

			while (!done) {
				pCurrent = (SchedEvent *) PR_NEXT_LINK(pCurrent);

				if (LL_CMP(pEvent->fireTime, <, pCurrent->fireTime)) {
					PR_INSERT_BEFORE((PRCList *) pEvent, (PRCList *) pCurrent);
					done = 1;
				}

			} /* while */
		} /* else */
	} /* else (not empty list */

	return;
}


void RescheduleEvent(SchedulerData *pScheduler, SchedEvent *pEvent) {

	/* compute the new firing time */

	/* rescheduled event times are computed by taking the event's scheduled time
	   and adding the appropriate number of seconds to the base.  Then, a randomized
	   value (plus or minus) is added, and the event is inserted into the queue.
	   This implies that examination of the event structure after the first firing
	   of an event will yield the adjusted base time, not the original base time.

	   If the newly scheduled event base time falls outside of the end time for the event,
	   the event is discarded and is not scheduled.  However, if the event's scheduled
	   time + its randomized value within range falls outside of the end time, the
	   event's firing time is pinned to the end time and still gets scheduled.
	 */

	PRTime				fireTime;
	PRInt32				random, randomSecs;
	PRInt64				randomizer, repeatValue;
	PRInt64				secs64, us2s;
	SchedulerTime		eventTime;

	LL_UI2L(us2s, PR_USEC_PER_SEC);
	LL_I2L(secs64, pEvent->eventTime.repeating);

	LL_MUL(repeatValue, secs64, us2s);
	LL_ADD(pEvent->eventTime.baseTime, pEvent->eventTime.baseTime, repeatValue);

	eventTime = pEvent->eventTime;

	if ((LL_NE(eventTime.end, LL_ZERO)) && (LL_CMP(eventTime.baseTime, >, eventTime.end))) {
		/* event is out of range; delete and move on */

		if (pEvent->eventName != NULL) {
			PR_Free(pEvent->eventName);
		}

		PR_DELETE(pEvent);
	} else {

		if (eventTime.range != 0) {

			random = (PRInt32) rand();
			randomSecs = random % eventTime.range;

			LL_I2L(secs64, randomSecs);
			LL_MUL(randomizer, secs64, us2s);

			if ((random % 2) == 0) {
				LL_NEG(randomizer, randomizer);
			}

			LL_ADD(fireTime, eventTime.baseTime, randomizer);
		} else {
			fireTime = eventTime.baseTime;
		}

		if ((LL_NE(LL_ZERO, eventTime.end)) && (LL_CMP(fireTime, >, eventTime.end))) {
			pEvent->fireTime = eventTime.end;
		} else if (LL_CMP(fireTime, <, eventTime.start)) {
			pEvent->fireTime = eventTime.start;
		} else {
			pEvent->fireTime = fireTime;
		}


		PR_Lock(pScheduler->pTimerLock);

		InsertEvent(pScheduler, pEvent);

		PR_NotifyCondVar(pScheduler->pTimerCVar);

		PR_Unlock(pScheduler->pTimerLock);
	}

}

void
NotifyObservers(SchedulerData *pScheduler, SchedObsType type,
				PRUint32 eventID, char *szEventName, SchedFuncDataPtr pEventData)
{

	SchedObserver	*pHead, *pCurrent;

	PR_ASSERT(pScheduler != NULL);

	if ((pScheduler->pObserverQueue != NULL) && (!PR_CLIST_IS_EMPTY(pScheduler->pObserverQueue))) {

		pHead = pCurrent = (SchedObserver *) PR_LIST_HEAD(pScheduler->pObserverQueue);

		do {
			if (pCurrent->typeMask & type) {
				pCurrent->pObserverProc(pScheduler, pCurrent->observerID, pCurrent->pObserverData,
										type, eventID, szEventName, pEventData);
			}

			pCurrent = (SchedObserver *) PR_NEXT_LINK(pCurrent);
		} while (pCurrent != pHead);
	}


	return;
}


SchedObserver *
ObserverFromID(SchedulerData *pData, PRUint32 observerID)
{
	SchedObserver *pReturn = NULL;
	SchedObserver *pHead, *pCurrent;

	PR_ASSERT(pData != NULL);

	if ((pData->pObserverQueue != NULL) && (!PR_CLIST_IS_EMPTY(pData->pObserverQueue))) {

		pHead = pCurrent = (SchedObserver *) PR_LIST_HEAD(pData->pObserverQueue);

		do {
			if (pCurrent->observerID == observerID) {
				pReturn = pCurrent;
			}

			pCurrent = (SchedObserver *) PR_NEXT_LINK(pCurrent);
		} while ((pReturn == NULL) && (pCurrent != pHead));
	}

	return pReturn;
}

/* Implementation of publically callable functions */

PR_IMPLEMENT(SchedulerPtr)
SchedulerStart(void)
{
	SchedulerData	*pNewScheduler = PR_NEWZAP(SchedulerData);

	if (pNewScheduler != NULL) {

		SchedulerInit(pNewScheduler);
	}

	return (SchedulerPtr) pNewScheduler;
}


PR_IMPLEMENT(SchedulerErr)
SchedulerStop(SchedulerPtr pArg)
{
	SchedulerData		*pData = (SchedulerData *) pArg;

	if (pData == NULL) {
		PR_ASSERT(pData != NULL);

		return SCHED_ERR_INVALID_SCHEDULER;
	}

	pData->flags |= SCHED_TERMINATED;
	
	PR_Lock(pData->pTimerLock);
	PR_NotifyCondVar(pData->pTimerCVar);
	PR_Unlock(pData->pTimerLock);

	return SCHED_ERR_NOERR;
}

PR_IMPLEMENT(SchedulerErr)
SchedulerPause(SchedulerPtr pArg)
{
	SchedulerData		*pData = (SchedulerData *) pArg;

	if (pData == NULL) {
		PR_ASSERT(pData != NULL);

		return SCHED_ERR_INVALID_SCHEDULER;
	}

	pData->flags |= SCHED_PAUSED;
	return SCHED_ERR_NOERR;
}

PR_IMPLEMENT(SchedulerErr)
SchedulerResume(SchedulerPtr pArg)
{
	SchedulerData		*pData = (SchedulerData *) pArg;

	if (pData == NULL) {
		PR_ASSERT(pData != NULL);

		return SCHED_ERR_INVALID_SCHEDULER;
	}

	pData->flags = (pData->flags & ~SCHED_PAUSED);
	return SCHED_ERR_NOERR;
}


PR_IMPLEMENT(SchedulerErr)
SchedulerAddEvent(SchedulerPtr pArg,
							   PRUint32 *pEventID,
							   const char *szName,
							   SchedulerTime *pTime,
							   SchedFuncPtr function,
							   SchedFuncDataPtr pData) 
{
	SchedulerData		*pScheduler = (SchedulerData *) pArg;
	SchedEvent			*pNewEvent;	
	SchedulerErr		err = SCHED_ERR_NOERR;

	if (pScheduler == NULL) {
		PR_ASSERT(pScheduler != NULL);

		return SCHED_ERR_INVALID_SCHEDULER;
	}

	if (function == NULL) {
		return SCHED_ERR_BAD_PARAMETER;
	}

	if ((pTime == NULL) || (pTime->range < 0) ||
		(LL_CMP(pTime->baseTime, <, pTime->start)) ||
		((!LL_IS_ZERO(pTime->end)) && (LL_CMP(pTime->baseTime, >, pTime->end)))) {
		return SCHED_ERR_BAD_TIME;
	}

	pNewEvent = PR_NEWZAP(SchedEvent);

	if (pNewEvent == NULL) {
		return SCHED_ERR_OUT_OF_MEMORY;
	}

	pNewEvent->eventTime = *pTime;
	pNewEvent->pEventProc = function;
	pNewEvent->pEventData = pData;

	if (szName != NULL) {
		pNewEvent->eventName = PR_Malloc(strlen(szName) + 1);
		strcpy(pNewEvent->eventName, szName);
	}

	PR_Lock(pScheduler->pTimerLock);

	pNewEvent->eventID = pScheduler->nextID++;
	
	if (pEventID != NULL) {
		*pEventID = pNewEvent->eventID;
	}

	/* compute the firing time */
	/* The first firing time is computed as follows: Select a random positive integer
	   within the range.  Multiply the number by 60 to obtain seconds,
	   then add that to the base time to get a time.  If the time is after the end time,
	   pin to the end time.  If it's before the start time, pin to the start time.*/
	{
		PRTime				fireTime;
		PRInt32				random, randomSecs;
		PRInt64				randomizer, us2s;

		if (pTime->range != 0) {
			random = (PRInt32) rand();
			randomSecs = random % pTime->range;

			LL_UI2L(us2s, PR_USEC_PER_SEC);
			LL_I2L(randomizer, randomSecs);
			LL_MUL(randomizer, randomizer, us2s);

			if ((random % 2) == 0) {
				LL_NEG(randomizer, randomizer);
			}

			LL_ADD(fireTime, pTime->baseTime, randomizer);

		} else {
			fireTime = pTime->baseTime;
		}

		if ((LL_NE(LL_ZERO, pTime->end)) && (LL_CMP(fireTime, >, pTime->end))) {
			pNewEvent->fireTime = pTime->end;
		} else if (LL_CMP(fireTime, <, pTime->start)) {
			pNewEvent->fireTime = pTime->start;
		} else {
			pNewEvent->fireTime = fireTime;
		}
	}

	/* insert the item in the proper place in the list */

	InsertEvent(pScheduler, pNewEvent);

	PR_NotifyCondVar(pScheduler->pTimerCVar);

	PR_Unlock(pScheduler->pTimerLock);

	/* notify observers */
	NotifyObservers(pScheduler, SCHED_OBSERVE_ADD, pNewEvent->eventID, pNewEvent->eventName, pData);

	return err;
}

PR_IMPLEMENT(SchedulerErr)
SchedulerRemoveEvent(SchedulerPtr pArg, PRUint32 eventID)
{
	SchedulerData	*pScheduler = (SchedulerData *) pArg;
	SchedEvent		*pEvent;
	SchedulerErr	err = SCHED_ERR_BAD_EVENT;

	if (pScheduler == NULL) {
		PR_ASSERT(pScheduler != NULL);

		return SCHED_ERR_INVALID_SCHEDULER;
	}

	PR_Lock(pScheduler->pTimerLock);

	pEvent = EventFromID(pScheduler, eventID);

	if (pEvent != NULL) {

		PR_REMOVE_LINK(pEvent);

		if (LL_EQ(pScheduler->nextEventTime, pEvent->fireTime)) {

			/* the item we just removed was the next item to fire,
			   so reinitialize the firing time */

			if (PR_CLIST_IS_EMPTY(pScheduler->pEventQueue)) {
				pScheduler->nextEventTime = LL_ZERO;
			} else {
				SchedEvent		*pNextEvent = (SchedEvent *) PR_LIST_HEAD(pScheduler->pEventQueue);

				pScheduler->nextEventTime = pNextEvent->fireTime;
			}

			PR_NotifyCondVar(pScheduler->pTimerCVar);
		}
		
		err = SCHED_ERR_NOERR; 
	}

	PR_Unlock(pScheduler->pTimerLock);

	if (pEvent != NULL) {

		/* notify observers */
		NotifyObservers(pScheduler, SCHED_OBSERVE_REMOVE, pEvent->eventID, pEvent->eventName, pEvent->pEventData);

		/* free the event */
		if (pEvent->eventName != NULL) {
			PR_Free(pEvent->eventName);
		}

		PR_DELETE(pEvent);
	}


	return err;
}

PR_EXTERN(SchedulerErr)
SchedulerAddObserver(SchedulerPtr pArg,
					 PRUint32 *pObserverID,
					 SchedObsType typeMask,
					 SchedObsPtr function,
					 SchedObsDataPtr pData)
{
	SchedulerData	*pScheduler = (SchedulerData *) pArg;
	SchedObserver	*pNewObserver = NULL;

	if (pScheduler == NULL) {
		PR_ASSERT(pScheduler != NULL);

		return SCHED_ERR_INVALID_SCHEDULER;
	}

	if (function == NULL) {
		return SCHED_ERR_BAD_PARAMETER;
	}

	pNewObserver = PR_NEWZAP(SchedObserver);

	if (pNewObserver == NULL) {
		return SCHED_ERR_OUT_OF_MEMORY;
	}

	PR_Lock(pScheduler->pTimerLock);

	if (pScheduler->pObserverQueue == NULL) {
		pScheduler->pObserverQueue = PR_Malloc(sizeof(PRCList));

		if (pScheduler->pObserverQueue == NULL) {
			if (pNewObserver != NULL) {
				PR_Free(pNewObserver);
			}

			PR_Unlock(pScheduler->pTimerLock);

			return SCHED_ERR_OUT_OF_MEMORY;
		} else {
			PR_INIT_CLIST(pScheduler->pObserverQueue);
		}
	}

	pNewObserver->observerID = pScheduler->nextObserver++;
	
	if (pObserverID != NULL) {
		*pObserverID = pNewObserver->observerID;
	}

	pNewObserver->typeMask = typeMask;
	pNewObserver->pObserverProc = function;
	pNewObserver->pObserverData = pData;

	PR_APPEND_LINK((PRCList *) pNewObserver, pScheduler->pObserverQueue);

	PR_Unlock(pScheduler->pTimerLock);

	return SCHED_ERR_NOERR;
}


PR_IMPLEMENT(SchedulerErr)
SchedulerRemoveObserver(SchedulerPtr pArg, PRUint32 observerID)
{
	SchedulerData	*pScheduler = (SchedulerData *) pArg;
	SchedObserver	*pObserver;
	SchedulerErr	err = SCHED_ERR_BAD_PARAMETER;

	if (pScheduler == NULL) {
		PR_ASSERT(pScheduler != NULL);

		return SCHED_ERR_INVALID_SCHEDULER;
	}

	PR_Lock(pScheduler->pTimerLock);

	pObserver = ObserverFromID(pScheduler, observerID);

	if (pObserver != NULL) {
		PR_REMOVE_LINK(pObserver);
		PR_DELETE(pObserver);

		err = SCHED_ERR_NOERR; 
	}

	PR_Unlock(pScheduler->pTimerLock);

	return err;
}


#ifdef DEBUG

PR_IMPLEMENT(void)
Sched_FormatEventTime(char *output, int len, SchedulerTime *pTime)
{
	PRExplodedTime	exp_time;
	char			scratch[100];
	char			returnValue[1024];

	PR_ExplodeTime(pTime->baseTime, PR_LocalTimeParameters, &exp_time);
	PR_FormatTime(scratch, 100, "%X on %x", &exp_time);
	sprintf(returnValue, "%s (+/- %ld seconds)", scratch, pTime->range);

	if (pTime->repeating !=0) {
		sprintf(scratch, "repeating every %d seconds,\n", pTime->repeating);
	} else {
		strcpy(scratch, "(nonrepeating),\n");
	}

	strcat(returnValue, scratch);

	PR_ExplodeTime(pTime->start, PR_LocalTimeParameters, &exp_time);
	PR_FormatTime(scratch, 100, "beginning at %X on %x\n", &exp_time);
	strcat(returnValue, scratch);

	if (LL_IS_ZERO(pTime->end)) {
		strcpy(scratch, "and continuing until cancelled");
	} else {
		PR_ExplodeTime(pTime->end, PR_LocalTimeParameters, &exp_time);
		PR_FormatTime(scratch, 100, "and stopping at %X on %x\n", &exp_time);
	}

	strcat(returnValue, scratch);

	strncpy(output, returnValue, len);
}

#endif

