/****************************************************************************
*
* $RCSfile: DocketItem.c $
* $Revision: 1.1 $
* $Date: 1997/08/12 15:32:08 $
* $Author: ssolie $
*
*****************************************************************************
*
* Copyright (c) 1997 Software Evolution.  All Rights Reserved.
*
*****************************************************************************
*
* DocketItem.c -- DocketItem object source file
*
* This file contains the code required to use DocketItem objects.
*
* Assumptions:
*	1. Gregorian calender.
*	2. Weeks starts on a Sunday.
*	3. The minimums and maximums defined in the header file apply.
*/

#include <dos/dostags.h>
#include <exec/memory.h>
#include <utility/tagitem.h>

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

#include <string.h>

#include "DocketItem.h"
#include "Debug.h"


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


/*** Local data types ***/
struct DocketItemClass {
	struct MinNode node;	/* embedded list node */
	UWORD type;				/* type of item */
	UWORD month;			/* month */
	UWORD mday;				/* month day */
	UWORD wday;				/* week day */
	UWORD hour;				/* hour */
	UWORD min;				/* minute */
	UWORD sec;				/* second */
	TEXT command[MAX_CMD_LEN];	/* command to execute */
	ULONG expiration;		/* when to execute the command (seconds) */
};


/*** Local function prototypes ***/
STATIC UWORD max_mday(UWORD month);
STATIC UWORD adjust_mday(UWORD year, UWORD month, UWORD mday);


/*
 * newYearlyDocketItem -- Create yearly DocketItem
 *
 * This function creates a new yearly docket item ready for use.  Returns
 * a reference to the new DocketItem if successful or NULL on error.
 */
DocketItem newYearlyDocketItem(UWORD month, UWORD mday, UWORD hour,
	UWORD min, UWORD sec, STRPTR cmd)
{
	DocketItem this;

	D(bug("newYearlyDocketItem(%lu, %lu, %lu, %lu, %lu, %s)\n",
		month, mday, hour, min, sec, cmd));

	if ( month < MIN_MONTH || month > MAX_MONTH ||
		 mday < MIN_MDAY || mday > max_mday(month) ||
		 hour < MIN_HOUR || hour > MAX_HOUR ||
		 min < MIN_MINUTE || min > MAX_MINUTE ||
		 sec < MIN_SECOND || sec > MAX_SECOND ||
		 cmd == NULL )
		return(NULL);

	if ( strlen(cmd) >= MAX_CMD_LEN )
		return(NULL);

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

	this->type		 = YEARLY;
	this->month		 = month;
	this->mday		 = mday;
	this->hour		 = hour;
	this->min		 = min;
	this->sec		 = sec;
	this->expiration = 0;

	strcpy(this->command, cmd);

	return(this);
}


/*
 * newMonthlyDocketItem -- Create monthly DocketItem
 *
 * This function creates a new monthly docket item ready for use.  Returns
 * a reference to the new DocketItem if successful or NULL on error.
 */
DocketItem newMonthlyDocketItem(UWORD mday, UWORD hour, UWORD min, UWORD sec,
	STRPTR cmd)
{
	DocketItem this;

	D(bug("newMonthlyDocketItem(%lu, %lu, %lu, %lu, %s)\n",
		mday, hour, min, sec, cmd));

	if ( mday < MIN_MDAY || mday > MAX_MDAY ||
		 hour < MIN_HOUR || hour > MAX_HOUR ||
		 min < MIN_MINUTE || min > MAX_MINUTE ||
		 sec < MIN_SECOND || sec > MAX_SECOND ||
		 cmd == NULL )
		return(NULL);

	if ( strlen(cmd) >= MAX_CMD_LEN )
		return(NULL);

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

	this->type		 = MONTHLY;
	this->mday		 = mday;
	this->hour		 = hour;
	this->min		 = min;
	this->sec		 = sec;
	this->expiration = 0;

	strcpy(this->command, cmd);

	return(this);
}


/*
 * newWeeklyDocketItem -- Create weekly DocketItem
 *
 * This function creates a new weekly docket item ready for use.  Returns
 * a reference to the new DocketItem if successful or NULL on error.
 */
