/**
 * @file main.c  Main polling routine
 *
 * Copyright (C) 2010 Creytiv.com
 */
#include <re_types.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <sys/types.h>
#undef _STRICT_ANSI
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef WIN32
#include <winsock.h>
#elif defined(__SYMBIAN32__)
#define bzero(b,len) memset(b,0,len)
#endif
#ifdef HAVE_SIGNAL
#include <signal.h>
#endif
#ifdef HAVE_SELECT_H
#include <sys/select.h>
#endif
#ifdef HAVE_POLL
#include <poll.h>
#endif
#ifdef HAVE_EPOLL
#include <sys/epoll.h>
#endif
#include <re_fmt.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_list.h>
#include <re_tmr.h>
#include <re_main.h>
#include "main.h"
#ifdef HAVE_PTHREAD
#define __USE_GNU 1
#include <stdlib.h>
#include <pthread.h>
#endif


#define DEBUG_MODULE "main"
#define DEBUG_LEVEL 5
#include <re_dbg.h>

/*
  epoll() has been tested successfully on the following kernels:

  - Linux 2.6.16.29-xen   (Debian 4.0 etch)
  - Linux 2.6.18-4-amd64  (Debian 4.0 etch)


  TODO clean this up

  - The polling method is selectable both in compile-time and run-time
  - The polling method can be changed in run time. this is cool!
  - Maximum number of fds can be set from application, but only once!
  - Look at howto optimise main loop
 */

#ifndef RELEASE
#define MAIN_DEBUG 1  /**< Enable main loop debugging */
#endif


enum {
	MAX_BLOCKING = 100,    /**< Maximum time spent in handler in [ms] */
#if defined (WIN32) || defined (CYGWIN)
	DEFAULT_MAXFDS = 8192
#else
	DEFAULT_MAXFDS = 128
#endif
};


/** Polling loop data */
struct re {
	/** File descriptor handler set */
	struct {
		int flags;           /**< Polling flags (Read, Write, etc.) */
		fd_h *fh;            /**< Event handler                     */
		void *arg;           /**< Handler argument                  */
	} *fhs;
	int maxfds;                  /**< Maximum number of polling fds     */
	int nfds;                    /**< Number of active file descriptors */
	enum poll_method method;     /**< The current polling method        */
	bool update;                 /**< File descriptor set need updating */
	bool polling;                /**< Is polling flag                   */
	int sig;                     /**< Last caught signal                */
	struct list tmrl;            /**< List of timers                    */

#ifdef HAVE_POLL
	struct pollfd *fds;          /**< Event set for poll()              */
#endif

#ifdef HAVE_EPOLL
	struct epoll_event *events;  /**< Event set for epoll()             */
	int epfd;                    /**< epoll control file descriptor     */
#endif

#ifdef HAVE_PTHREAD
	pthread_mutex_t mutex;       /**< Mutex for thread synchronization  */
	pthread_mutex_t *mutexp;     /**< Pointer to active mutex           */
#endif
};

static struct re global_re = {
	NULL,
	0,
	0,
	METHOD_NULL,
	false,
	false,
	0,
	LIST_INIT,
#ifdef HAVE_POLL
	NULL,
#endif
#ifdef HAVE_EPOLL
	NULL,
	-1,
#endif
#ifdef HAVE_PTHREAD
#if MAIN_DEBUG && defined (PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP)
	PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP,
#else
	PTHREAD_MUTEX_INITIALIZER,
#endif
#endif
};


#ifdef HAVE_PTHREAD

static void poll_close(struct re *re);

#ifdef SOLARIS
static pthread_once_t pt_once = {PTHREAD_ONCE_INIT};
#else
static pthread_once_t pt_once = PTHREAD_ONCE_INIT;
#endif
static pthread_key_t  pt_key;


static void thread_destructor(void *arg)
{
	poll_close(arg);
	free(arg);
}


static void re_once(void)
{
	pthread_key_create(&pt_key, thread_destructor);
}


static struct re *re_get(void)
{
	struct re *re;

	pthread_once(&pt_once, re_once);

	re = pthread_getspecific(pt_key);
	if (!re) {
		re = &global_re;
	}

