#include <ascii.h>
#include "fido.h"
#include "fidomem.h"
#include "proto.h"

/* Scheduler for Fido, etc. Various scheduling and support routines.
These use the MSDOS time and date, and assumes a continuous seven
day schedule. Resolution is one minute.

til_sched(tag,m,dsp)
		Returns the index of the soonest event within M
		minutes; -1 of none within the window, or -2 if
		no such event exists.

		* Tags A - W are FidoNet events.
		* Tag X is ERRORLEVEL.
		* Tag Y is P)age.
		* Tag Z is FileRequest.
		* Tag ! is Idle.

		* OPTional and CONTinuous events return as -1, until 
		til_sched is called during the event itself, ie. 0 minutes 
		til runtime. This prevents OPT events from preempting other
		events; they are, after all, optional.

		* Completed events return -1. 
		
		* TaskIDs must match; events with not-our task ID are 
		considered to not exist, ie. return as -2.

		* If dsp is true, each valid event encountered is displayed.

		* If ? is passed as the search tag, then any runnable
		event A - W is allowed, else the tag must match exactly.

		* If / is passed as the search tag, then only runnable
		non-OPT or CONT events as in ? are checked.

		* If -1 is passed as a time til event, then no action is
		actually taken; usually used when merely listing events.

til_event(e)	Return the number of minutes until the specified event
		should be running, or 0 if it is currently running (or should
		be). Returns -1 if error.

put_sched()	Writes the time table out to disk.

dsp_event(b,e)	Generate a report string on event E in buffer B.


markevt(n)	Mark event #N as complete.

clrevts()	Reenable all events.

clrrevts()	Reenable RUSH events.

clrcevts()	Reenable CONT events.


cont(e)		Returns true if the event is CONT. 

rush(e)		Returns true if the event is RUSH.


set_cont(e)	Mark event e as a CONT event.

set_rush(e)	Mark event e as a RUSH event.

*/

/* Find the soonest runnable event within M minutes, and return it's 
index, or < 0 if none. */

til_sched(tag,m,dsp)
char tag;
int m;
int dsp;
{
int n,i;
FLAG no_events;
unsigned next_time;
int next_event;
char buff[SS];
struct _sched *sc;

	next_time= MINS_WK;				/* oldest possible */
	next_event= -1;					/* none of them */
	no_events= 1;					/* none found yet */

	for (i= 0; i < SCHEDS; i++) {
		sc= &fido.sched[i];			/* keep it simple! */
		if (! sc-> tag) break;			/* NUL == end of table */
		n= til_event(i);			/* time til this event runs, */

/* Clear the COMPLETE bit if not within this event's window. */

		if (n > 0) sc-> bits &= ~SCHED_COMPLETE;

		if (dsp) {				/* if display wanted, */
			if (sc-> bits & SCHED_COMPLETE) cprintf(SM+144); /* "complete: */
			else if (! n) cprintf(SM+145);	/* "now" */
			else cprintf(SM+146,n);
			dsp_event(buff,i);		/* display it */
			cputs(buff);
		}
		if ((tag == '?') || (tag == '/')) {	/* if ? or / specified */
			if (sc-> tag == 'Y') continue;	/* any runnable */
			if (sc-> tag == 'Z') continue;	/* any runnable */

		} else if (sc-> tag != tag) continue;	/* else must be exact match */

/* If this is not for our task ID, then ignore it. */

		if (sc-> taskid && (sc-> taskid != taskid))
			continue;			/* only our task ID */

/* Flag that we at least found an event. */

		no_events= 0;				/* 0 == event found */

/* If there is a switch specified for this event, make sure it is on, otherwise
ignore this event. */

		if (sc-> swtch) {			/* if a switch specified, */
			if (! istoggle(sc-> swtch - 1)) 
				continue;		/* it must be on */
		}

/* If we are passed -1, we're only listing events; don't check any further 
for a runnable one. */

		if (m < 0) continue;			/* do nothing else */

/* If we are looking for hard events only, ignore all CONT and OPT events. */

		if ((tag == '/') && (sc-> bits & (SCHED_OPTIONAL | SCHED_CNT)))
			continue;

/* If the event is runnable NOW (ie. time til event is 0) check if it has
already been run. If so, ignore it. (ie. 60 min. ERRORLEVELs). If not, stop
looking; run it immediately. */

		if (n == 0) {				/* runnable NOW */
			if (sc-> bits & SCHED_COMPLETE) continue;
			next_event= i;			/* this is soonest */
			next_time= n;			/* so far, */
			break;				/* stop looking */

/* If this is the soonest event we found so far, remember it. We don't
remember CONT or OPT events until they are runnable NOW; they are, after
all, "optional", we don't want to shorten callers time limits etc. */

		} else if (n < next_time) {		/* soonest so far */
			if (sc-> bits & (SCHED_OPTIONAL | SCHED_CNT)) continue;
			next_event= i;			/* this is soonest */
			next_time= n;			/* so far, */
		}

/* Since this event is no longer current, clear it's completion bit. */

/*		sc-> bits &= ~SCHED_COMPLETE;	*/	/* clear it */
	}
	if (dsp && no_events) cprintf(SM+147);

/* If we found one within the desired time, return it; returns -1 if not
within the specified window, or -2 if there is no such event. */

	if (next_time <= m) return(next_event);		/* one we found, */
	else if (no_events) return(-2);			/* or no such event, */
	else return(-1);				/* or not within window */
}

