/*
**	Accountant.c
**
**	Connection cost accounting
**
**	Copyright © 1990-1996 by Olaf `Olsen' Barthel
**		All Rights Reserved
**
**	:ts=4
*/

#ifndef _GLOBAL_H
#include "Global.h"
#endif

/****************************************************************************/

enum	{ STATE_Idle, STATE_Waiting };

/****************************************************************************/

enum	{ ACMSG_Start,ACMSG_Stop,ACMSG_Query };

typedef struct AccountantMsg
{
	struct Message	Message;

	UWORD			Type;
	struct timeval	Time;
} AccountantMsg;

/****************************************************************************/

STATIC struct MsgPort			*AccountantPort;
STATIC struct Task				*AccountantTask;

STATIC struct SignalSemaphore	 AccountantSemaphore;
STATIC ULONG					 AccountantCost;

/****************************************************************************/

	/* AccountantEntry(VOID):
	 *
	 *	The rates accounting task.
	 */

STATIC VOID SAVE_DS
AccountantEntry(VOID)
{
	struct MsgPort *TimePort;

		/* Set up the timer resources */

	if(TimePort = CreateMsgPort())
	{
		struct timerequest *TimeRequest;

		if(TimeRequest = (struct timerequest *)CreateIORequest(TimePort,sizeof(struct timerequest)))
		{
			if(!OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)TimeRequest,NULL))
			{
					/* Now create the communications port */

				if(AccountantPort = CreateMsgPort())
				{
					struct timeval StopTime;
					struct timeval Now;
					ULONG Signals;
					WORD State;

						/* That's me */

					AccountantTask = FindTask(NULL);

						/* Wake up the main program */

					Signal((struct Task *)ThisProcess,SIG_HANDSHAKE);

						/* Doing nothing yet... */

					State = STATE_Idle;

					FOREVER
					{
							/* Wait for a sign */

						Signals = Wait(PORTMASK(TimePort) | PORTMASK(AccountantPort) | SIG_KILL);

							/* Terminate this task? */

						if(Signals & SIG_KILL)
							break;

							/* The timer has elapsed? */

						if((Signals & PORTMASK(TimePort)) && State == STATE_Waiting)
						{
							PhoneEntry *ChosenEntry;
							struct List *ChosenPattern;
							ULONG Cost;

								/* Terminate the timer request */

							WaitIO((struct IORequest *)TimeRequest);

								/* We are no longer waiting */

							State = STATE_Idle;

								/* Start with a clean slate */

							Cost = 0;

								/* We want to read the accounting data */

							ChosenPattern = LockActivePattern();
							ChosenEntry = LockActiveEntry(GlobalPhoneHandle);

							if(ChosenEntry || ChosenPattern)
							{
									/* Look up the current time */

								GetSysTime(&Now);

									/* Check which rate data is current */

								SelectTime(ChosenEntry,ChosenPattern,&Now);

									/* Store the cost for the next unit */

								Cost = PayPerUnit[DT_NEXT_UNIT];

									/* Does this unit last for a certain time? */

								if(SecPerUnit[DT_NEXT_UNIT] > 0)
								{
									struct timeval TimeVal;

										/* This is the time it takes for the */
										/* unit to elapse */

									TimeVal.tv_secs		= SecPerUnit[DT_NEXT_UNIT] / 10000;
									TimeVal.tv_micro	= (SecPerUnit[DT_NEXT_UNIT] % 10000) * 100;

										/* Remember when the unit will have elapsed */

									GetSysTime(&StopTime);
									AddTime(&StopTime,&TimeVal);

										/* Start waiting again */

									TimeRequest->tr_node.io_Command	= TR_ADDREQUEST;
									TimeRequest->tr_time			= TimeVal;

									SendIO((struct IORequest *)TimeRequest);

									State = STATE_Waiting;
								}
							}

							UnlockActiveEntry(GlobalPhoneHandle);
							UnlockActivePattern();

								/* Add up the connection cost */

							ObtainSemaphore(&AccountantSemaphore);
							AccountantCost += Cost;
							ReleaseSemaphore(&AccountantSemaphore);
						}

							/* Somebody wants us to do something... */

						if(Signals & PORTMASK(AccountantPort))
						{
							AccountantMsg *Message;

								/* For each message... */

							while(Message = (AccountantMsg *)GetMsg(AccountantPort))
							{
								switch(Message->Type)
								{
										/* Start waiting? */

									case ACMSG_Start:

											/* Remember the time when this unit */
											/* will have elapsed */

										GetSysTime(&StopTime);
										AddTime(&StopTime,&Message->Time);

											/* Start waiting */

										TimeRequest->tr_node.io_Command	= TR_ADDREQUEST;
										TimeRequest->tr_time			= Message->Time;

										SendIO((struct IORequest *)TimeRequest);

										State = STATE_Waiting;

										break;

										/* Stop waiting? */

									case ACMSG_Stop:

											/* Are we currently waiting? */

										if(State == STATE_Waiting)
										{
											if(!CheckIO((struct IORequest *)TimeRequest))
												AbortIO((struct IORequest *)TimeRequest);

											WaitIO((struct IORequest *)TimeRequest);

											State = STATE_Idle;
										}

										break;

										/* Return the time that still has to elapse? */

									case ACMSG_Query:

										if(State == STATE_Waiting)
										{
												/* This is now */

											GetSysTime(&Now);

												/* Is there still time left */
												/* before the unit elapses? */

											if(CmpTime(&StopTime,&Now) < 0)	/* i.e. "StopTime > Now" */
											{
													/* Return the remaining time */

												Message->Time = StopTime;

												SubTime(&Message->Time,&Now);

												break;
											}
										}

											/* Clear the time value */

										memset(&Message->Time,0,sizeof(Message->Time));

										break;
								}

									/* Return the message */

								ReplyMsg((struct Message *)Message);
							}
						}
					}

						/* Are we still waiting? */

					if(State == STATE_Waiting)
					{
						if(!CheckIO((struct IORequest *)TimeRequest))
							AbortIO((struct IORequest *)TimeRequest);

						WaitIO((struct IORequest *)TimeRequest);
					}

						/* Start cleaning up... */

					DeleteMsgPort(AccountantPort);
				}

				CloseDevice((struct IORequest *)TimeRequest);
			}

			DeleteIORequest((struct IORequest *)TimeRequest);
		}

		DeleteMsgPort(TimePort);
	}

		/* Finished; wave goodbye and exit */

	Forbid();

	AccountantTask = NULL;
	Signal((struct Task *)ThisProcess,SIG_HANDSHAKE);
}

