/****************************************************************************\
 *									      *
 *			C R E A T O R    O F   L E G E N D S		      *
 *				(AberMud Version 5)			      *
 *									      *
 *  The Creator Of Legends System is (C) Copyright 1989 Alan Cox, All Rights  *
 *  Reserved.		  						      *
 *									      *
 \****************************************************************************/

/*
 *	Time Scheduling Control
 *
 *
 */

#include "System.h"

Module	"Time Scheduler";
Version "1.05";
Author  "Alan Cox";

/*
 *	1.00	Basic Scheduling
 *	1.01	Added Facilities To Retain Defined $ME For WHEN
 *	1.02	Fixed crash when $ME died during a WHEN
 *	1.03	Major changes to allow for removal of event after current event
 *		during the current events execution (phew!) - took a while to
 *		find him!
 *	1.04	Added a CountSchedules() function for profiling
 *	1.05	Strict ansification, cleaned up time_t
 */

struct TimeEvent	/* This is a private internal structure */
{
	unsigned long ti_Time;
	short ti_Table;
	ITEM *ti_Runner;
	struct TimeEvent *ti_Next;
};

typedef struct TimeEvent EVENT;

static EVENT *EventList=NULL;
static EVENT *CurrentEvent;

long CountSchedules()
{
	EVENT *c=EventList;
	long ct=0;
	while(c)
	{
		ct++;
		c=c->ti_Next;
	}
	return(ct);
}

void AddEvent(unsigned long tm, short table)
{
	time_t t;	/* How much software will crash when the sign 
				   bit flips ? */
	EVENT *New=Allocate(EVENT);
	EVENT *Walk=EventList;
	EVENT *Prev=NULL;
	time(&t);
	New->ti_Time=t+tm;
	New->ti_Table=table;
	New->ti_Runner=Me();
	LockItem(Me());
/*
 *	Decide Where To Add Entry
 */
	while(Walk)
	{
		if(Walk->ti_Time>=New->ti_Time)
		{
			if(Prev)
			{
				Prev->ti_Next=New;
				New->ti_Next=Walk;
				return;
			}
			else
			{
				New->ti_Next=EventList;
				EventList=New;
				return;
			}
		}
		Prev=Walk;
		Walk=Walk->ti_Next;
	}
	if(Prev)
	{
		Prev->ti_Next=New;
		New->ti_Next=NULL;
	}
	else
	{
		EventList=New;
		New->ti_Next=NULL;
	}
}

static void DeleteEvent(EVENT *Ev)
{
/*
 * The system takes care to avoid deleting the current event twice when the
 * runner of that event occurs. We delete it the FIRST time only, so that 
 * we don't leave the dying person locked.
 */
	if(Ev==CurrentEvent)
		CurrentEvent=NULL;	/* The current event has been eaten */
	UnlockItem(Ev->ti_Runner);
	if(EventList==Ev)
	{
		EventList=Ev->ti_Next;
		free((char *)Ev);
	}
	else
	{
		EVENT *Step=EventList;
		if(Step==NULL)
			Error("DeleteEvent: No Events!");
		while(Step->ti_Next)
		{
			if(Step->ti_Next==Ev)
			{
				Step->ti_Next=Ev->ti_Next;
				free((char *)Ev);
				return;
			}
			Step=Step->ti_Next;
		}
		Error("DeleteEvent: Bad Event Pointer");
	}
}

static void RunEvent(EVENT *event)
{
/*	We keep this little routine seperate in case we need to add multiple
 *	event classes.	
 */

	TABLE *tab;
	Verb=0;		/* 0 is timeout verb code in system */

	tab=FindTable(event->ti_Table);
	if(tab)
		ExecBackground(tab,event->ti_Runner);
}

void Scheduler(void)
{
/*	We have to be careful here, since the event queue may get changed
 *	AS we wander down it. Due to the way this routine currently works
 *	if you keep requeueing items too fast the queue may keep the 
 *	scheduler running forever without returning. For the MUD system 
 *	this is unlikely to cause problems.
 */
	time_t t;
	EVENT *Walk=EventList;
	time(&t);
	while(EventList)
	{
		if(EventList->ti_Time>t)
			break;	/* Finished -> Since queue is time ordered */
		Walk=EventList;		/* Next event is always top */
		CurrentEvent=Walk;	/* CurrentEvent manages dying in WHEN */
		RunEvent(Walk);		/* Do the event */
		if(CurrentEvent)
			DeleteEvent(Walk);
	}
}

void KillEventQueue(ITEM *i)
{
	EVENT *e=EventList,*ne;
	while(e)
	{
		ne=e->ti_Next;
		if(e->ti_Runner==i)
			DeleteEvent(e);
		e=ne;
	}
}

void WipeEventQueue(void)
{
	EVENT *e=EventList,*ne;
	while(e)
	{
		ne=e->ti_Next;
		DeleteEvent(e);
		e=ne;
	}
}