/****************************************************************************
*
* $RCSfile: Docket.c $
* $Revision: 2.0 $
* $Date: 1997/09/06 15:40:44 $
* $Author: ssolie $
*
*****************************************************************************
*
* Copyright (c) 1997 Software Evolution.  All Rights Reserved.
*
*****************************************************************************
*
* Docket.c -- Docket object source file
*
* This file contains all the code needed to use Docket objects.
*/

#include <exec/memory.h>
#include <libraries/iffparse.h>
#include <utility/tagitem.h>

#include <clib/alib_protos.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/iffparse.h>

#include <string.h>

#include "Debug.h"
#include "Docket.h"


/*** Global variables ***/
IMPORT struct ExecBase		*SysBase;
IMPORT struct DosLibrary	*DOSBase;
IMPORT struct Library		*IFFParseBase;


/*** Local macros ***/
#define IsMinListEmpty(x)	(((x)->mlh_TailPred) == (struct MinNode*)(x))


/*** Local defines ***/
#define ID_DCKT	MAKE_ID('D', 'C', 'K', 'T')		/* docket form */
#define ID_DCHD	MAKE_ID('D', 'C', 'H', 'D')		/* docket header */
#define ID_DCIT MAKE_ID('D', 'C', 'I', 'T')		/* docket item */


/*** Local constants ***/
const UWORD DCKT_VERSION = 1;		/* IFF file version */
const UWORD DEF_MONTH	 = JANUARY;
const UWORD DEF_MDAY	 = 1;
const UWORD DEF_WDAY	 = MONDAY;
const UWORD DEF_HOUR	 = 12;
const UWORD DEF_MINUTE	 = 0;
const UWORD DEF_SECOND	 = 0;
const STRPTR DEF_COMMAND = "C:RequestChoice Amiga Amiga! OK";


/*** Local data types ***/
struct DocketClass {
	struct MinList item_list;	/* list of items in docket */
	DocketItem current_item;	/* current docket item */
};

struct DCHD {
	UWORD version;				/* file version */
};

struct DCIT {
	UWORD type;					/* type of item */
	UWORD month;				/* month number */
	UWORD mday;					/* month day */
	UWORD wday;					/* week day */
	UWORD hr;					/* hour */
	UWORD min;					/* minute */
	UWORD sec;					/* second */
	/* Variable length command string goes here */
};


/*** Local function prototypes ***/
BOOL read_docket(Docket this, struct IFFHandle *handle);
BOOL write_docket(Docket this, struct IFFHandle *handle);


/*
 * newDocket -- Create Docket object
 *
 * This function is used to create a new Docket object.  Returns a reference
 * to the docket or NULL on error.
 */
Docket newDocket(VOID)
{
	Docket this;

	D(bug("newDocket()\n"));

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

	NewList((struct List*)&this->item_list);
	this->current_item = NULL;

	return(this);
}


/*
 * deleteDocket -- Delete Docket object
 *
 * This function is used to delete a Docket object.
 */
VOID deleteDocket(Docket this)
{
	struct Node *node;

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

	if ( this != NULL )  {
		while ( (node = RemHead((struct List*)&this->item_list)) != NULL )
			deleteDocketItem((DocketItem)node);

		FreeVec(this);
	}
}


/*
 * executeDocketItems -- Execute Docket items
 *
 * This function is used to execute a set of docket items within the given
 * time range.  If no items can be executed, this function does nothing.
 * Returns TRUE if successful or FALSE on error.
 */
BOOL executeDocketItems(Docket this, ULONG t0, ULONG t1)
{
	DocketItem item;
	struct MinNode *node;
	ULONG expiration;
	BOOL result;

	D(bug("executeDocketItems(%08lx, %lu, %lu)\n", this, t0, t1));

	if ( this == NULL || t1 <= t0 )
		return(FALSE);

	result = TRUE;
	node = this->item_list.mlh_Head;
	while ( node->mln_Succ != NULL )  {
		item = (DocketItem)node;
		node = node->mln_Succ;

		expiration = getDocketItemExpiration(item);
		if ( expiration >= t0 && expiration < t1 )  {
			result = executeDocketItemCommand(item);
			if ( result == TRUE )
				result = setDocketItemExpiration(item, t1);
		}
	}

	return(result);
}


/*
 * firstDocketItem -- Get first DocketItem
 *
 * This function gets the first docket item.  Returns a reference to a
 * DocketItem or NULL if the docket is empty.
 */
