/*
 * timer.c
 *
 * Appshell Timer Handler
 * Copyright (C) 1991 Kompositor Software
 * Written by John F Wiederhirn
 *
 * Based on the AppShell Notification Handler
 * Copyright (C) 1990 Commodore-Amiga, Inc.
 * Written by David N. Junod
 *
 */

/* The normal AppShell and system of includes... */

#include <exec/exec.h>
#include <devices/timer.h>
#include <libraries/appshell.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/libraries.h>
#include <intuition/intuition.h>
#include <graphics/gfx.h>
#include <libraries/gadtools.h>
#include <libraries/appshell.h>
#include <utility/tagitem.h>
#include <clib/alib_protos.h>
#include <clib/alib_stdio_protos.h>
#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/appshell_protos.h>
#include <clib/utility_protos.h>
#include <pragmas/appshell.h>
#include <string.h>
#include "ae:support/misc_protos.h"

#include "timer.h"

extern struct Library *AppShellBase;

/* Grabbing this will embed the version information generated */
/* by BUMPREV in the make process into the actual executable. */
#include "timer_rev.h"

/* This is the global Timer handler information packet. */
struct TimerInfo
{
    struct timerequest *ti_TR;	/* Template timerequest */
};

/* The TimerObject structure holds all the information specific to a */
/* given timer event request.  It also contains pointers to a dispatch */
/* function and the actual timer event request (along with a duplicate */
/* timeval struct to reset the request in a TH_TICKING situation. */
struct TimerObject
{
    struct timerequest ti_TR;	/* Embedded. */
    struct timeval ti_TimeVal;	/* Embedded, holds the length to wait */
    ULONG ti_FuncID;		/* The app FuncID to execute */
    ULONG ti_Mode;		/* Either ticking or one-shot */
    struct MHObject *ti_MHO;	/* Pointer back to parent */
};

/* timer.c: function prototypes */
BOOL open_timer (struct AppInfo *, struct MsgHandler *, struct TagItem *);
BOOL handle_timer (struct AppInfo *, struct MsgHandler *, struct TagItem *);
BOOL close_timer (struct AppInfo *, struct MsgHandler *, struct TagItem *);
BOOL shutdown_timer (struct AppInfo *, struct MsgHandler *, struct TagItem *);

/* The following comment block is in Autodoc format.  This is a stringent
 * standard, do NOT modify the format!!! */

/****** timer.lib/setup_timerA *********************************************
*
*    NAME
*	setup_timerA - Initializes the timer message handler.
*
*    SYNOPSIS
*	mh = setup_timerA (ai, tl)
*	D0                 D0  D1
*
*	struct MsgHandler *mh;
*	struct AppInfo *ai;
*	struct TagItem *tl;
*
*    FUNCTION
*	This is the low-level function used to initialize the timer message
*	handler.
*
*	NOTE: This call should not be called directly by the application,
*	but should invoked by using the APSH_AddHandler tag.
*
*	There are no tags recognized at startup time.
*
*	Valid tagitems to use at OPEN time:
*
*	APSH_TimerMode, <timer mode>
*	This determines the what the handler returns to the user
*	application.  Recognizes:
*
*          TH_TICKING      Repeated timer events, with frequency
*                          based on content of APSH_TimerLen
*
*          TH_ONESHOT      One-shot alarm after duration given
*                          in APSH_TimerLen
*
*	APSH_TimerLen,  <timeval>
*	Sets the duration of the timer command.  Data is a pointer
*	to a timeval structure (see timer.device).
*
*	APSH_CmdID,     <function ID>
*	Function to execute when timer triggered.  Examine APSH_NameTag
*	to get the ID code for the timer request.
*
*
*    INPUTS
*	ai  - Pointer to the application's AppInfo structure.
*	tl  - Pointer to an array of TagItem attributes for the
*	      function.
*
*    RESULT
*	mh  - Pointer to a MsgHandler structure.
*
*    BUGS
*
*    SEE ALSO
*
**************************************************************************
*  Created:  21-May-91, John F Wiederhirn
*
*/