	return re;
}


static inline void re_lock(struct re *re)
{
	int err;

	err = pthread_mutex_lock(re->mutexp);
	if (err) {
		DEBUG_WARNING("re_lock: %m\n", err);
	}
}


static inline void re_unlock(struct re *re)
{
	int err;

	err = pthread_mutex_unlock(re->mutexp);
	if (err) {
		DEBUG_WARNING("re_unlock: %m\n", err);
	}
}


#else

static struct re *re_get(void)
{
	return &global_re;
}

#define re_lock(x)    /**< Stub */
#define re_unlock(x)  /**< Stub */

#endif


#if MAIN_DEBUG
/**
 * Call the application event handler
 *
 * @param re     Poll state
 * @param fd     File descriptor
 * @param flags  Event flags
 */
static void fd_handler(struct re *re, int fd, int flags)
{
	const uint64_t tick = tmr_jiffies();
	uint32_t diff;

	DEBUG_INFO("event on fd=%d (flags=0x%02x)...\n", fd, flags);

	re->fhs[fd].fh(flags, re->fhs[fd].arg);

	diff = (uint32_t)(tmr_jiffies() - tick);

	if (diff > MAX_BLOCKING) {
		DEBUG_WARNING("long async blocking: %u>%u ms (h=%p arg=%p)\n",
			      diff, MAX_BLOCKING,
			      re->fhs[fd].fh, re->fhs[fd].arg);
	}
}
#endif


#ifdef HAVE_POLL
static int set_poll_fds(struct re *re, int fd, int flags)
{
	if (!re->fds)
		return 0;

	if (flags)
		re->fds[fd].fd = fd;
	else
		re->fds[fd].fd = -1;

	re->fds[fd].events = 0;
	if (flags & FD_READ)
		re->fds[fd].events |= POLLIN;
	if (flags & FD_WRITE)
		re->fds[fd].events |= POLLOUT;
	if (flags & FD_EXCEPT)
		re->fds[fd].events |= POLLERR;

	return 0;
}
#endif


#ifdef HAVE_EPOLL
static int set_epoll_fds(struct re *re, int fd, int flags)
{
	struct epoll_event event;
	int err = 0;

	if (re->epfd <= 0)
		return EBADFD;

	memset(&event, 0, sizeof(event));

	DEBUG_INFO("set_epoll_fds: fd=%d flags=0x%02x\n", fd, flags);

	if (flags) {
		event.data.fd = fd;

		if (flags & FD_READ)
			event.events |= EPOLLIN;
		if (flags & FD_WRITE)
			event.events |= EPOLLOUT;
		if (flags & FD_EXCEPT)
			event.events |= EPOLLERR;

		/* Try to add it first */
		if (-1 == epoll_ctl(re->epfd, EPOLL_CTL_ADD, fd, &event)) {

			/* If already exist then modify it */
			if (EEXIST == errno) {

				if (-1 == epoll_ctl(re->epfd, EPOLL_CTL_MOD,
						    fd, &event)) {
					err = errno;
					DEBUG_WARNING("epoll_ctl:"
						      " EPOLL_CTL_MOD:"
						      " fd=%d (%m)\n",
						      fd, err);
				}
			}
			else {
				err = errno;
				DEBUG_WARNING("epoll_ctl: EPOLL_CTL_ADD:"
					      " fd=%d (%m)\n",
					      fd, err);
			}
		}
	}
	else {
		if (-1 == epoll_ctl(re->epfd, EPOLL_CTL_DEL, fd, &event)) {
			err = errno;
			DEBUG_INFO("epoll_ctl: EPOLL_CTL_DEL: fd=%d (%m)\n",
				   fd, err);
		}
	}

	return err;
}
#endif


/**
 * Rebuild the file descriptor mapping table. This must be done whenever
 * the polling method is changed.
 */