DocketItem firstDocketItem(Docket this)
{
	DocketItem item;

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

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

	if ( IsMinListEmpty(&this->item_list) )
		return(NULL);
	else  {
		item = (DocketItem)this->item_list.mlh_Head;
		this->current_item = item;
		return(item);
	}
}


/*
 * nextDocketItem -- Get next DocketItem
 *
 * This function gets the next docket item.  Always call firstDocketItem()
 * before calling this function.  Returns a reference to the next DocketItem
 * or NULL if there are no more items in the docket.
 */
DocketItem nextDocketItem(Docket this)
{
	DocketItem item;
	struct MinNode *node;

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

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

	if ( this->current_item == NULL )
		return(NULL);

	node = (struct MinNode*)this->current_item;
	if ( node == this->item_list.mlh_TailPred )
		return(NULL);
	else  {
		item = (DocketItem)node->mln_Succ;
		this->current_item = item;
		return(item);
	}
}


/*
 * addNewDocketItem -- Add new DocketItem
 *
 * This function is used to add a new item to the docket with default
 * values.  Returns a reference to the new DocketItem or NULL on error.
 */
DocketItem addNewDocketItem(Docket this)
{
	DocketItem item;

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

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

	item = newYearlyDocketItem(DEF_MONTH, DEF_MDAY, DEF_HOUR, DEF_MINUTE,
		DEF_SECOND, DEF_COMMAND);

	if ( item != NULL )
		AddTail((struct List*)&this->item_list, (struct Node*)item);

	return(item);
}


/*
 * removeDocketItem -- Remove DocketItem
 *
 * This function is used to remove an item from a docket.  The item will
 * be removed and deleted.
 */
VOID removeDocketItem(Docket this, DocketItem item)
{
	D(bug("removeDocketItem(%08lx, %08lx)\n", this, item));

	if ( this == NULL || item == NULL )
		return;

	Remove((struct Node*)item);
	deleteDocketItem(item);
}


/*
 * loadDocket -- Load docket from file
 *
 * Loads in a docket from a file.  The docket item list will not be cleared
 * to allow for appending lists together in the future.  Returns TRUE if
 * successful or FALSE on error.
 */
BOOL loadDocket(Docket this, STRPTR file_name)
{
	struct IFFHandle *handle;
	BOOL result;

	D(bug("loadDocket(%08lx, %s)\n", this, file_name));

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

	result = FALSE;
	handle = AllocIFF();
	if ( handle != NULL )  {
		handle->iff_Stream = (ULONG)Open(file_name, MODE_OLDFILE);
		if ( handle->iff_Stream != NULL )  {
			InitIFFasDOS(handle);
			if ( OpenIFF(handle, IFFF_READ) == 0 )  {
				result = read_docket(this, handle);
				CloseIFF(handle);
			}

			Close(handle->iff_Stream);
		}

		FreeIFF(handle);
	}

	return(result);
}


/*
 * saveDocket -- Save docket to file
 *
 * Saves a docket to a file.  If the docket item list is empty nothing
 * will be saved to the file.  Returns TRUE if successful or FALSE on error.
 */
BOOL saveDocket(Docket this, STRPTR file_name)
{
	struct IFFHandle *handle;
	BOOL result;

	D(bug("saveDocket(%08lx, %s)\n", this, file_name));

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

	if ( IsMinListEmpty(&this->item_list) )
		return(TRUE);

	result = FALSE;
	handle = AllocIFF();
	if ( handle != NULL )  {
		handle->iff_Stream = (ULONG)Open(file_name, MODE_NEWFILE);
		if ( handle->iff_Stream != NULL )  {
			InitIFFasDOS(handle);
			if ( OpenIFF(handle, IFFF_WRITE) == 0 )  {
				result = write_docket(this, handle);
				CloseIFF(handle);
			}

			Close(handle->iff_Stream);
		}

		FreeIFF(handle);
	}

	return(result);
}


/*
 * read_docket -- Read IFF docket file
 *
 * This private function reads an IFF docket file and adds each docket item
 * to the list.  Corrupted records are ignored.  Returns TRUE if successful
 * or FALSE on error.
 */