/* Generate a report on event E. */

void dsp_event(b,e)
char *b;
int e;
{
char c;
char buff[SS];

/* 
complete: 
1234 Min: Cont Rush Opt *Errorlevel 255, Sun 23:59 1440 min, ID=1, Trig=7(Once)
*/
	*b= NUL;
	if (fido.sched[e].bits & SCHED_OPTIONAL) strcat(b,"Opt "); else strcat(b,"    ");
	if (fido.sched[e].bits & SCHED_CNT) strcat(b,"Cont "); 
	else if (fido.sched[e].bits & SCHED_RUSH) strcat(b,"Rush "); 
	else strcat(b,"     ");

	b += strlen(b);				/* point to the end */
	if (e == fido.event) strcat(b,"-");	/* is currently running */
	else strcat(b," ");
/*	else strcat(b,(e == fido.event ? "*" : " ")); */ /* or last run */

	b += strlen(b);
	switch (fido.sched[e].tag) {
		case 'Z': sprintf(buff,0,"FileRequest,  "); break;
		case 'Y': sprintf(buff,0,"Page,         "); break;
		case 'X': sprintf(buff,0,"ErrorLevel %d,",fido.sched[e].result); break;
		case '!': sprintf(buff,0,"Idle,"); break;
		default: sprintf(buff, 0,"FidoNet \"%c\",",fido.sched[e].tag); break;
	}
	while (strlen(buff) < sizeof("Errorlevel 255,")) strcat(buff," ");
	strcat(b,buff);				/* pad it out */

	b += strlen(b);
	sprintf(b,0," %s %02d:%02d %4d min",
	  dayname[fido.sched[e].daywk],		/* daywk */
	  fido.sched[e].hr,			/* hour, */
	  fido.sched[e].min,			/* min */
	  fido.sched[e].len);			/* how long */

	b += strlen(b);				/* show task ID */
	sprintf(b,0,(fido.sched[e].taskid ? ", ID=%d," : "      "),fido.sched[e].taskid);

	c= fido.sched[e].swtch;
	if (c) {				/* if a switch set, */
		b += strlen(b);
		sprintf(b,SM+148,c);		/* "T=%d" switch status */
		switch (istoggle(c - 1)) {
			case 1: strcat(b,string(SM+1)); break;	/* ON */
			case 0: strcat(b,string(SM+2)); break;	/* OFF */
			case -1: strcat(b,string(SM+3)); break;	/* ONCE */
		}
		strcat(b,")");
	}
	strcat(b,"\r\n");
}