static int rebuild_fds(struct re *re)
{
	int i, err = 0;

	DEBUG_INFO("rebuilding fds (nfds=%d)\n", re->nfds);

	/* Update fd sets */
	for (i=0; i<re->nfds; i++) {
		if (!re->fhs[i].fh)
			continue;

		switch (re->method) {

#ifdef HAVE_POLL
		case METHOD_POLL:
			err = set_poll_fds(re, i, re->fhs[i].flags);
			break;
#endif
#ifdef HAVE_EPOLL
		case METHOD_EPOLL:
			err = set_epoll_fds(re, i, re->fhs[i].flags);
			break;
#endif
		default:
			break;
		}
	}

	return err;
}


static int poll_init(struct re *re)
{
	DEBUG_INFO("poll init (maxfds=%d)\n", re->maxfds);

	if (!re->maxfds) {
		DEBUG_WARNING("poll init: maxfds is 0\n");
		return EINVAL;
	}

	switch (re->method) {

#ifdef HAVE_POLL
	case METHOD_POLL:
		if (!re->fds) {
			re->fds = mem_zalloc(re->maxfds * sizeof(*re->fds),
					    NULL);
			if (!re->fds)
				return ENOMEM;
		}
		break;
#endif
#ifdef HAVE_EPOLL
	case METHOD_EPOLL:
		if (!re->events) {
			DEBUG_INFO("allocate %u bytes for epoll set\n",
				   re->maxfds * sizeof(*re->events));
			re->events = mem_zalloc(re->maxfds*sizeof(*re->events),
					      NULL);
			if (!re->events)
				return ENOMEM;
		}

		if (re->epfd <= 0
		    && -1 == (re->epfd = epoll_create(re->maxfds))) {
			DEBUG_WARNING("epoll_create: %m (maxfds=%d)\n",
				      errno, re->maxfds);
			return errno;
		}
		DEBUG_INFO("init: epoll_create() epfd=%d\n", re->epfd);
		break;
#endif
	default:
		break;
	}
	return 0;
}


/** Free all resources */
static void poll_close(struct re *re)
{
	DEBUG_INFO("poll close\n");

	re->fhs = mem_deref(re->fhs);
	re->maxfds = 0;

#ifdef HAVE_POLL
	re->fds = mem_deref(re->fds);
#endif
#ifdef HAVE_EPOLL
	DEBUG_INFO("poll_close: epfd=%d\n", re->epfd);

	if (re->epfd > 0) {
		(void)close(re->epfd);
		re->epfd = -1;
	}

	re->events = mem_deref(re->events);
#endif
}


static int poll_setup(struct re *re)
{
	int err;

	err = fd_setsize(DEFAULT_MAXFDS);
	if (err)
		goto out;

	if (METHOD_NULL == re->method) {
		err = poll_method_set(poll_method_best());
		if (err)
			goto out;

		DEBUG_INFO("poll setup: poll method not set - set to `%s'\n",
			   poll_method_name(re->method));
	}

	err = poll_init(re);

 out:
	if (err)
		poll_close(re);

	return err;
}


/**
 * Listen for events on a file descriptor
 *
 * @param fd     File descriptor
 * @param flags  Wanted event flags
 * @param fh     Event handler
 * @param arg    Handler argument
 *
 * @return 0 if success, otherwise errorcode
 */
int fd_listen(int fd, int flags, fd_h *fh, void *arg)
{
	struct re *re = re_get();
	int err = 0;

	DEBUG_INFO("fd_listen: fd=%d flags=0x%02x\n", fd, flags);

	if (fd < 0) {
		DEBUG_WARNING("fd_listen: corrupt fd %d\n", fd);
		return EBADF;
	}

	if (flags || fh) {
		err = poll_setup(re);
		if (err)
			return err;
	}

	if (fd >= re->maxfds) {
		if (flags) {
			DEBUG_WARNING("fd_listen: fd=%d flags=0x%02x"
				      " - Max %d fds\n",
				      fd, flags, re->maxfds);
		}
		return EMFILE;
	}

	/* Update fh set */
	if (re->fhs) {
		re->fhs[fd].flags = flags;
		re->fhs[fd].fh    = fh;
		re->fhs[fd].arg   = arg;
	}

	re->nfds = max(re->nfds, fd+1);

	switch (re->method) {

#ifdef HAVE_POLL
	case METHOD_POLL:
		err = set_poll_fds(re, fd, flags);
		break;
#endif

#ifdef HAVE_EPOLL
	case METHOD_EPOLL:
		if (re->epfd <= 0)
			return EBADFD;
		err = set_epoll_fds(re, fd, flags);
		break;
#endif
	default:
		break;
	}

	if (err) {
		if (flags && fh) {
			fd_close(fd);
			DEBUG_WARNING("fd_listen: fd=%d flags=0x%02x (%m)\n",
				      fd, flags, err);
		}
	}

	return err;
}