BOOL read_docket(Docket this, struct IFFHandle *handle)
{
	DocketItem item;
	struct DCHD *dchd;
	struct DCIT *dcit;
	struct StoredProperty *prop;
	struct CollectionItem *coll;
	STRPTR cmd;

	/*** Parse the IFF file (pretty easy!) ***/
	if ( PropChunk(handle, ID_DCKT, ID_DCHD) != 0 ||
		 CollectionChunk(handle, ID_DCKT, ID_DCIT) != 0 ||
		 StopOnExit(handle, ID_DCKT, ID_FORM) != 0 ||
		 ParseIFF(handle, IFFPARSE_SCAN) != IFFERR_EOC )
		return(FALSE);

	/*** Check the version of the file ***/
	prop = FindProp(handle, ID_DCKT, ID_DCHD);
	if ( prop == NULL )
		return(FALSE);

	dchd = prop->sp_Data;
	if ( dchd->version != DCKT_VERSION )
		return(FALSE);

	/*** Create the docket items and add them to the docket list ***/
	coll = FindCollection(handle, ID_DCKT, ID_DCIT);
	while ( coll != NULL )  {
		dcit = coll->ci_Data;
		cmd  = (STRPTR)dcit + sizeof(struct DCIT);
		coll = coll->ci_Next;

		switch ( dcit->type )  {
			case YEARLY:
				item = newYearlyDocketItem(dcit->month, dcit->mday, dcit->hr,
					dcit->min, dcit->sec, cmd);
				break;
			case MONTHLY:
				item = newMonthlyDocketItem(dcit->mday, dcit->hr, dcit->min,
					dcit->sec, cmd);
				break;
			case WEEKLY:
				item = newWeeklyDocketItem(dcit->wday, dcit->hr, dcit->min,
					dcit->sec, cmd);
				break;
			case DAILY:
				item = newDailyDocketItem(dcit->hr, dcit->min, dcit->sec, cmd);
				break;
			default:
				item = NULL;
		}

		if ( item != NULL )		/* note collections are reversed */
			AddHead((struct List*)&this->item_list, (struct Node*)item);
	}

	return(TRUE);
}


/*
 * write_docket -- Write IFF docket file
 *
 * This private function writes an IFF docket file and saves the entire
 * docket.  Returns TRUE if successful or FALSE on error.
 */
BOOL write_docket(Docket this, struct IFFHandle *handle)
{
	STATIC struct DCHD dchd;
	STATIC struct DCIT dcit;
	struct MinNode *node;
	DocketItem item;
	STRPTR command;
	LONG result, length;

	/*** Write the form header ***/
	if ( PushChunk(handle, ID_DCKT, ID_FORM, IFFSIZE_UNKNOWN) != 0 )
		return(FALSE);

	/*** Write the docket header ***/
	if ( PushChunk(handle, ID_DCKT, ID_DCHD, sizeof(struct DCHD)) != 0 )
		return(FALSE);

	dchd.version = DCKT_VERSION;
	result = WriteChunkBytes(handle, &dchd, sizeof(struct DCHD));
	if ( result != sizeof(struct DCHD) )
		return(FALSE);

	if ( PopChunk(handle) != 0 )
		return(FALSE);

	/*** Write the docket items ***/
	node = this->item_list.mlh_Head;
	while ( node->mln_Succ != NULL )  {
		item = (DocketItem)node;
		node = node->mln_Succ;

		if ( PushChunk(handle, ID_DCKT, ID_DCIT, IFFSIZE_UNKNOWN) != 0 )
			return(FALSE);

		dcit.type	= getDocketItemType(item);
		dcit.month	= getDocketItemMonth(item);
		dcit.mday	= getDocketItemMonthDay(item);
		dcit.wday	= getDocketItemWeekDay(item);
		dcit.hr		= getDocketItemHour(item);
		dcit.min	= getDocketItemMinute(item);
		dcit.sec	= getDocketItemSecond(item);
		result = WriteChunkBytes(handle, &dcit, sizeof(struct DCIT));
		if ( result != sizeof(struct DCIT) )
			return(FALSE);

		command = getDocketItemCommand(item);
		length = strlen(command) + 1;		/* include terminator */
		result = WriteChunkBytes(handle, command, length);
		if ( result != length )
			return(FALSE);

		if ( PopChunk(handle) != 0 )
			return(FALSE);
	}

	/*** Complete the form header ***/
	if ( PopChunk(handle) != 0 )
		return(FALSE);

	return(TRUE);
}
