/****************************************************************************
*
* $RCSfile: Controller.c $
* $Revision: 1.9 $
* $Date: 1997/09/13 11:24:32 $
* $Author: ssolie $
*
*****************************************************************************
*
* Copyright (c) 1997 Software Evolution.  All Rights Reserved.
*
*****************************************************************************
*
* Controller.c -- Controller module
*
* This file contains the source for the Controller object.
*/

#include <exec/memory.h>
#include <libraries/commodities.h>

#include <proto/commodities.h>
#include <proto/exec.h>
#include <proto/intuition.h>

#include "Debug.h"
#include "Controller.h"
#include "LocaleManager.h"
#include "Timer.h"
#include "Docket.h"
#include "DocketEditor.h"


/*** Global variables ***/
IMPORT LocaleManager		locale_mgr;
IMPORT struct ExecBase		*SysBase;
IMPORT struct Library		*CxBase;
IMPORT struct IntuitionBase	*IntuitionBase;


/*** Local constants ***/
#define POPKEY_PRESSED				1	/* popkey pressed message */

const STRPTR	CX_NAME				= "Docket";
const STRPTR	CX_TITLE			= "Docket 2.0";
const ULONG		TIMER_GRANULARITY	= 1;	/* seconds */


/*** Local data types ***/
struct ControllerClass {
	struct MsgPort *broker_port;	/* broker message port */
	CxObj *broker;					/* commodities broker */
	ULONG signal_set;				/* signal set to wait on */
	ULONG t0;						/* start time (seconds) */
	ULONG t1;						/* end time (seconds) */
	BOOL done;						/* done flag */
	BOOL disabled;					/* disabled flag */
	BOOL show;						/* show docket editor flag */

	Timer timer;					/* timer object */
	Docket docket;					/* docket object */
	DocketEditor editor;			/* docket editor object */
	ULONG editor_sig;				/* docket editor signal */
};


/*** Local function prototypes ***/
STATIC VOID handle_broker_signal(Controller this);
STATIC VOID show_docket_editor(Controller this);
STATIC VOID hide_docket_editor(Controller this);


/*
 * newController -- Create Controller object
 *
 * This function is used to create a new Controller object.  The controller
 * will be in an inactive state and ready for use.  Returns a reference
 * to the Controller or NULL on error.
 */
Controller newController(BYTE cx_pri, BOOL cx_popup, STRPTR cx_popkey)
{
	STATIC struct NewBroker new_broker;
	Controller this;
	CxObj *filter, *sender;

	D(bug("newController(%ld, %lu, %s)\n", cx_pri, cx_popup, cx_popkey));

	this = AllocVec(sizeof(struct ControllerClass), MEMF_CLEAR);
	if ( this == NULL )
		return(NULL);

	/*** Setup the broker ***/
	this->broker_port = CreateMsgPort();
	if ( this->broker_port == NULL )  {
		deleteController(this);
		return(NULL);
	}

	new_broker.nb_Version		  = NB_VERSION;
	new_broker.nb_Name			  = CX_NAME;
	new_broker.nb_Title			  = CX_TITLE;
	new_broker.nb_Descr			  =
		getString(locale_mgr, MSG_CX_DESCRIPTION, MSG_CX_DESCRIPTION_STR);
	new_broker.nb_Unique		  = NBU_UNIQUE | NBU_NOTIFY;
	new_broker.nb_Flags			  = COF_SHOW_HIDE;
	new_broker.nb_Pri			  = cx_pri;
	new_broker.nb_Port			  = this->broker_port;
	new_broker.nb_ReservedChannel = NULL;

	this->broker = CxBroker(&new_broker, NULL);
	if ( this->broker == NULL )  {
		deleteController(this);
		return(NULL);
	}

	/*** Optional broker popkey handling ***/
	filter = CxFilter(cx_popkey);
	if ( filter != NULL )  {
		AttachCxObj(this->broker, filter);

		sender = CxSender(this->broker_port, POPKEY_PRESSED);
		if ( sender != NULL )
			AttachCxObj(filter, sender);
	}

	/*** Setup remaining objects ***/
	this->timer = newTimer();
	this->docket = newDocket();
	if ( this->timer == NULL || this->docket == NULL )  {
		deleteController(this);
		return(NULL);
	}

	/*** Try to load docket from a file ***/
	loadDocket(this->docket, DEF_DOCKET_FILE_NAME);

	/*** State information ***/
	ActivateCxObj(this->broker, TRUE);
	this->done = FALSE;
	this->disabled = TRUE;
	this->show = cx_popup;

	return(this);
}