struct MsgHandler * ASM
setup_timerA (REG(d0) struct AppInfo * ai, REG(d1) struct TagItem * tl)
{
    struct MsgHandler *mh;
    struct MHObject *mho;
    struct TimerInfo *md;

    ULONG msize;

    /* This determines the amount of memory needed to hold the */
    /* "anchor" information for a msg handler */
    msize = sizeof (struct MsgHandler) +
      sizeof (struct TimerInfo) +
      (4L * sizeof (ULONG));

    /* Attempt to allocate the structure.  If it fails, fall */
    /* through to a panic condition. */
    if (mh = (struct MsgHandler *) AllocVec (msize, MEMFLAGS))
    {
	/* mho gets set to point to the embedded MHObject */
	/* structure inside the MsgHandler struct we allocated... */
	mho = &(mh->mh_Header);

	/* ...and the node embedded inside the MHObject struct */
	/* needs to be set so that the system list handling */
	/* functions can identify the owner. */
	mho->mho_Node.ln_Type = APSH_MH_HANDLER_T;
	mho->mho_Node.ln_Pri = APSH_MH_HANDLER_P;
	mho->mho_Node.ln_Name = "TIMER";

	/* Initialize the list anchor.  All timer requests */
	/* will be attached to this list anchor.  List is */
	/* a standard doubly-linked Exec list. */
	NewList (&(mho->mho_ObjList));

	/* This is just our personal message handler ID code */
	/* so that AppShell can identify who is the owner of */
	/* messages which get passed to it by Intuition. */
	mho->mho_ID = APSH_TIMER_ID;

	/* get a pointer to the instance data */
	mho->mho_SysData = md = MEMORY_FOLLOWING (mh);

	/* The array of function pointers is set up so that */
	/* AppShell can dispatch messages as needed to the */
	/* proper Timer Handler functions. */
	mh->mh_NumFuncs = 4;
	mh->mh_Func = MEMORY_FOLLOWING (md);
	mh->mh_Func[APSH_MH_OPEN] = open_timer;
	mh->mh_Func[APSH_MH_HANDLE] = handle_timer;
	mh->mh_Func[APSH_MH_CLOSE] = close_timer;
	mh->mh_Func[APSH_MH_SHUTDOWN] = shutdown_timer;

	/* Create a standard Exec IPC message port.  Since the */
	/* port is private to the AppShell-based application */
	/* and only AppShell will be sending messages to it, we */
	/* don't need to bother giving it a name... */
	if (mh->mh_Port = CreatePort (NULL, NULL))
	{
	    /* ...but we do need to make sure that AppShell is */
	    /* aware of which bit is going to be the trigger for */
	    /* incoming message notification.  This is done by */
	    /* duplicating the mask of the port in the MsgHandler */
	    /* field designed for it. */
	    mh->mh_SigBits = (1L << mh->mh_Port->mp_SigBit);

	    /* Create the template timerequest */
	    if (md->ti_TR = (struct timerequest *)
		CreateExtIO (mh->mh_Port, sizeof (struct timerequest)))
	    {
		/* Open the timer.device */
		if (!(OpenDevice ("timer.device", NULL, (struct IORequest *) md->ti_TR, UNIT_WAITUNTIL)))
		{
		    struct timerequest *treq = md->ti_TR;

		    /* Also, the node embedded in the timerrequest needs */
		    /* to indicate the IPC port which should be notified. */
		    treq->tr_node.io_Message.mn_ReplyPort = mh->mh_Port;

		    /* Set the command to tell the system a timer event */
		    /* is being requested, and to add it to the queue. */
		    treq->tr_node.io_Command = TR_ADDREQUEST;

		    /* All the non-dynamic setup for the message handler */
		    /* is complete.  Return a pointer to the valid */
		    /* MsgHandler structure. */
		    return (mh);
		}

		/* Delete the template timerequest */
		DeleteExtIO ((struct IORequest *) md->ti_TR);
		md->ti_TR = NULL;
	    }

	    DeletePort (mh->mh_Port);
	    mh->mh_Port = NULL;

	    /* Unable to allocate things */
	    ai->ai_Pri_Ret = RETURN_FAIL;
	    ai->ai_Sec_Ret = APSH_CLDNT_CREATE_PORT;
	    ai->ai_TextRtn = PrepText (ai, APSH_MAIN_ID, ai->ai_Sec_Ret, NULL);
	}
	else
	{
	    /* The system was unable to allocate the IPC port. */
	    /* We set the proper flags to inform AppShell of the */
	    /* failure, and prepare the error message we want */
	    /* AppShell to display to the user (In this case, */
	    /* the stock "Timer Handler couldn't create port!" */
	    /* AppShell message). */
	    ai->ai_Pri_Ret = RETURN_FAIL;
	    ai->ai_Sec_Ret = APSH_CLDNT_CREATE_PORT;
	    ai->ai_TextRtn = PrepText (ai, APSH_MAIN_ID, ai->ai_Sec_Ret, NULL);
	}

	/* The memory allocated for the MsgHandler structure can */
	/* be deallocated now.  Just to make it clear, we also clear */
	/* the mh and mho pointers for easy debugging. */
	FreeVec ((APTR) mh);
	mho = NULL;
	mh = NULL;
    }
    else
    {
	/* Things are pretty bad.  The attempt to allocate the memory for */
	/* the MsgHandler failed.  Chances are, the system will not be able */
	/* to put up any requesters, but we set the AppShell flag to */
	/* indicate we failed, and prepare the message (stock AppShell */
	/* "Not Enough Memory!" message). */
	ai->ai_Pri_Ret = RETURN_FAIL;
	ai->ai_Sec_Ret = APSH_NOT_ENOUGH_MEMORY;
	ai->ai_TextRtn = PrepText (ai, APSH_MAIN_ID, ai->ai_Sec_Ret, NULL);
    }

    /* Return NULL to indicate failure just in case something besides */
    /* AppShell is watching entry/exit conditions. */
    return (NULL);

}

