/***********************************************************************
*
* event.c
*
* Abstraction of select call into "event-handling" to make programming
* easier.
*
* Copyright (C) 2001 Roaring Penguin Software Inc.
*
* This program may be distributed according to the terms of the GNU
* General Public License, version 2 or (at your option) any later version.
*
* LIC: GPL
*
***********************************************************************/

static char const RCSID[] =
"$Id$";

#include "event.h"
#include <stdlib.h>
#include <errno.h>

static void DestroySelector(EventSelector *es);
static void DestroyHandler(EventHandler *eh);
static void DoPendingChanges(EventSelector *es);

/**********************************************************************
* %FUNCTION: Event_CreateSelector
* %ARGUMENTS:
*  None
* %RETURNS:
*  A newly-allocated EventSelector, or NULL if out of memory.
* %DESCRIPTION:
*  Creates a new EventSelector.
***********************************************************************/
EventSelector *
Event_CreateSelector(void)
{
    EventSelector *es = malloc(sizeof(EventSelector));
    if (!es) return NULL;
    es->handlers = NULL;
    es->nestLevel = 0;
    es->destroyPending = 0;
    es->opsPending = 0;
    EVENT_DEBUG(("CreateSelector() -> %p\n", (void *) es));
    return es;
}

/**********************************************************************
* %FUNCTION: Event_DestroySelector
* %ARGUMENTS:
*  es -- EventSelector to destroy
* %RETURNS:
*  Nothing
* %DESCRIPTION:
*  Destroys an EventSelector.  Destruction may be delayed if we
*  are in the HandleEvent function.
***********************************************************************/
void
Event_DestroySelector(EventSelector *es)
{
    if (es->nestLevel) {
	es->destroyPending = 1;
	es->opsPending = 1;
	return;
    }
    DestroySelector(es);
}

/**********************************************************************
* %FUNCTION: Event_HandleEvent
* %ARGUMENTS:
*  es -- EventSelector
* %RETURNS:
*  0 if OK, non-zero on error.  errno is set appropriately.
* %DESCRIPTION:
*  Handles a single event (uses select() to wait for an event.)
***********************************************************************/
int
Event_HandleEvent(EventSelector *es)
{
    fd_set readfds, writefds;
    fd_set *rd, *wr;
    unsigned int flags;

    struct timeval abs_timeout, now;

    struct timeval timeout;
    struct timeval *tm;
    EventHandler *eh;

    int r = 0;
    int errno_save = 0;
    int foundTimeoutEvent = 0;
    int foundReadEvent = 0;
    int foundWriteEvent = 0;
    int maxfd = -1;
    int pastDue;

    /* Avoid compiler warning */
    abs_timeout.tv_sec = 0;
    abs_timeout.tv_usec = 0;

    EVENT_DEBUG(("Enter Event_HandleEvent(es=%p)\n", (void *) es));

    /* Build the select sets */
    FD_ZERO(&readfds);
    FD_ZERO(&writefds);

    eh = es->handlers;
    for (eh=es->handlers; eh; eh=eh->next) {
	if (eh->flags & EVENT_FLAG_DELETED) continue;
	if (eh->flags & EVENT_FLAG_READABLE) {
	    foundReadEvent = 1;
	    FD_SET(eh->fd, &readfds);
	    if (eh->fd > maxfd) maxfd = eh->fd;
	}
	if (eh->flags & EVENT_FLAG_WRITEABLE) {
	    foundWriteEvent = 1;
	    FD_SET(eh->fd, &writefds);
	    if (eh->fd > maxfd) maxfd = eh->fd;
	}
	if (eh->flags & EVENT_TIMER_BITS) {
	    if (!foundTimeoutEvent) {
		abs_timeout = eh->tmout;
		foundTimeoutEvent = 1;
	    } else {
		if (eh->tmout.tv_sec < abs_timeout.tv_sec ||
		    (eh->tmout.tv_sec == abs_timeout.tv_sec &&
		     eh->tmout.tv_usec < abs_timeout.tv_usec)) {
		    abs_timeout = eh->tmout;
		}
	    }
	}
    }
    if (foundReadEvent) {
	rd = &readfds;
    } else {
	rd = NULL;
    }
    if (foundWriteEvent) {
	wr = &writefds;
    } else {
	wr = NULL;
    }

    if (foundTimeoutEvent) {
	gettimeofday(&now, NULL);
	/* Convert absolute timeout to relative timeout for select */
	timeout.tv_usec = abs_timeout.tv_usec - now.tv_usec;
	timeout.tv_sec = abs_timeout.tv_sec - now.tv_sec;
	if (timeout.tv_usec < 0) {
	    timeout.tv_usec += 1000000;
	    timeout.tv_sec--;
	}
	if (timeout.tv_sec < 0 ||
	    (timeout.tv_sec == 0 && timeout.tv_usec < 0)) {
	    timeout.tv_sec = 0;
	    timeout.tv_usec = 0;
	}
	tm = &timeout;
    } else {
	tm = NULL;
    }

    if (foundReadEvent || foundWriteEvent || foundTimeoutEvent) {
	for(;;) {
	    r = select(maxfd+1, rd, wr, NULL, tm);
	    if (r < 0) {
		if (errno == EINTR) continue;
	    }
	    break;
	}
    }

    if (foundTimeoutEvent) gettimeofday(&now, NULL);
    errno_save = errno;
    es->nestLevel++;

    if (r >= 0) {
	/* Call handlers */
	for (eh=es->handlers; eh; eh=eh->next) {

	    /* Pending delete for this handler?  Ignore it */
	    if (eh->flags & EVENT_FLAG_DELETED) continue;

	    flags = 0;
	    if ((eh->flags & EVENT_FLAG_READABLE) &&
		FD_ISSET(eh->fd, &readfds)) {
		flags |= EVENT_FLAG_READABLE;
	    }
	    if ((eh->flags & EVENT_FLAG_WRITEABLE) &&
		FD_ISSET(eh->fd, &writefds)) {
		flags |= EVENT_FLAG_WRITEABLE;
	    }
	    if (eh->flags & EVENT_TIMER_BITS) {
		pastDue = (eh->tmout.tv_sec < now.tv_sec ||
			   (eh->tmout.tv_sec == now.tv_sec &&
			    eh->tmout.tv_usec <= now.tv_usec));
		if (pastDue) {
		    flags |= EVENT_TIMER_BITS;
		    if (eh->flags & EVENT_FLAG_TIMER) {
			/* Timer events are only called once */
			es->opsPending = 1;
			eh->flags |= EVENT_FLAG_DELETED;
		    }
		}
	    }
	    /* Do callback */
	    if (flags) {
		EVENT_DEBUG(("Enter callback: eh=%p flags=%u\n", eh, flags));
		eh->fn(es, eh->fd, flags, eh->data);
		EVENT_DEBUG(("Leave callback: eh=%p flags=%u\n", eh, flags));
	    }
	}
    }

    es->nestLevel--;

    if (!es->nestLevel && es->opsPending) {
	DoPendingChanges(es);
    }
    errno = errno_save;
    return r;
}