DocketItem newWeeklyDocketItem(UWORD wday, UWORD hour, UWORD min, UWORD sec,
	STRPTR cmd)
{
	DocketItem this;

	D(bug("newWeeklyDocketItem(%lu, %lu, %lu, %lu, %s)\n",
		wday, hour, min, sec, cmd));

	if ( wday < MIN_WDAY || wday > MAX_WDAY ||
		 hour < MIN_HOUR || hour > MAX_HOUR ||
		 min < MIN_MINUTE || min > MAX_MINUTE ||
		 sec < MIN_SECOND || sec > MAX_SECOND ||
		 cmd == NULL )
		return(NULL);

	if ( strlen(cmd) >= MAX_CMD_LEN )
		return(NULL);

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

	this->type		 = WEEKLY;
	this->wday		 = wday;
	this->hour		 = hour;
	this->min		 = min;
	this->sec		 = sec;
	this->expiration = 0;

	strcpy(this->command, cmd);

	return(this);
}


/*
 * newDailyDocketItem -- Create daily DocketItem
 *
 * This function creates a new daily docket item ready for use.  Returns
 * a reference to the new DocketItem if successful or NULL on error.
 */
DocketItem newDailyDocketItem(UWORD hour, UWORD min, UWORD sec, STRPTR cmd)
{
	DocketItem this;

	D(bug("newDailyDocketItem(%lu, %lu, %lu, %s)\n", hour, min, sec, cmd));

	if ( hour < MIN_HOUR || hour > MAX_HOUR ||
		 min < MIN_MINUTE || min > MAX_MINUTE ||
		 sec < MIN_SECOND || sec > MAX_SECOND ||
		 cmd == NULL )
		return(NULL);

	if ( strlen(cmd) >= MAX_CMD_LEN )
		return(NULL);

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

	this->type		 = DAILY;
	this->hour		 = hour;
	this->min		 = min;
	this->sec		 = sec;
	this->expiration = 0;

	strcpy(this->command, cmd);

	return(this);
}


/*
 * deleteDocketItem -- Delete DocketItem
 *
 * This function is used to delete a DocketItem object.  Note that this
 * function does not remove the DocketItem from any lists--that remains
 * the responsibility of the list owner.
 */
VOID deleteDocketItem(DocketItem this)
{
	D(bug("deleteDocketItem(%08lx)\n", this));

	FreeVec(this);
}


/*
 * executeDocketItemCommand -- Execute item command
 *
 * This function asynchronously executes the docket item's command string.
 * Returns TRUE if successful or FALSE on error.
 */
BOOL executeDocketItemCommand(DocketItem this)
{
	STATIC struct TagItem tags[] = {
		{SYS_Asynch, TRUE},
		{SYS_Input, NULL},
		{SYS_Output, NULL},
		{TAG_END}
	};

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

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

	if ( SystemTagList(this->command, tags) == 0 )
		return(TRUE);
	else
		return(FALSE);
}


/*
 * getDocketItemType -- Get item type
 *
 * Gets docket item type.  Returns the docket item type of UNKNOWN on error.
 */
UWORD getDocketItemType(DocketItem this)
{
	D(bug("getDocketItemType(%08lx)\n", this));

	if ( this == NULL )
		return(UNKNOWN);
	else
		return( this->type );
}


/*
 * getDocketItemCommand -- Get item command string
 *
 * Gets docket item command string.  Returns the command string or NULL on
 * error.
 */
STRPTR getDocketItemCommand(DocketItem this)
{
	D(bug("getDocketItemCommand(%08lx)\n", this));

	if ( this == NULL )
		return(NULL);
	else
		return( this->command );
}


/*
 * getDocketItemMonth -- Get item month
 *
 * Returns the docket item month or -1 on error.
 */
WORD getDocketItemMonth(DocketItem this)
{
	D(bug("getDocketItemMonth(%08lx)\n", this));

	if ( this == NULL )
		return(-1);
	else if ( this->type != YEARLY )
		return(-1);
	else
		return( (WORD)this->month );
}


