/* this file contains some functions for handling the heartbeat. */

#include <sys/types.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/tasks.h>
#include <exec/interrupts.h>
#include <devices/timer.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <functions.h>

/* our functions */
void setup_exception(void);
int alarm(int);
void cleanexit(char *, LONG);
void cleanup_exception(void);

static struct timerequest	*treq = NULL;
static struct MsgPort		*tport = NULL;
static BOOL OpenedTimer = FALSE;
static int setup = 0;
int eintr_signal;

extern int comm_time_to_call_heartbeat;
extern int errno;


static void setup_exception()
{
    /* allocate a signal , used in select() */
    if((eintr_signal = AllocSignal(-1)) == -1)
	cleanexit("Could not allocate EINTR signal\n", RETURN_FAIL);

    /* allocate a messageport */
    if(!(tport = CreatePort(0L, 0L)))
	cleanexit("Could not allocate port\n", RETURN_FAIL);

    if(!(treq = (struct timerequest *)
		CreateExtIO(tport, sizeof(struct timerequest))))
	cleanexit("Could not allocate message\n",RETURN_FAIL);

    /* open the timer device */
    if(OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest* )treq, 0L))
	cleanexit("Can't open timer device\n", RETURN_FAIL);

    OpenedTimer = TRUE;

    setup = 1;
}

/* signal installs a handler for a given signal. */
void signal(int sig, void (* handler) () )
{
    register ULONG except_signal;
    register struct Task *this_task;

    if(!setup)
	setup_exception();

    except_signal = 1L << tport->mp_SigBit;
    this_task = FindTask(NULL);

    if(handler == SIG_IGN)
	{	/* remove handler */
	except_signal = this_task->tc_SigExcept;
	SetExcept(0L, except_signal);
	cleanup_exception();
	}
    else if(sig == SIGALRM)
	{	/* install handler */
	this_task->tc_ExceptCode = (APTR) handler;
	SetExcept(except_signal, except_signal);
	}
    /* else do nothing .. */
}


/* clean up the mess made by a previous alarm, and set a new alarm up */
int alarm(int seconds)
{
    while(GetMsg(tport))
	;
    if(seconds > 0) {
	/* set up the new timer command */
	treq->tr_node.io_Command = TR_ADDREQUEST;
	treq->tr_time.tv_secs = seconds;
	treq->tr_time.tv_micro = 0;
	SendIO((struct IORequest *) treq);
	}
    else
	{
	/* delete out-standing request */
	AbortIO((struct IORequest *) treq);
	WaitIO((struct IORequest *) treq);
	while(GetMsg(tport))
	    ;
	}
    return 0;
}


static void cleanexit(char *s, LONG n)
{
    if(*s)  printf(s);
    cleanup_exception();
    exit(n);
}


static void cleanup_exception()
{
    if(OpenedTimer)	CloseDevice((struct IORequest *) treq);
    OpenedTimer = 0;
    if(treq)		DeleteExtIO((struct IORequest *) treq);
	treq = 0;
    if(tport)		DeletePort(tport);
	tport = 0;
    setup = 0;
}

/* check if any signals from the sets are available
 * return -1 if failure,
 *         0 if time-out
 * number of ready descriptor otherwise
 * I use the heartbeat as timeout, but according to the UNIX-manual, an
 * interrupted select() returns the unchanged masks. But we already cleared
 * the signals, so the messages would not be read next time. So just save
 * the ready sockets, so we can read them next time.
 */
int select (width, readfds, writefds, exceptfds, timeout)
    int width;
    register fd_set *readfds, *writefds, *exceptfds;
    struct timeval *timeout;
{
    static fd_set saved = 0L;

    /* find all ready descriptors */
    if(saved) {
	*readfds = saved;  /* these were ready the last time already */
	saved = 0L;	   /* not needed anymore */
	}
    else
	*readfds = Wait(*readfds | eintr_signal);

    if(*readfds & eintr_signal) {
	saved = *readfds &~eintr_signal;
	errno = EINTR;
	return -1; /* Failure */
	}

    return 1;  /* Well, this should be the number of ready descriptors */
}