/**********************************************************************
* %FUNCTION: Event_AddHandler
* %ARGUMENTS:
*  es -- event selector
*  fd -- file descriptor to watch
*  flags -- combination of EVENT_FLAG_READABLE and EVENT_FLAG_WRITEABLE
*  fn -- callback function to call when event is triggered
*  data -- extra data to pass to callback function
* %RETURNS:
*  A newly-allocated EventHandler, or NULL.
***********************************************************************/
EventHandler *
Event_AddHandler(EventSelector *es,
		 int fd,
		 unsigned int flags,
		 EventCallbackFunc fn,
		 void *data)
{
    EventHandler *eh;

    /* Specifically disable timer and deleted flags */
    flags &= (~(EVENT_TIMER_BITS | EVENT_FLAG_DELETED));

    /* Bad file descriptor */
    if (fd < 0) {
	errno = EBADF;
	return NULL;
    }

    eh = malloc(sizeof(EventHandler));
    if (!eh) return NULL;
    eh->fd = fd;
    eh->flags = flags;
    eh->tmout.tv_usec = 0;
    eh->tmout.tv_sec = 0;
    eh->fn = fn;
    eh->data = data;

    /* Add immediately.  This is safe even if we are in a handler. */
    eh->next = es->handlers;
    es->handlers = eh;

    EVENT_DEBUG(("Event_AddHandler(es=%p, fd=%d, flags=%u) -> %p\n", es, fd, flags, eh));
    return eh;
}