/**
 * Stop listening for events on a file descriptor
 *
 * @param fd     File descriptor
 */
void fd_close(int fd)
{
	(void)fd_listen(fd, 0, NULL, NULL);
}


/**
 * Polling loop
 *
 * @param re Poll state.
 *
 * @return 0 if success, otherwise errorcode
 */
static int fd_poll(struct re *re)
{
	uint64_t to = tmr_next_timeout(&re->tmrl);
	int i, n;
#ifdef HAVE_SELECT
	fd_set rfds, wfds, efds;
#endif

	DEBUG_INFO("next timer: %llu ms\n", to);

	// limiting poll timeout to call control queue handler in a timely manner
	if (to > 100) {
    	to = 100;
	}

	/* Wait for I/O */
	switch (re->method) {

#ifdef HAVE_POLL
	case METHOD_POLL:
		re_unlock(re);
		n = poll(re->fds, re->nfds, to ? (int)to : -1);
		re_lock(re);
		break;
#endif
#ifdef HAVE_SELECT
	case METHOD_SELECT: {
		struct timeval tv;

		/* Clear and update fd sets */
		FD_ZERO(&rfds);
		FD_ZERO(&wfds);
		FD_ZERO(&efds);

		for (i=0; i<re->nfds; i++) {
			if (!re->fhs[i].fh)
				continue;

			if (re->fhs[i].flags & FD_READ)
				FD_SET(i, &rfds);
			if (re->fhs[i].flags & FD_WRITE)
				FD_SET(i, &wfds);
			if (re->fhs[i].flags & FD_EXCEPT)
				FD_SET(i, &efds);
		}

#ifdef WIN32
		tv.tv_sec  = (long) to / 1000;
#else
		tv.tv_sec  = (time_t) to / 1000;
#endif
		tv.tv_usec = (uint32_t) (to % 1000) * 1000;
		re_unlock(re);
		n = select(re->nfds, &rfds, &wfds, &efds, to ? &tv : NULL);
		re_lock(re);
	}
		break;
#endif
#ifdef HAVE_EPOLL
	case METHOD_EPOLL:
		re_unlock(re);
		n = epoll_wait(re->epfd, re->events, re->maxfds,
			       to ? (int)to : -1);
		re_lock(re);
		break;
#endif

	default:
		(void)to;
		DEBUG_WARNING("no polling method set\n");
		return EINVAL;
	}

	if (n < 0)
		return errno;

	/* Check for events */
	for (i=0; (n > 0) && (i < re->nfds); i++) {
		int fd, flags = 0;

		switch (re->method) {

#ifdef HAVE_POLL
		case METHOD_POLL:
			fd = i;
			if (re->fds[fd].revents & POLLIN)
				flags |= FD_READ;
			if (re->fds[fd].revents & POLLOUT)
				flags |= FD_WRITE;
			if (re->fds[fd].revents & (POLLERR|POLLHUP|POLLNVAL))
				flags |= FD_EXCEPT;
			if (re->fds[fd].revents & POLLNVAL) {
				DEBUG_WARNING("event: fd=%d POLLNVAL"
					      " (fds.fd=%d,"
					      " fds.events=0x%02x)\n",
					      fd, re->fds[fd].fd,
					      re->fds[fd].events);
			}
			/* Clear events */
			re->fds[fd].revents = 0;
			break;
#endif
#ifdef HAVE_SELECT
		case METHOD_SELECT:
			fd = i;
			if (FD_ISSET(fd, &rfds))
				flags |= FD_READ;
			if (FD_ISSET(fd, &wfds))
				flags |= FD_WRITE;
			if (FD_ISSET(fd, &efds))
				flags |= FD_EXCEPT;
			break;
#endif
#ifdef HAVE_EPOLL
		case METHOD_EPOLL:
			fd = re->events[i].data.fd;

			if (re->events[i].events & EPOLLIN)
				flags |= FD_READ;
			if (re->events[i].events & EPOLLOUT)
				flags |= FD_WRITE;
			if (re->events[i].events & EPOLLERR)
				flags |= FD_EXCEPT;

			if (!flags) {
				DEBUG_WARNING("epoll: no flags fd=%d\n", fd);
			}

			break;
#endif
		default:
			return EINVAL;
		}

		if (!flags)
			continue;

		if (re->fhs[fd].fh) {
#if MAIN_DEBUG
			fd_handler(re, fd, flags);
#else
			re->fhs[fd].fh(flags, re->fhs[fd].arg);
#endif
		}

		/* Check if polling method was changed */
		if (re->update) {
			re->update = false;
			return 0;
		}

		--n;
	}

	return 0;
}