/*
 * getDocketItemMonthDay -- Get item month day
 *
 * Returns the docket item month day or -1 on error.
 */
WORD getDocketItemMonthDay(DocketItem this)
{
	D(bug("getDocketItemMonthDay(%08lx)\n", this));

	if ( this == NULL )
		return(-1);
	else if ( this->type != MONTHLY && this->type != YEARLY )
		return(-1);
	else
		return( (WORD)this->mday );
}


/*
 * getDocketItemWeekDay -- Get item week day
 *
 * Returns the docket item week day or -1 on error.
 */
WORD getDocketItemWeekDay(DocketItem this)
{
	D(bug("getDocketItemWeekDay(%08lx)\n", this));

	if ( this == NULL )
		return(-1);
	else if ( this->type != WEEKLY )
		return(-1);
	else
		return( (WORD)this->wday );
}


/*
 * getDocketItemHour -- Get item hour
 *
 * Returns the docket item hour or -1 on error.
 */
WORD getDocketItemHour(DocketItem this)
{
	D(bug("getDocketItemHour(%08lx)\n", this));

	if ( this == NULL )
		return(-1);
	else
		return( (WORD)this->hour );
}


/*
 * getDocketItemMinute -- Get item minute
 *
 * Returns the docket item minute or -1 on error.
 */
WORD getDocketItemMinute(DocketItem this)
{
	D(bug("getDocketItemMinute(%08lx)\n", this));

	if ( this == NULL )
		return(-1);
	else
		return( (WORD)this->min );
}


/*
 * getDocketItemSecond -- Get item second
 *
 * Returns the docket item second or -1 on error.
 */
WORD getDocketItemSecond(DocketItem this)
{
	D(bug("getDocketItemSecond(%08lx)\n", this));

	if ( this == NULL )
		return(-1);
	else
		return( (WORD)this->sec );
}


/*
 * getDocketItemExpiration -- Get item expiration
 *
 * This function gets the expiration (in seconds) for an item.  A value
 * of zero indicates the expiration has not been set.
 */
ULONG getDocketItemExpiration(DocketItem this)
{
	D(bug("getDocketItemExpiration(%08lx)\n", this));

	if ( this == NULL )
		return(0);
	else
		return( this->expiration );
}


/*
 * setDocketItemType -- Set item type
 *
 * Sets the docket item type.  The item parameters will be reset to default
 * values.  Be sure to save the previous values if you need them.  Returns
 * TRUE if successful or FALSE on error.
 */
BOOL setDocketItemType(DocketItem this, UWORD new_type)
{
	D(bug("setDocketItemType(%08lx, %lu)\n", this, new_type));

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

	switch ( new_type )  {
		case YEARLY:
			this->month	= JANUARY;
			this->mday	= 1;
			break;
		case MONTHLY:
			this->mday = 1;
			break;
		case WEEKLY:
			this->wday = SUNDAY;
			break;
		case DAILY:
			break;
		default:
			return(FALSE);
	}

	this->type		 = new_type;
	this->hour		 = 0;
	this->min		 = 0;
	this->sec		 = 0;
	this->expiration = 0;

	return(TRUE);
}


/*
 * setDocketItemCommand -- Set item command string
 *
 * Sets the docket item command string.  If the new command string is too
 * long it will not be set.  Returns TRUE if successful or FALSE on error.
 */
BOOL setDocketItemCommand(DocketItem this, STRPTR new_cmd)
{
	D(bug("setDocketItemCommand(%08lx, %s)\n", this, new_cmd));

	if ( this == NULL || new_cmd == NULL || strlen(new_cmd) >= MAX_CMD_LEN )
		return(FALSE);

	strcpy(this->command, new_cmd);

	return(TRUE);
}


/*
 * setDocketItemMonth -- Set item month
 *
 * Sets the docket item month.  If the month cannot be set for this type
 * this function will fail.  Returns TRUE if successful or FALSE on error.
 */
BOOL setDocketItemMonth(DocketItem this, UWORD new_month)
{
	D(bug("setDocketItemMonth(%08lx, %lu)\n", this, new_month));

	if ( this == NULL || new_month < MIN_MONTH || new_month > MAX_MONTH )
		return(FALSE);

	if ( this->type != YEARLY )
		return(FALSE);

	this->month = new_month;

	return(TRUE);
}