/**********************************************************************
* %FUNCTION: Event_AddHandlerWithTimeout
* %ARGUMENTS:
*  es -- event selector
*  fd -- file descriptor to watch
*  flags -- combination of EVENT_FLAG_READABLE and EVENT_FLAG_WRITEABLE
*  t -- Timeout after which to call handler, even if not readable/writable.
*       If t.tv_sec < 0, calls normal Event_AddHandler with no timeout.
*  fn -- callback function to call when event is triggered
*  data -- extra data to pass to callback function
* %RETURNS:
*  A newly-allocated EventHandler, or NULL.
***********************************************************************/
EventHandler *
Event_AddHandlerWithTimeout(EventSelector *es,
			    int fd,
			    unsigned int flags,
			    struct timeval t,
			    EventCallbackFunc fn,
			    void *data)
{
    EventHandler *eh;
    struct timeval now;

    /* If timeout is negative, just do normal non-timing-out event */
    if (t.tv_sec < 0 || t.tv_usec < 0) {
	return Event_AddHandler(es, fd, flags, fn, data);
    }

    /* Specifically disable timer and deleted flags */
    flags &= (~(EVENT_FLAG_TIMER | EVENT_FLAG_DELETED));
    flags |= EVENT_FLAG_TIMEOUT;

    /* Bad file descriptor? */
    if (fd < 0) {
	errno = EBADF;
	return NULL;
    }

    /* Bad timeout? */
    if (t.tv_usec >= 1000000) {
	errno = EINVAL;
	return NULL;
    }

    eh = malloc(sizeof(EventHandler));
    if (!eh) return NULL;

    /* Convert time interval to absolute time */
    gettimeofday(&now, NULL);

    t.tv_sec += now.tv_sec;
    t.tv_usec += now.tv_usec;
    if (t.tv_usec >= 1000000) {
	t.tv_usec -= 1000000;
	t.tv_sec++;
    }

    eh->fd = fd;
    eh->flags = flags;
    eh->tmout = t;
    eh->fn = fn;
    eh->data = data;

    /* Add immediately.  This is safe even if we are in a handler. */
    eh->next = es->handlers;
    es->handlers = eh;

    EVENT_DEBUG(("Event_AddHandlerWithTimeout(es=%p, fd=%d, flags=%u, t=%d/%d) -> %p\n", es, fd, flags, t.tv_sec, t.tv_usec, eh));
    return eh;
}


/**********************************************************************
* %FUNCTION: Event_AddTimerHandler
* %ARGUMENTS:
*  es -- event selector
*  t -- time interval after which to trigger event
*  fn -- callback function to call when event is triggered
*  data -- extra data to pass to callback function
* %RETURNS:
*  A newly-allocated EventHandler, or NULL.
***********************************************************************/
EventHandler *
Event_AddTimerHandler(EventSelector *es,
		      struct timeval t,
		      EventCallbackFunc fn,
		      void *data)
{
    EventHandler *eh;
    struct timeval now;

    /* Check time interval for validity */
    if (t.tv_sec < 0 || t.tv_usec < 0 || t.tv_usec >= 1000000) {
	errno = EINVAL;
	return NULL;
    }

    eh = malloc(sizeof(EventHandler));
    if (!eh) return NULL;

    /* Convert time interval to absolute time */
    gettimeofday(&now, NULL);

    t.tv_sec += now.tv_sec;
    t.tv_usec += now.tv_usec;
    if (t.tv_usec >= 1000000) {
	t.tv_usec -= 1000000;
	t.tv_sec++;
    }

    eh->fd = -1;
    eh->flags = EVENT_FLAG_TIMER;
    eh->tmout = t;
    eh->fn = fn;
    eh->data = data;

    /* Add immediately.  This is safe even if we are in a handler. */
    eh->next = es->handlers;
    es->handlers = eh;

    EVENT_DEBUG(("Event_AddTimerHandler(es=%p, t=%d/%d) -> %p\n", es, t.tv_sec,t.tv_usec, eh));
    return eh;
}

/**********************************************************************
* %FUNCTION: Event_DelHandler
* %ARGUMENTS:
*  es -- event selector
*  eh -- event handler
* %RETURNS:
*  0 if OK, non-zero if there is an error
* %DESCRIPTION:
*  Deletes the event handler eh
***********************************************************************/
int
Event_DelHandler(EventSelector *es,
		 EventHandler *eh)
{
    /* Scan the handlers list */
    EventHandler *cur, *prev;
    EVENT_DEBUG(("Event_DelHandler(es=%p, eh=%p)\n", es, eh));
    for (cur=es->handlers, prev=NULL; cur; prev=cur, cur=cur->next) {
	if (cur == eh) {
	    if (es->nestLevel) {
		eh->flags |= EVENT_FLAG_DELETED;
		es->opsPending = 1;
		return 0;
	    } else {
		if (prev) prev->next = cur->next;
		else      es->handlers = cur->next;

		DestroyHandler(cur);
		return 0;
	    }
	}
    }

    /* Handler not found */
    return 1;
}

/**********************************************************************
* %FUNCTION: DestroySelector
* %ARGUMENTS:
*  es -- an event selector
* %RETURNS:
*  Nothing
* %DESCRIPTION:
*  Destroys selector and all associated handles.
***********************************************************************/
void
DestroySelector(EventSelector *es)
{
    EventHandler *cur, *next;
    for (cur=es->handlers; cur; cur=next) {
	next = cur->next;
	DestroyHandler(cur);
    }

    free(es);
}