/* Mark this event as completed */

void markevt(n)
unsigned n;
{
	if (n < SCHEDS) {
		fido.sched[n].bits |= SCHED_COMPLETE;
		n= fido.sched[n].swtch;			/* (save typing) */
		if (n--) {				/* if a switch set, */
			if (istoggle(n) < 0)		/* and its a oneshot */
				stoggle(n,0);		/* turn it off */
		}
	}
}

/* Clear all events to reenable them. */

void clrevts() {
int i;

	for (i= 0; i < SCHEDS; i++) {
		if (! fido.sched[i].tag) break;		/* end of table */
		clrevt(i);
	}
}

/* Clear RUSH events to reenable them. */

void clrrevts() {
int i;

	for (i= 0; i < SCHEDS; i++) {
		if (! fido.sched[i].tag) break;		/* end of table */
		if (fido.sched[i].bits & SCHED_RUSH)
			clrevt(i);
	}
}

/* Clear CONT events to reenable them. */

void clrcevts() {
int i;

	for (i= 0; i < SCHEDS; i++) {
		if (! fido.sched[i].tag) break;		/* end of table */
		if (fido.sched[i].bits & SCHED_CNT)
			clrevt(i);
	}
}

/* Reenable this event. */

void clrevt(e)
unsigned e;
{
	if (e < SCHEDS) fido.sched[e].bits &= ~SCHED_COMPLETE;
}

/* Return true if this is a CONTinuous event. */

int cont(e)
unsigned e;
{
	return(e > SCHEDS ? 0 : fido.sched[e].bits & SCHED_CNT);
}

/* Return true if this is a RUSH event. */

int rush(n)
unsigned n;
{
	if (n < SCHEDS) return(fido.sched[n].bits & SCHED_RUSH);
	return(0);
}

/* Set the CONT bit on this event. */

void set_cont(e)
int e;
{
	if (e < SCHEDS) fido.sched[e].bits |= SCHED_CNT;
	
}

/* Set the RUSH bit on this event. */

void set_rush(e)
int e;
{
	if (e < SCHEDS) fido.sched[e].bits |= SCHED_RUSH;
	
}

/* Return the number of minutes until this event can be run, or 0 if it
should be running now. */

int til_event(n)
int n;
{
char daywk,d,h,m;
int now,start,end;

	if (n >= SCHEDS) return(-1);		/* out of range */

	get_time(&d,&h,&m);			/* get current time, */
	now= lt(d,h,m);				/* make mins since Sun 00:00 */

	daywk= fido.sched[n].daywk;		/* convert event day "ALL" */
	if (daywk > 6) daywk= d;		/* into "today" */
	start= lt(daywk,fido.sched[n].hr,fido.sched[n].min); /* when it starts */

	end= start + fido.sched[n].len;		/* when it ends */

	if ((now >= start) && (now < end)) 	/* should be running now */
		return(0);			/* zero mins until start ... */

	start -= now;				/* just time until it starts */
	if (start < 0) start += MINS_WK;	/* modulo one week */
	if (fido.sched[n].daywk > 6) 		/* if day == ALL */
		start %= MINS_DAY;		/* 24 * 60 or less, no? */
	return(start);
}
/* Convert the time elements dayinweek, hour, minute to an integer minutes 
since Sun. 00:00. */

static lt(d,h,m)
char d,h,m;
{
	return((d * MINS_DAY) + (h * MINS_HR) + m);
}

/* Get the time of day, hours & minutes. Make sure we dont catch in changing. */

static get_time(d,h,m)
char *d,*h,*m;
{
char dt,ht,mt;
int n;

	for (n= 0; n < 10; n++) {
		*d= gtod3(3); *h= gtod3(4); *m= gtod3(5);
		dt= gtod3(3); ht= gtod3(4); mt= gtod3(5);
		if ((*d == dt) && (*h == ht) && (*m == mt)) return;
	}
	clprintf(SM+114);		/* clock keeps changing? */
}