BOOL open_timer (struct AppInfo * ai, struct MsgHandler * mh, struct TagItem * tl)
{
    struct MHObject *mho = &(mh->mh_Header);
    struct TimerInfo *md = mho->mho_SysData;
    struct TimerObject *tinf;
    struct timerequest *treq;
    struct MHObject *mob;
    BOOL retval = FALSE;
    struct timeval *tv;
    STRPTR nam1, nam2;
    ULONG mode, tsize;
    LONG FuncID;

    /* The list of tags pointed to by the tl pointer contain the key-value */
    /* pairs for the request to be queued.  This is done in an array of */
    /* standard Exec TagItems. */

    /* The _NameTag TagItem needs to contain a pointer to a string */
    /* which has the unique name for the request.  Since this is a */
    /* required parameter, the default is NULL.  Also, because the */
    /* "data side" of a TagItem key-value set is defined as a long, */
    /* we need to cast it to a STRPTR. */
    nam1 = (STRPTR) GetTagData (APSH_NameTag, NULL, tl);

    /* The _CmdID Tag contains the AppShell ID number for the Application */
    /* function to be executed when the timer event occurs.  Again, the */
    /* Tag is required so the default is NULL.  ID numbers happen to be */
    /* longs as well, so no cast. */
    FuncID = GetTagData (APSH_CmdID, NULL, tl);

    /* The last of the required Tags, the _TimerLen should contain a */
    /* pointer to a standard timer.device timeval structure which sets */
    /* the number of seconds and microseconds until the event. */
    /* */
    /* NOTE: Timing hasnt been saturation tested yet, so for the time */
    /* being, this Handler is certified only for "second" granularity. */
    tv = (struct timeval *) GetTagData (APSH_TimerLen, NULL, tl);

    /* The optional _TimerMode Tag contains a mode setting for the */
    /* event to be queued.  The default value is TH_TICKING, which */
    /* causes a recurrent event stream from the request.  TH_ONESHOT */
    /* as mode would cause a single event from the request. */
    mode = GetTagData (APSH_TimerMode, TH_TICKING, tl);

    /* This makes sure the required Tags were all present.  If not, */
    /* the function simply returns a FALSE value. */
    if (nam1 && FuncID && tv)
    {
	/* Here, the mode is checked to verify it falls within the */
	/* range of acceptable values.  Failure case just falls */
	/* through to a FALSE return right now. */
	if ((mode > TH_STRTMODE) && (mode < TH_ENDMODE))
	{
	    /* Calculate the amount of memory needed to hold all */
	    /* of the structs and data related to a given request... */
	    tsize = sizeof (struct MHObject) +
	      sizeof (struct TimerObject) +
	      strlen (nam1) + 1L;

	    /* ...and allocate them. Failure simply falls through */
	    /* to a FALSE return currently.  This will eventually set */
	    /* up an error condition similar to the failed allocation */
	    /* error condition in setup_timerA.  The first structure */
	    /* is the MHObject so it gets the pointer returned from */
	    /* the allocation and casts it to the proper type. */
	    if (mob = (struct MHObject *) AllocVec (tsize, MEMFLAGS))
	    {
		/* The rest of the structure pointers can now be aimed */
		/* to the proper offsets inside the allocated block and */
		/* cast to the proper type. */
		tinf = (struct TimerObject *) MEMORY_FOLLOWING (mob);
		tinf->ti_MHO = mob;
		nam2 = (STRPTR) MEMORY_FOLLOWING (tinf);

		/* The embedded Exec node structure needs to be setup */
		/* so the system knows who owns it.  The values get */
		/* set to the assigned Timer Handler values. */
		mob->mho_Node.ln_Type = APSH_MH_HANDLER_T;
		mob->mho_Node.ln_Pri = APSH_MH_HANDLER_P;
		mob->mho_Node.ln_Name = nam2;

		/* A field of the request's MHObject structure is set */
		/* to point to the related TimerObject.  We need this */
		/* later to find everything... */
		mob->mho_SysData = tinf;

		/* Set the parent object */
		mob->mho_Parent = mho;

		/* The MHObject is declared to belong to the Timer */
		/* Handler, so AppShell can dispatch properly when. */
		/* the event occurs. */
		mob->mho_ID = APSH_TIMER_ID;

		/* Copy the contents pointed at by the _NameTag Tag */
		/* into our own memory. */
		strcpy (nam2, nam1);

		/* And finally, attach the request to the list of */
		/* requests which are anchored by the MHObject that */
		/* is embedded in the MsgHandler structure. */
		AddTail (&(mho->mho_ObjList), &(mob->mho_Node));

		/* Copy over the values from the template (p.874 L&D RKM) */
		tinf->ti_TR = *md->ti_TR;

		/* Get a pointer to the embedded timerequest */
		treq = &(tinf->ti_TR);

		/* Initialize the timerequest structure with the */
		/* values for the seconds and microseconds to wait */
		/* until triggering the event. */
		treq->tr_time.tv_secs = tinf->ti_TimeVal.tv_secs = tv->tv_secs;
		treq->tr_time.tv_micro = tinf->ti_TimeVal.tv_micro = tv->tv_micro;

		/* Initialize the TimerObject structure with the */
		/* information passed in the Tag array and where */
		/* our handler should call to redispatch to the */
		/* function given by the application. */
		tinf->ti_Mode = mode;
		tinf->ti_FuncID = FuncID;

		/* Everything has been intialized.  The request is */
		/* placed in the system queue, and finally the retval */
		/* var is changed so the function returns TRUE. */
		SendIO ((struct IORequest *) treq);

		/* Increment the number of outstanding messages */
		mh->mh_Outstanding++;

		retval = TRUE;
	    }
	}
    }

    /* Whatever the retval var holds at this point, that's what the */
    /* AppShell dispatcher gets back. */
    return (retval);
}

