;/* Execute me to Compile and link ShellTimerDaemon.c
shelltimer start verbose
; For debugging, use the following command:
; lc -. -O -ms -b1 -cfistq -j73 -v -Lit -d3 -isrc:memlib -dMWDEBUG=1 ShellTimerDaemon.c
; For production, use the following command:
lc -. -O -ms -b1 -cfistq -j73 -Lit ShellTimerDaemon.c
echo "Time to compile and link: " NOLINE
shelltimer stop
quit
*/

/***
****	SHELLTIMERDAEMON.C
****
****	Creation:		John Lindwall
****					19 Jan 1992
****
****	Description:	The timer server for the ShellTimer system.
****		This daemon accepts requests from the client program, ShellTimer.
****		It maintains a dynamic list of timer sessions.
****
****		When the client	requests that a timer session be started, the
****		daemon records the shell PID of the client and the current time
****		in an ST_Node data structure.  Later client requests can query
****		the value of the timer, cancel the timer session, and stop the
****		the timer.  The daemon can also be commanded to terminate.
****		Elapsed timer values are computed by the daemon and sent back to
****		client.
****
****		The AmigaDOS Break command can also be used to terminate the
****		daemon, by sending it a Control-C.
****
****		As written, this program requires AmigaDOS 2.04.  It could
****		be modified to run under 1.3, but I don't have the inclination.
****		2.0 features used include the AllocVec() call (love it!).
****
****		See ShellTimer.doc for more information.
****
****		The ShellTimer system (which includes the programs
****		ShellTimerDaemon and ShellTimer) is released into the Public
****		Domain by the author, John Lindwall.  I ask that the archive
****		be kept intact with the docs and the source code.  Bug reports
****		should be sent to me at johnl@crash.cts.com.
****
****	Note:	The code is not commented ... sorry.
****
****	Overhauls:
***/


#include <exec/types.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <exec/libraries.h>
#include <dos/dos.h>
#include <devices/timer.h>

#include <proto/exec.h>
#include <proto/timer.h>
#include <proto/dos.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "ShellTimer.h"

#define APPNAME			"ShellTimerDaemon"
#define ADOS2_VERSION	37

void CleanExit(char *, int);
void SetupTimerDevice(void);
void SetupMessagePort(void);
void ProcessMessages(void);
void StartTimer(struct ST_Message *msg);
void StopTimer(struct ST_Message *msg);
void QueryTimer(struct ST_Message *msg);
void CancelTimer(struct ST_Message *msg);
void SetupRequestList(void);
void DestroyRequestList(void);
BOOL InsertRequest(int requestID, struct timeval *startTime);
struct ST_Node *RemoveRequest(int requestID);
BOOL InitMsgEndDiffTime(struct ST_Message *msg, struct ST_Node *request,
	struct timeval *currentTime);
void ToggleTimer(struct ST_Message *msg);
void Print(char *s);

struct ST_Node
{
	struct Node node;
	int requestID;
	struct timeval startTime;
};

struct List requestList;

struct Library *TimerBase;
struct timerequest *TimerIO;
struct timeval *time;
struct MsgPort *publicPort;

UBYTE versionTag[] = "\0$VER: " APPNAME " 1.0 (03.02.92) by John Lindwall\n";

/* Disable SAS/C Control-C/D checking */
int CXBRK(void) { return(0); }
int chkabort(void) { return(0); }

void
main(int argc, char *argv[])
{
	extern struct DOSLibrary *DOSBase;

	if( DOSBase->dl_lib.lib_Version < ADOS2_VERSION )
	{
		CleanExit("AmigaDOS 2.0 required!\n", -20);
	}
	Forbid();
	if( FindPort(ST_PORT) )
	{
		Permit();
		CleanExit("Daemon already loaded!\n", -5);
	}
	Permit();
	if( argc != 0 )
	{
		Print(&versionTag[7]);
	}
#ifdef MWDEBUG
	MWInit(NULL, 0, "CON:0/0/639/199/MemLib");
#endif
	SetupTimerDevice();
	SetupRequestList();
	SetupMessagePort();
	ProcessMessages();
	CleanExit("Exiting.\n", 0);
}