/*
 * deleteController -- Delete Controller object
 *
 * This function is used to delete a Controller object.  All the controller
 * resources will be freed.
 */
VOID deleteController(Controller this)
{
	CxMsg *cx_msg;

	D(bug("deleteController(%lx)\n", this));

	if ( this == NULL )
		return;

	deleteDocketEditor(this->editor);
	deleteDocket(this->docket);
	deleteTimer(this->timer);

	/*** Delete the broker ***/
	if ( this->broker != NULL )  {
		ActivateCxObj(this->broker, FALSE);
		DeleteCxObjAll(this->broker);
	}

	if ( this->broker_port != NULL )  {
		while ( (cx_msg = (CxMsg*)GetMsg(this->broker_port)) != NULL )
			ReplyMsg((struct Message*)cx_msg);
			DeleteMsgPort(this->broker_port);
	}

	FreeVec(this);
}


/*
 * handleController -- Handle Controller object
 *
 * This function is used to handle a Controller object.  The controller
 * will handle message dispatch and return when the user has chosen to
 * quit.  Returns TRUE if successful or FALSE on error.
 */
BOOL handleController(Controller this)
{
	DocketItem item;
	ULONG break_sig, broker_sig, timer_sig;
	ULONG signals, seconds, micros;
	LONG result;

	D(bug("handleController(%lx)\n", this));

	if ( this == NULL )
		return(FALSE);

	/*** Setup the task signalling ***/
	break_sig  = SIGBREAKF_CTRL_C;
	broker_sig = (ULONG)1 << this->broker_port->mp_SigBit;
	timer_sig  = getTimerSignal(this->timer);

	this->signal_set = break_sig | broker_sig | timer_sig;

	/*** Enable docket items and the controller ***/
	CurrentTime(&seconds, &micros);
	item = firstDocketItem(this->docket);
	while ( item != NULL )  {
		setDocketItemExpiration(item, seconds);
		item = nextDocketItem(this->docket);
	}

	enableController(this);

	/*** Show the docket editor if the user wanted to ***/
	if ( this->show == TRUE )
		show_docket_editor(this);

	/*** Loop until the user wants to quit ***/
	this->done = FALSE;
	while ( !this->done )  {
		signals = Wait(this->signal_set);

		if ( signals & break_sig )
			quitController(this);

		if ( (signals & broker_sig) && !this->done )
			handle_broker_signal(this);

		if ( (signals & timer_sig) && !this->done )  {
			handleTimerSignal(this->timer);
			if ( !this->disabled )  {
				CurrentTime(&this->t1, &micros);
				(VOID)executeDocketItems(this->docket, this->t0, this->t1);
				this->t0 = this->t1;
				startTimer(this->timer, TIMER_GRANULARITY);
			}
		}

		if ( (signals & this->editor_sig) && !this->done )  {
			result = handleDocketEditorSignal(this->editor);
			switch ( result )  {
				case DOCKETEDITOR_OK:
					break;
				case DOCKETEDITOR_HIDE:
					hide_docket_editor(this);
					break;
				case DOCKETEDITOR_QUIT:
					quitController(this);
					break;
				default:
					D2(bug(" docket editor error=%ld\n", result));
			}
		}
	}

	disableController(this);

	return(TRUE);
}