BOOL handle_timer (struct AppInfo * ai, struct MsgHandler * mh, struct TagItem * tl)
{
    struct MHObject *mho = &(mh->mh_Header);
    struct TimerObject *tinf;
    struct timerequest *treq;
    struct timeval *tv;
    LONG FuncID;

    /* Pull the messages from the port */
    while (tinf = (struct TimerObject *) GetMsg (mh->mh_Port))
    {
	/* Decrement the number of outstanding messages */
	mh->mh_Outstanding--;

	/* Set the current object */
	mho->mho_CurNode = tinf->ti_MHO;

	/* This ugly little declaration/initialization just */
	/* pulls the ID number of the application function to */
	/* be called from where it was stashed in the TimerObject */
	/* structure. */
	FuncID = tinf->ti_FuncID;

	/* Since the function ID comes from the application, its */
	/* not safe to assume it will be meaningful.  AppShell can */
	/* handle the case where the function ID doesnt point to */
	/* an existing app function, but this cannot be detected */
	/* by the dispatcher so gets checked here.  NO_FUNCTION is */
	/* the legal value for a null function table ID. */
	if (FuncID != NO_FUNCTION)
	{
	    /* This Tag array, like the FuncID var, doesnt need */
	    /* to hang around long, so local is fine. */
	    struct TagItem attrs[2];

	    /* All these do is inform the dispatcher where the */
	    /* dispatch came from (a handler), and who owns it */
	    /* (the Timer handler). */
	    attrs[0].ti_Tag = APSH_Handler;
	    attrs[0].ti_Data = APSH_TIMER_ID;
	    attrs[1].ti_Tag = TAG_DONE;

	    /* This is the entry point for the AppShell dispatcher */
	    /* for external dispatch requests.  It looks up the ID */
	    /* in its tables and dispatches to that function.  The */
	    /* NULL is because we are passing no command line along */
	    /* to the function. */
	    PerfFunc (ai, FuncID, NULL, NULL);
	}

	/* treq to the timerequest struct stashed in the TimerObject... */
	treq = (struct timerequest *) & (tinf->ti_TR);

	/* and tv to the timeval struct embedded in the TimerObject. */
	tv = (struct timeval *) & (tinf->ti_TimeVal);

	/* When DispatchUser returns, this determines the mode so */
	/* that the request can be requeued or terminated. */
	switch (tinf->ti_Mode)
	{
		/* Just as it looks, requeue the request for another */
		/* run.  Since the timer device trashes the values in */
		/* the actual timerequest for how long to wait, they */
		/* get re-initialized from the duplicate copies in the */
		/* TimerObject structure.  Then the request is sent via */
		/* the generic SendIO function back into the timer device */
		/* queue. */
	    case TH_TICKING:
		treq->tr_time.tv_secs = tv->tv_secs;
		treq->tr_time.tv_micro = tv->tv_micro;
		SendIO ((struct IORequest *) treq);

		/* Increment the number of outstanding messages */
		mh->mh_Outstanding++;
		break;

		/* In this case, the request can be terminated, so it */
		/* gets passed to close_timer() for deallocation.  The node */
		/* pointer gets reused here.  The two NULLs are to */
		/* satify the prototype. */
	    case TH_ONESHOT:
		close_timer (ai, mh, NULL);
		break;
	}
    }

    /* Clear the current object, now that it is out there in the TwiZone */
    mho->mho_CurNode = NULL;

    return (TRUE);
}