/*
 * setDocketItemMonthDay -- Set item month day
 *
 * Sets the docket item month day.  If the month day cannot be set for this
 * type this function will fail.  Returns TRUE if successful or FALSE on
 * error.
 */
BOOL setDocketItemMonthDay(DocketItem this, UWORD new_mday)
{
	D(bug("setDocketItemMonthDay(%08lx, %lu)\n", this, new_mday));

	if ( this == NULL || new_mday < MIN_MDAY || new_mday > MAX_MDAY )
		return(FALSE);

	if ( this->type != YEARLY && this->type != MONTHLY )
		return(FALSE);

	if ( this->type == YEARLY && new_mday > max_mday(this->month) )
		return(FALSE);

	this->mday = new_mday;

	return(TRUE);
}


/*
 * setDocketItemWeekDay -- Set item week day
 *
 * Sets the docket item week day.  If the week day cannot be set for this
 * type this function will fail.  Returns TRUE if successful or FALSE on
 * error.
 */
BOOL setDocketItemWeekDay(DocketItem this, UWORD new_wday)
{
	D(bug("setDocketItemWeekDay(%08lx, %lu)\n", this, new_wday));

	if ( this == NULL || new_wday < MIN_WDAY || new_wday > MAX_WDAY )
		return(FALSE);

	if ( this->type != WEEKLY )
		return(FALSE);

	this->wday = new_wday;

	return(TRUE);
}


/*
 * setDocketItemHour -- Set item hour
 *
 * Sets the docket item hour.  Returns TRUE if successful or FALSE on error.
 */
BOOL setDocketItemHour(DocketItem this, UWORD new_hour)
{
	D(bug("setDocketItemHour(%08lx, %lu)\n", this, new_hour));

	if ( this == NULL || new_hour < MIN_HOUR || new_hour > MAX_HOUR )
		return(FALSE);

	this->hour = new_hour;

	return(TRUE);
}


/*
 * setDocketItemMinute -- Set item minute
 *
 * Sets the docket item minute.  Returns TRUE if successful or FALSE on error.
 */
BOOL setDocketItemMinute(DocketItem this, UWORD new_min)
{
	D(bug("setDocketItemMinute(%08lx, %lu)\n", this, new_min));

	if ( this == NULL || new_min < MIN_MINUTE || new_min > MAX_MINUTE )
		return(FALSE);

	this->min = new_min;

	return(TRUE);
}


/*
 * setDocketItemSecond -- Set item second
 *
 * Sets the docket item second.  Returns TRUE if successful or FALSE on error.
 */
BOOL setDocketItemSecond(DocketItem this, UWORD new_sec)
{
	D(bug("setDocketItemSecond(%08lx, %lu)\n", this, new_sec));

	if ( this == NULL || new_sec < MIN_SECOND || new_sec > MAX_SECOND )
		return(FALSE);

	this->sec = new_sec;

	return(TRUE);
}


/*
 * setDocketItemExpiration -- Set item expiration
 *
 * Sets a docket item's expiration relative to the given time in seconds.
 * The expiration will be set as close to the given time as possible to
 * allow the user to start and stop the docket at any time.  If any error
 * occurs, the expiration will be reset to zero.  Returns TRUE if successful
 * or FALSE on error.
 */