/**
 * Set the maximum number of file descriptors
 *
 * @param maxfds Max FDs. 0 to free.
 *
 * @return 0 if success, otherwise errorcode
 */
int fd_setsize(int maxfds)
{
	struct re *re = re_get();

	if (!maxfds) {
		fd_debug();
		poll_close(re);
		return 0;
	}

	if (!re->maxfds)
		re->maxfds = maxfds;

	if (!re->fhs) {
		DEBUG_INFO("fd_setsize: maxfds=%d, allocating %u bytes\n",
			   re->maxfds, re->maxfds * sizeof(*re->fhs));

		re->fhs = mem_zalloc(re->maxfds * sizeof(*re->fhs), NULL);
		if (!re->fhs)
			return ENOMEM;
	}

	return 0;
}


/**
 * Print all file descriptors in-use
 */
void fd_debug(void)
{
	const struct re *re = re_get();
	int i;

	if (!re->fhs)
		return;

	for (i=0; i<re->nfds; i++) {

		if (!re->fhs[i].flags)
			continue;

		(void)re_printf(
				 "fd %d in use: flags=%x fh=%p arg=%p\n",
				 i, re->fhs[i].flags, re->fhs[i].fh,
				 re->fhs[i].arg);
	}
}


#ifdef HAVE_SIGNAL
/* Thread-safe signal handling */
static void signal_handler(int sig)
{
	(void)signal(sig, signal_handler);
	re_get()->sig = sig;
}
#endif


/**
 * Main polling loop for async I/O events. This function will only return when
 * re_cancel() is called or an error occured.
 *
 * @param signalh Optional Signal handler
 *
 * @return 0 if success, otherwise errorcode
 */
int re_main(re_signal_h *signalh, control_poll_callback_h *controlh)
{
	struct re *re = re_get();
	int err;

#ifdef HAVE_SIGNAL
	if (signalh) {
		(void)signal(SIGINT, signal_handler);
		(void)signal(SIGALRM, signal_handler);
		(void)signal(SIGTERM, signal_handler);
	}
#endif

	if (re->polling) {
		DEBUG_WARNING("main loop already polling\n");
		return EALREADY;
	}

	err = poll_setup(re);
	if (err)
		goto out;

	DEBUG_INFO("Using async I/O polling method: `%s'\n",
		   poll_method_name(re->method));

	re->polling = true;

#ifdef HAVE_ACTSCHED
	if (METHOD_ACTSCHED == re->method) {
		err = actsched_start();
		goto out;
	}
#endif

	re_lock(re);
	for (;;) {

		if (re->sig) {
			if (signalh)
				signalh(re->sig);

			re->sig = 0;
		}

		if (!re->polling) {
			err = 0;
			break;
		}

		err = fd_poll(re);
		if (err) {
			if (EINTR == err)
				continue;

#ifdef DARWIN
			/* NOTE: workaround for Darwin */
			if (EBADF == err)
				continue;
#endif

			break;
		}

		tmr_poll(&re->tmrl);

		if (controlh) {
        	controlh();
		}
	}
	re_unlock(re);

 out:
	re->polling = false;

	return err;
}


/**
 * Cancel the main polling loop
 */