/**********************************************************************
* %FUNCTION: DestroyHandler
* %ARGUMENTS:
*  eh -- an event handler
* %RETURNS:
*  Nothing
* %DESCRIPTION:
*  Destroys handler
***********************************************************************/
void
DestroyHandler(EventHandler *eh)
{
    EVENT_DEBUG(("DestroyHandler(eh=%p)\n", eh));
    free(eh);
}

/**********************************************************************
* %FUNCTION: DoPendingChanges
* %ARGUMENTS:
*  es -- an event selector
* %RETURNS:
*  Nothing
* %DESCRIPTION:
*  Makes all pending insertions and deletions happen.
***********************************************************************/
void
DoPendingChanges(EventSelector *es)
{
    EventHandler *cur, *prev, *next;

    es->opsPending = 0;

    /* If selector is to be deleted, do it and skip everything else */
    if (es->destroyPending) {
	DestroySelector(es);
	return;
    }

    /* Do deletions */
    cur = es->handlers;
    prev = NULL;
    while(cur) {
	if (!(cur->flags & EVENT_FLAG_DELETED)) {
	    prev = cur;
	    cur = cur->next;
	    continue;
	}

	/* Unlink from list */
	if (prev) {
	    prev->next = cur->next;
	} else {
	    es->handlers = cur->next;
	}
	next = cur->next;
	DestroyHandler(cur);
	cur = next;
    }
}

/**********************************************************************
* %FUNCTION: Event_GetCallback
* %ARGUMENTS:
*  eh -- the event handler
* %RETURNS:
*  The callback function
***********************************************************************/
EventCallbackFunc
Event_GetCallback(EventHandler *eh)
{
    return eh->fn;
}

/**********************************************************************
* %FUNCTION: Event_GetData
* %ARGUMENTS:
*  eh -- the event handler
* %RETURNS:
*  The "data" field.
***********************************************************************/
void *
Event_GetData(EventHandler *eh)
{
    return eh->data;
}

/**********************************************************************
* %FUNCTION: Event_SetCallbackAndData
* %ARGUMENTS:
*  eh -- the event handler
*  fn -- new callback function
*  data -- new data value
* %RETURNS:
*  Nothing
* %DESCRIPTION:
*  Sets the callback function and data fields.
***********************************************************************/
void
Event_SetCallbackAndData(EventHandler *eh,
			 EventCallbackFunc fn,
			 void *data)
{
    eh->fn = fn;
    eh->data = data;
}

#ifdef DEBUG_EVENT
#include <stdarg.h>
#include <stdio.h>
FILE *Event_DebugFP = NULL;
/**********************************************************************
* %FUNCTION: Event_DebugMsg
* %ARGUMENTS:
*  fmt, ... -- format string
* %RETURNS:
*  Nothing
* %DESCRIPTION:
*  Writes a debug message to the debug file.
***********************************************************************/
void
Event_DebugMsg(char const *fmt, ...)
{
    va_list ap;
    struct timeval now;

    if (!Event_DebugFP) return;

    gettimeofday(&now, NULL);

    fprintf(Event_DebugFP, "%03d.%03d ", (int) now.tv_sec % 1000,
	    (int) now.tv_usec / 1000);

    va_start(ap, fmt);
    vfprintf(Event_DebugFP, fmt, ap);
    va_end(ap);
    fflush(Event_DebugFP);
}

#endif

/**********************************************************************
* %FUNCTION: Event_EnableDebugging
* %ARGUMENTS:
*  fname -- name of file to log debug messages to
* %RETURNS:
*  1 if debugging was enabled; 0 otherwise.
***********************************************************************/
int
Event_EnableDebugging(char const *fname)
{
#ifndef DEBUG_EVENT
    return 0;
#else
    Event_DebugFP = fopen(fname, "w");
    return (Event_DebugFP != NULL);
#endif
}

/**********************************************************************
* %FUNCTION: Event_ChangeTimeout
* %ARGUMENTS:
*  h -- event handler
*  t -- new timeout
* %RETURNS:
*  Nothing
* %DESCRIPTION:
*  Changes timeout of event handler to be "t" seconds in the future.
***********************************************************************/
void
Event_ChangeTimeout(EventHandler *h, struct timeval t)
{
    struct timeval now;

    /* Check time interval for validity */
    if (t.tv_sec < 0 || t.tv_usec < 0 || t.tv_usec >= 1000000) {
	return;
    }
    /* Convert time interval to absolute time */
    gettimeofday(&now, NULL);

    t.tv_sec += now.tv_sec;
    t.tv_usec += now.tv_usec;
    if (t.tv_usec >= 1000000) {
	t.tv_usec -= 1000000;
	t.tv_sec++;
    }

    h->tmout = t;
}