BOOL close_timer (struct AppInfo * ai, struct MsgHandler * mh, struct TagItem * tl)
{
    struct MHObject *mho = &(mh->mh_Header);
    struct List *mhl = &(mho->mho_ObjList);
    struct MHObject *mob = NULL;
    struct TimerObject *tinf;
    struct timerequest *treq;
    struct timeval *tv;
    BOOL retval = FALSE;
    STRPTR nam1;

    /* See if they specified an event to close */
    if (nam1 = (STRPTR) GetTagData (APSH_NameTag, NULL, tl))
    {
	/* Locate the node which contains the timer request. */
	/* Since the node will always be the very first part */
	/* of the MHObject it is related to, we can create a */
	/* pointer to the MHObject with a simple cast. */
	mob = (struct MHObject *) FindName (mhl, nam1);
    }
    /* No name was provided, so close the current object */
    else
    {
	/* Get a pointer to the current object */
	mob = mho->mho_CurNode;
    }

    /* See if we have an object to close */
    if (mob)
    {
	/* Once again, all the pointers need to be aimed at the proper */
	/* elements of the request. */
	tinf = (struct TimerObject *) mob->mho_SysData;
	treq = (struct timerequest *) & (tinf->ti_TR);
	tv = (struct timeval *) & (tinf->ti_TimeVal);

	/* Since this can be called by close_timer with the request */
	/* still outstanding, test if it is. */
	if (~CheckIO ((struct IORequest *) treq))
	{
	    /* Decrement the number of outstanding messages */
	    mh->mh_Outstanding--;

	    /* Its outstanding.  Use the generic AbortIO function */
	    /* to force the system to abort the request. */
	    AbortIO ((struct IORequest *) treq);

	    /* This needs to wait until the system tells it that */
	    /* the request has been aborted. */
	    WaitIO ((struct IORequest *) treq);
	}

	/* This unlinks the request from within our list of requests. */
	Remove ((struct Node *) mob);

	/* And finally, all the memory allocated in open_timer can be given */
	/* back to the system. */
	FreeVec (mob);

	/* Indicate success */
	retval = TRUE;
    }

    return (retval);
}

BOOL shutdown_timer (struct AppInfo * ai, struct MsgHandler * mh, struct TagItem * tl)
{
    struct MHObject *mho = &(mh->mh_Header);
    struct TimerInfo *md = mho->mho_SysData;
    struct List *list = &(mho->mho_ObjList);
    struct Node *node, *nxtnode;

    /* Remove the message handler from the AppShell list */
    Remove ((struct Node *) mh);

    /* Get rid of any outstanding timer requests */

    /* Make sure there are entries in the list */
    if (list->lh_TailPred != (struct Node *) list)
    {
	/* Let's start at the very beginning... */
	node = list->lh_Head;

	/* Continue while there are still nodes */
	while (nxtnode = node->ln_Succ)
	{
	    /* Make this the current node */
	    mho->mho_CurNode = (struct MHObject *) node;

	    /* Close the current node */
	    close_timer (ai, mh, NULL);

	    /* Go on to the next node */
	    node = nxtnode;
	}
    }

    /* Close the timer device */
    CloseDevice ((struct IORequest *) md->ti_TR);

    /* Delete the template timerequest */
    DeleteExtIO ((struct IORequest *) md->ti_TR);

    /* At this point, the IPC port will receive no further */
    /* messages from the timer device as all requests are */
    /* deleted.  This implies all messages have been */
    /* ReplyMsg'd as well, so its now safe to close the */
    /* port. */
    DeletePort (mh->mh_Port);

    /* Everything else is deallocated so this just returns */
    /* the memory allocated in setup_timerA back to the */
    /* system. */
    FreeVec (mh);

    return (TRUE);
}

/* END OF TIMER.C */
