/*
**	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 __saveds __asm
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,TimeRequest,NULL))
			{
				struct Device *TimerBase;

				TimerBase = TimeRequest->tr_node.io_Device;

					// 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(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)
						{
							ULONG Cost;

								// Terminate the timer request

							WaitIO(TimeRequest);

								// We are no longer waiting

							State = STATE_Idle;

								// Start with a clean slate

							Cost = 0;

								// We want to read the accounting data

							ObtainSemaphoreShared(&PatternSemaphore);

							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(TimeRequest);

									State = STATE_Waiting;
								}
							}

							ReleaseSemaphore(&PatternSemaphore);

								// 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(TimeRequest);

										State = STATE_Waiting;

										break;

										// Stop waiting?

									case ACMSG_Stop:

											// Are we currently waiting?

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

											WaitIO(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(Message);
							}
						}
					}

						// Are we still waiting?

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

						WaitIO(TimeRequest);
					}

						// Start cleaning up...

					DeleteMsgPort(AccountantPort);
				}

				CloseDevice(TimeRequest);
			}

			DeleteIORequest(TimeRequest);
		}

		DeleteMsgPort(TimePort);
	}

		// Finished; wave goodbye and exit

	Forbid();

	AccountantTask = NULL;
	Signal(ThisProcess,SIG_HANDSHAKE);
}

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

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

VOID
DeleteAccountant()
{
	if(AccountantTask)
	{
		Forbid();

		SetSignal(0,SIG_HANDSHAKE);
		Signal(AccountantTask,SIG_KILL);

		Permit();
	}
}

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

BOOL
CreateAccountant()
{
	InitSemaphore(&AccountantSemaphore);

	Forbid();

	if(CreateTask("term Accountant Task",MIN(127,ThisProcess->pr_Task.tc_Node.ln_Pri + 10),AccountantEntry,4096))
	{
		SetSignal(0,SIG_HANDSHAKE);

		Wait(SIG_HANDSHAKE);
	}

	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,&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;

	ObtainSemaphoreShared(&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,&Message);

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

	return(Cost);
}

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

VOID
StartAccountant(ULONG OnlineSeconds)
{
	struct timeval Remain;
	struct timeval Now;
	ULONG Cost;

		// 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)	// i.e. "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,&Message);
	}
}