/*
 * enableController -- Enable Controller object
 *
 * This function is used to enable a Controller object.  The controller
 * will be enabled and will execute docket items.
 */
VOID enableController(Controller this)
{
	ULONG micros;

	D(bug("enableController(%lx)\n", this));

	if ( this == NULL )
		return;

	if ( this->disabled )  {
		this->disabled = FALSE;
		CurrentTime(&this->t0, &micros);
		this->t1 = this->t0;
		startTimer(this->timer, TIMER_GRANULARITY);
	}
}


/*
 * disableController -- Disable Controller object
 *
 * This function is used to disable a Controller object.  The controller
 * will be disabled and will not execute docket items.
 */
VOID disableController(Controller this)
{
	ULONG micros;

	D(bug("disableController(%lx)\n", this));

	if ( this == NULL )
		return;

	if ( !this->disabled )  {
		stopTimer(this->timer);
		CurrentTime(&this->t1, &micros);
		this->t0 = this->t1;
		this->disabled = TRUE;
	}
}


/*
 * quitController -- Quit Controller
 *
 * This function tells the controller to quit processing user input.
 */
VOID quitController(Controller this)
{
	D(bug("quitController(%lx)\n", this));

	if ( this != NULL )
		this->done = TRUE;
}


/*
 * handle_broker_signal -- Handle broker signal
 *
 * This function handles a broker signal and processes any messages
 * waiting at the message port.
 */
STATIC VOID handle_broker_signal(Controller this)
{
	CxMsg *cx_msg;
	ULONG cx_msg_type;
	LONG cx_msg_id;

	if ( this == NULL )
		return;

	cx_msg = (CxMsg*)GetMsg(this->broker_port);
	while ( cx_msg != NULL && !this->done )  {
		cx_msg_id = CxMsgID(cx_msg);
		cx_msg_type = CxMsgType(cx_msg);
		ReplyMsg((struct Message*)cx_msg);

		switch ( cx_msg_type )  {
			case CXM_IEVENT:
				switch ( cx_msg_id)  {
					case POPKEY_PRESSED:
						show_docket_editor(this);
						break;
					default:
						D2(bug(" bad cx_msg_id=%ld\n", cx_msg_id));
				}
				break;
			case CXM_COMMAND:
				switch ( cx_msg_id )  {
					case CXCMD_DISABLE:
						disableController(this);
						break;
					case CXCMD_ENABLE:
						enableController(this);
						break;
					case CXCMD_APPEAR:
						show_docket_editor(this);
						break;
					case CXCMD_DISAPPEAR:
						hide_docket_editor(this);
						break;
					case CXCMD_KILL:
						quitController(this);
						break;
					case CXCMD_LIST_CHG:
						D(bug(" CXCMD_LIST_CHG\n"));
						break;
					case CXCMD_UNIQUE:
						show_docket_editor(this);
						break;
					default:
						D2(bug(" bad cx_msg_id=%ld\n", cx_msg_id));
				}
				break;
			default:
				D2(bug(" bad cx_msg_type=%ld\n", cx_msg_type));
		}

		cx_msg = (CxMsg*)GetMsg(this->broker_port);
	}
}


/*
 * show_docket_editor -- Show docket editor
 *
 * This private function will show the docket editor if it isn't showing.
 */
STATIC VOID show_docket_editor(Controller this)
{
	if ( this == NULL || this->editor != NULL )
		return;

	this->editor = newDocketEditor(this->docket);
	this->editor_sig = getDocketEditorSignal(this->editor);

	this->signal_set |= this->editor_sig;
}


/*
 * hide_docket_editor -- Hide docket editor
 *
 * This private function will hide the docket editor if it showing.
 */
STATIC VOID hide_docket_editor(Controller this)
{
	if ( this == NULL || this->editor == NULL )
		return;

	this->signal_set &= ~(this->editor_sig);

	deleteDocketEditor(this->editor);
	this->editor_sig = NULL;
	this->editor = NULL;
}