BOOL setDocketItemExpiration(DocketItem this, ULONG now)
{
	struct ClockData clock, now_clock;
	UWORD days_diff;

	D(bug("setDocketItemExpiration(%08lx, %lu)\n", this, now));

	if ( this == NULL || now == 0 )
		return(FALSE);

	Amiga2Date(now, &now_clock);

	if ( now_clock.year >= MAX_YEAR )
		return(FALSE);

	this->expiration = 0;

	switch ( this->type )  {
		case YEARLY:
			clock.year  = now_clock.year;
			clock.month = this->month;
			clock.mday  = adjust_mday(clock.year, this->month, this->mday);
			clock.hour  = this->hour;
			clock.min   = this->min;
			clock.sec   = this->sec;
			clock.wday  = 0;

			this->expiration = Date2Amiga(&clock);

			if ( this->expiration <= now )  {
				clock.year += 1;
				clock.mday = adjust_mday(clock.year, this->month, this->mday);
				this->expiration = Date2Amiga(&clock);
			}
			return(TRUE);
		case MONTHLY:
			clock.year  = now_clock.year;
			clock.month = now_clock.month;
			clock.mday  = adjust_mday(clock.year, clock.month, this->mday);
			clock.hour  = this->hour;
			clock.min   = this->min;
			clock.sec   = this->sec;
			clock.wday  = 0;

			this->expiration = Date2Amiga(&clock);

			if ( this->expiration <= now )  {
				clock.month += 1;
				if ( clock.month > MONTHS_PER_YEAR )  {
					clock.year += 1;
					clock.month = JANUARY;
				}

				clock.mday = adjust_mday(clock.year, clock.month, this->mday);
				this->expiration = Date2Amiga(&clock);
			}
			return(TRUE);
		case WEEKLY:
			clock.year  = now_clock.year;
			clock.month = now_clock.month;
			clock.mday  = now_clock.mday;
			clock.hour  = this->hour;
			clock.min   = this->min;
			clock.sec   = this->sec;
			clock.wday  = 0;

			this->expiration = Date2Amiga(&clock);

			if ( this->wday > now_clock.wday )
				days_diff = this->wday - now_clock.wday;
			else if ( this->wday < now_clock.wday )
				days_diff = DAYS_PER_WEEK - (now_clock.wday - this->wday);
			else if ( this->expiration <= now )
				days_diff = DAYS_PER_WEEK;
			else
				days_diff = 0;

			this->expiration += days_diff * SECS_PER_DAY;
			return(TRUE);
		case DAILY:
			clock.year  = now_clock.year;
			clock.month = now_clock.month;
			clock.mday  = now_clock.mday;
			clock.hour  = this->hour;
			clock.min   = this->min;
			clock.sec   = this->sec;
			clock.wday  = 0;

			this->expiration = Date2Amiga(&clock);

			if ( this->expiration <= now )
				this->expiration += SECS_PER_DAY;

			return(TRUE);
		default:
			D2(bug(" item type=%lu\n", this->type));
	}

	return(FALSE);
}


/*
 * max_mday -- Get maximum month day
 *
 * This function gets the maximum month day for a given month.  Note that the
 * year is ignored so this function does not take into account leap years.
 * Returns the maximum days in the month or zero on error.
 */
STATIC UWORD max_mday(UWORD month)
{
	switch ( month )  {
		case FEBRUARY:
			return(29);
		case APRIL:
		case JUNE:
		case SEPTEMBER:
		case NOVEMBER:
			return(30);
		case JANUARY:
		case MARCH:
		case MAY:
		case JULY:
		case AUGUST:
		case OCTOBER:
		case DECEMBER:
			return(31);
		default:
			return(0);
	}
}


/*
 * adjust_mday -- Adjust month day
 *
 * Adjust the month day according to the given year and month so that the
 * month day is valid.  If the month day is too great for the given year
 * and month, the maximum allowable month day is returned.  Otherwise, the
 * given month day value will be returned.
 *
 * WARNING: There is no date checking done by this function.
 */
STATIC UWORD adjust_mday(UWORD year, UWORD month, UWORD mday)
{
	UWORD max_mday;

	switch ( month )  {
		case FEBRUARY:
			if ( year % 4 == 0 )	/* simplified due to year range */
				max_mday = 29;
			else
				max_mday = 28;
			break;
		case APRIL:
		case JUNE:
		case SEPTEMBER:
		case NOVEMBER:
			max_mday = 30;
			break;
		case JANUARY:
		case MARCH:
		case MAY:
		case JULY:
		case AUGUST:
		case OCTOBER:
		case DECEMBER:
			max_mday = 31;
			break;
		default:
			max_mday = mday;
	}

	if ( mday > max_mday )
		return(max_mday);
	else
		return(mday);
}