void
SetupRequestList(void)
{
	NewList(&requestList);
}

void
DestroyRequestList(void)
{
	struct ST_Node *node;

	while( (node = (struct ST_Node *) RemHead(&requestList)) != NULL )
	{
		FreeVec(node);
	}
}

BOOL
InsertRequest(int requestID, struct timeval *startTime)
{
	struct ST_Node *node;

	if( (node = AllocVec(sizeof(struct ST_Node), MEMF_ANY)) == NULL )
	{
		return(FALSE);
	}
	node->requestID = requestID;
	node->startTime.tv_secs = startTime->tv_secs;
	node->startTime.tv_micro = startTime->tv_micro;
	Enqueue(&requestList, (struct Node *)node);
	return(TRUE);
}

struct ST_Node *
RemoveRequest(int requestID)
{
	struct ST_Node *foundNode;
	BOOL match;
	static struct ST_Node copyOfFoundNode;

	if( IsListEmpty(&requestList) )
	{
		return(NULL);
	}
	match = FALSE;
	foundNode = (struct ST_Node *) requestList.lh_Head;

	do
	{
		if( requestID == foundNode->requestID )
		{
			match = TRUE;
			break;
		}
		foundNode = (struct ST_Node *) foundNode->node.ln_Succ;
	}
	while( ! match && foundNode->node.ln_Succ != NULL );
	if( match )
	{
		Remove((struct Node *)foundNode);
		CopyMem(foundNode, &copyOfFoundNode, sizeof(struct ST_Node));
		FreeVec(foundNode);
		return(&copyOfFoundNode);
	}
	else
	{
		return(NULL);
	}
}


void
ProcessMessages(void)
{
	ULONG sigRecvd;
	BOOL done = FALSE;
	struct ST_Message *msg;

	while( ! done )
	{
		sigRecvd = Wait(SIGBREAKF_CTRL_C | 1L << publicPort->mp_SigBit);
		if( sigRecvd & SIGBREAKF_CTRL_C )
		{
			done = TRUE;
		}
		else
		{
			msg = (struct ST_Message *) GetMsg(publicPort);
#ifdef DEBUG
			printf("STD: I got a message! : %ld\n", msg->code); fflush(stdout);
#endif
			switch( msg->code )
			{
				case ST_START:
					StartTimer(msg);
					break;
				case ST_STOP:
					StopTimer(msg);
					break;
				case ST_CANCEL:
					CancelTimer(msg);
					break;
				case ST_TOGGLE:
					ToggleTimer(msg);
					break;
				case ST_QUERY:
					QueryTimer(msg);
					break;
				case ST_QUIT:
					done = TRUE;
					break;
			}
			ReplyMsg((struct Message *)msg);
		}
	}
}

void
StartTimer(struct ST_Message *msg)
{
	struct timeval currentTime;

	GetSysTime(&currentTime);
	RemoveRequest(msg->requestID);
	if( InsertRequest(msg->requestID, &currentTime) )
	{
		msg->startTime.tv_secs  = currentTime.tv_secs;
		msg->startTime.tv_micro = currentTime.tv_micro;
	}
	else
	{
		msg->code = ST_ERR;
	}
}

void
StopTimer(struct ST_Message *msg)
{
	struct ST_Node *request;
	struct timeval currentTime;

	GetSysTime(&currentTime);
	if( (request = RemoveRequest(msg->requestID)) != NULL )
	{
		if( ! InitMsgEndDiffTime(msg, request, &currentTime) )
		{
			msg->code = ST_TIMEWARP;
		}
	}
	else
	{
		msg->code = ST_ERR_NO_PENDING;
	}
}