void re_cancel(void)
{
	struct re *re = re_get();

	re->polling = false;

#ifdef HAVE_ACTSCHED
	if (METHOD_ACTSCHED == re->method) {
		actsched_stop();
	}
#endif
}


/**
 * Debug the main polling loop
 *
 * @param pf     Print handler where debug output is printed to
 * @param unused Unused parameter
 *
 * @return 0 if success, otherwise errorcode
 */
int re_debug(struct re_printf *pf, void *unused)
{
	struct re *re = re_get();
	int err = 0;

	(void)unused;

	err |= re_hprintf(pf, "re main loop:\n");
	err |= re_hprintf(pf, "  maxfds:  %d\n", re->maxfds);
	err |= re_hprintf(pf, "  nfds:    %d\n", re->nfds);
	err |= re_hprintf(pf, "  method:  %d (%s)\n", re->method,
			  poll_method_name(re->method));

	return err;
}


/**
 * Set async I/O polling method. This function can also be called while the
 * program is running.
 *
 * @param method New polling method
 *
 * @return 0 if success, otherwise errorcode
 */
int poll_method_set(enum poll_method method)
{
	struct re *re;
	int err;

	switch (method) {

#ifdef HAVE_POLL
	case METHOD_POLL:
		break;
#endif
#ifdef HAVE_SELECT
	case METHOD_SELECT:
		break;
#endif
#ifdef HAVE_EPOLL
	case METHOD_EPOLL:
		if (!epoll_check())
			return EINVAL;
		break;
#endif
#ifdef HAVE_ACTSCHED
	case METHOD_ACTSCHED:
		break;
#endif
	default:
		DEBUG_WARNING("poll method not supported: '%s'\n",
			      poll_method_name(method));
		return EINVAL;
	}

	re = re_get();
	re->method = method;
	re->update = true;

	DEBUG_INFO("Setting async I/O polling method to `%s'\n",
		   poll_method_name(re->method));

	err = fd_setsize(DEFAULT_MAXFDS);
	if (err)
		return err;

	err = poll_init(re);
	if (err)
		return err;

	return rebuild_fds(re);
}


/**
 * Add a worker thread for this thread
 *
 * @return 0 if success, otherwise errorcode
 */
int re_thread_init(void)
{
#ifdef HAVE_PTHREAD
	struct re *re;

	pthread_once(&pt_once, re_once);

	re = pthread_getspecific(pt_key);
	if (re) {
		DEBUG_WARNING("thread_init: already added for thread %d\n",
			      pthread_self());
		return EALREADY;
	}

	re = malloc(sizeof(*re));
	if (!re)
		return ENOMEM;

	memset(re, 0, sizeof(*re));
	pthread_mutex_init(&re->mutex, NULL);
	re->mutexp = &re->mutex;

#ifdef HAVE_EPOLL
	re->epfd = -1;
#endif

	pthread_setspecific(pt_key, re);
	return 0;
#else
	return ENOSYS;
#endif
}


/**
 * Remove the worker thread for this thread
 */
void re_thread_close(void)
{
#ifdef HAVE_PTHREAD
	struct re *re;

	pthread_once(&pt_once, re_once);

	re = pthread_getspecific(pt_key);
	if (re) {
		free(re);
		pthread_setspecific(pt_key, NULL);
	}
#endif
}


/**
 * Enter an 're' thread
 *
 * @note Must only be called from a non-re thread
 */
void re_thread_enter(void)
{
	re_lock(re_get());
}


/**
 * Leave an 're' thread
 *
 * @note Must only be called from a non-re thread
 */
void re_thread_leave(void)
{
	re_unlock(re_get());
}


/**
 * Set an external mutex for this thread
 *
 * @param mutexp Pointer to external mutex, NULL to use internal
 */
void re_set_mutex(void *mutexp)
{
#ifdef HAVE_PTHREAD
	struct re *re = re_get();

	re->mutexp = mutexp ? mutexp : &re->mutex;
#else
	(void)mutexp;
#endif
}


/**
 * Get the timer-list for this thread
 *
 * @return Timer list
 *
 * @note only used by tmr module
 */
struct list *tmrl_get(void);
struct list *tmrl_get(void)
{
	return &re_get()->tmrl;
}