/****************************************************************************/

	/* DeleteAccountant():
	 *
	 *	Stop the accounting task.
	 */

VOID
DeleteAccountant()
{
	ShakeHands(AccountantTask,SIG_KILL);
}

	/* CreateAccountant():
	 *
	 *	Launch the accounting task.
	 */

BOOL
CreateAccountant()
{
	InitSemaphore(&AccountantSemaphore);

	Forbid();

	if(LocalCreateTask("term Accountant Task",ThisProcess->pr_Task.tc_Node.ln_Pri + 10,(TASKENTRY)AccountantEntry,4096,0))
		WaitForHandshake();

	Permit();

	return((BOOL)(AccountantTask != NULL));
}

/****************************************************************************/

	/* QueryAccountantTime(struct timeval *Time):
	 *
	 *	Query the time that will have to elapse before
	 *	the next unit will begin.
	 */

ULONG
QueryAccountantTime(struct timeval *Time)
{
	AccountantMsg Message;

	memset(&Message,0,sizeof(Message));

	Message.Message.mn_Length	= sizeof(Message);
	Message.Type				= ACMSG_Query;

	SendMessageGetReply(AccountantPort,(struct Message *)&Message);

	if(Time)
	{
		Time->tv_secs	= Message.Time.tv_secs;
		Time->tv_micro	= Message.Time.tv_micro;
	}

	return(Message.Time.tv_secs);
}

	/* QueryAccountantCost():
	 *
	 *	Query the cost of the current connection.
	 */

ULONG
QueryAccountantCost()
{
	ULONG Cost;

	SafeObtainSemaphoreShared(&AccountantSemaphore);
	Cost = AccountantCost;
	ReleaseSemaphore(&AccountantSemaphore);

	return(Cost);
}

	/* StopAccountant():
	 *
	 *	Stop cost accounting and return the current
	 *	connection cost.
	 */