void
QueryTimer(struct ST_Message *msg)
{
	struct ST_Node *request;
	struct timeval currentTime, trueStartTime;

	GetSysTime(&currentTime);
	if( (request = RemoveRequest(msg->requestID)) != NULL )
	{
		trueStartTime.tv_secs = request->startTime.tv_secs;
		trueStartTime.tv_micro = request->startTime.tv_micro;
		if( ! InitMsgEndDiffTime(msg, request, &currentTime) )
		{
			msg->code = ST_TIMEWARP;
			return;
		}
	}
	else
	{
		msg->code = ST_ERR_NO_PENDING;
		return;
	}
	if( ! InsertRequest(msg->requestID, &trueStartTime) )
	{
		msg->code = ST_ERR;
	}
}

BOOL
InitMsgEndDiffTime(struct ST_Message *msg, struct ST_Node *request,
	struct timeval *currentTime)
{
	if( CmpTime(currentTime, &(request->startTime)) != -1 )
	{
		return(FALSE);
	}
	msg->endTime.tv_secs =  currentTime->tv_secs;
	msg->endTime.tv_micro = currentTime->tv_micro;
	SubTime(currentTime, &(request->startTime));
	msg->diffTime.tv_secs =  currentTime->tv_secs;
	msg->diffTime.tv_micro = currentTime->tv_micro;
	return(TRUE);
}


void
CancelTimer(struct ST_Message *msg)
{
	struct ST_Node *request;

	if( (request = RemoveRequest(msg->requestID)) == NULL )
	{
		msg->code = ST_ERR;
	}
}

void
ToggleTimer(struct ST_Message *msg)
{
	struct ST_Node *request;
	struct timeval currentTime;

	GetSysTime(&currentTime);
	if( (request = RemoveRequest(msg->requestID)) != NULL )
	{
		if( ! InitMsgEndDiffTime(msg, request, &currentTime) )
		{
			msg->code = ST_TIMEWARP;
		}
		else
		{
			msg->code = ST_STOP;
		}
	}
	else
	{
		if( InsertRequest(msg->requestID, &currentTime) )
		{
			msg->startTime.tv_secs  = currentTime.tv_secs;
			msg->startTime.tv_micro = currentTime.tv_micro;
			msg->code = ST_START;
		}
		else
		{
			msg->code = ST_ERR;
		}
	}
}

void
SetupMessagePort(void)
{
	if( (publicPort = CreatePort(ST_PORT, 0)) == NULL )
	{
		CleanExit("Can't open public message port\n", -3);
	}
}

void
SetupTimerDevice(void)
{
	LONG error;

	TimerIO = (struct timerequest *)
		AllocVec(sizeof(struct timerequest), MEMF_PUBLIC | MEMF_CLEAR);
	time = (struct timeval *)
		AllocVec(sizeof(struct timeval), MEMF_PUBLIC | MEMF_CLEAR);
	if( TimerIO	== NULL || time == NULL )
	{
		CleanExit("Out of memory\n", -1);
	}
	if( error = OpenDevice(TIMERNAME, UNIT_MICROHZ,
		(struct IORequest *) TimerIO, 0L) )
	{
		CleanExit("Can't open timer device\n", -2);
	}
	TimerBase = (struct Library *) TimerIO->tr_node.io_Device;
}

void
CleanExit(char *string, int returnCode)
{
	struct ST_Message *msg;

	if( string != NULL )
	{
		Print(APPNAME ": ");
		Print(string);
	}

	DestroyRequestList();
	if( publicPort )
	{
		while( msg = (struct ST_Message *) GetMsg(publicPort) )
		{
			msg->code = ST_ERR;
			ReplyMsg((struct Message *)msg);
		}
		DeletePort(publicPort);
	}
	if( TimerBase )
	{
		CloseDevice((struct IORequest *) TimerIO);
	}
	if( TimerIO )
	{
		FreeVec(TimerIO);
	}
	if( time )
	{
		FreeVec(time);
	}
#ifdef MWDEBUG
	MWTerm();
#endif
	exit(returnCode);
}

void
Print(char *string)
{
	Write(Output(), string, strlen(string));
}