ULONG
StopAccountant()
{
	AccountantMsg Message;
	ULONG Cost;

	memset(&Message,0,sizeof(Message));

	Message.Message.mn_Length	= sizeof(Message);
	Message.Type				= ACMSG_Stop;

	SendMessageGetReply(AccountantPort,(struct Message *)&Message);

	SafeObtainSemaphoreShared(&AccountantSemaphore);
	Cost = AccountantCost;
	ReleaseSemaphore(&AccountantSemaphore);

	return(Cost);
}

	/* StartAccountant(ULONG OnlineSeconds):
	 *
	 *	Start cost accounting for the current connection.
	 */

VOID
StartAccountant(ULONG OnlineSeconds)
{
	PhoneEntry *ChosenEntry;
	struct List *ChosenPattern;
	struct timeval Remain;
	struct timeval Now;
	ULONG Cost;

	ChosenPattern = LockActivePattern();
	ChosenEntry = LockActiveEntry(GlobalPhoneHandle);

		/* Forget the old data */

	StopAccountant();

		/* Now is the time for all good men... */

	GetSysTime(&Now);

		/* Did the connection happen a few seconds earlier? */

	if(OnlineSeconds > 0)
	{
		struct timeval Lead;

			/* Take care of the leading time */

		Lead.tv_secs	= OnlineSeconds;
		Lead.tv_micro	= 0;

			/* Technically, the connection happened a bit earlier. */

		SubTime(&Now,&Lead);

			/* Choose the right rate accounting time. */

		SelectTime(ChosenEntry,ChosenPattern,&Now);

			/* Enter the first rate. */

		Cost = PayPerUnit[DT_FIRST_UNIT];

		Remain.tv_secs	= SecPerUnit[DT_FIRST_UNIT] / 10000;
		Remain.tv_micro	= (SecPerUnit[DT_FIRST_UNIT] % 10000) * 100;

			/* Did the first unit elapse already? */

		while(-CmpTime(&Lead,&Remain) >= 0 && (Remain.tv_secs || Remain.tv_micro))	/* "Lead >= Remain" */
		{
				/* Subtract the unit time */

			SubTime(&Lead,&Remain);

				/* Update the "current" time so SelectTime */
				/* can do something sensible. */

			AddTime(&Now,&Remain);

			SelectTime(ChosenEntry,ChosenPattern,&Now);

			Remain.tv_secs	= SecPerUnit[DT_NEXT_UNIT] / 10000;
			Remain.tv_micro	= (SecPerUnit[DT_NEXT_UNIT] % 10000) * 100;

				/* In any case, a new unit starts here */

			Cost += PayPerUnit[DT_NEXT_UNIT];
		}

			/* Adjust the remaining time it takes for the unit */
			/* to elapse. */

		SubTime(&Remain,&Lead);
	}
	else
	{
			/* Choose the right rate accounting time. */

		SelectTime(ChosenEntry,ChosenPattern,&Now);

			/* Enter the first rate. */

		Cost = PayPerUnit[DT_FIRST_UNIT];

		Remain.tv_secs	= SecPerUnit[DT_FIRST_UNIT] / 10000;
		Remain.tv_micro	= (SecPerUnit[DT_FIRST_UNIT] % 10000) * 100;
	}

		/* Store the cost */

	ObtainSemaphore(&AccountantSemaphore);
	AccountantCost = Cost;
	ReleaseSemaphore(&AccountantSemaphore);

		/* That's it; "Remain" now holds the time to wait before */
		/* the first unit elapses. Careful here, we must not */
		/* launch a `zero' time wait request. */

	if(Remain.tv_secs > 0 || Remain.tv_micro > 0)
	{
		AccountantMsg Message;

		memset(&Message,0,sizeof(Message));

		Message.Message.mn_Length	= sizeof(Message);
		Message.Type				= ACMSG_Start;
		Message.Time				= Remain;

		SendMessageGetReply(AccountantPort,(struct Message *)&Message);
	}

	UnlockActiveEntry(GlobalPhoneHandle);
	UnlockActivePattern();
}
