/**
 * \file
 */

#if defined(HAVE_EPOLL)

#include <sys/epoll.h>

#if defined(HOST_WIN32)
/* We assume that epoll is not available on windows */
#error
#endif

#define EPOLL_NEVENTS 128

static gint epoll_fd;
static struct epoll_event *epoll_events;

static gboolean
epoll_init (gint wakeup_pipe_fd)
{
	struct epoll_event event;

#ifdef EPOOL_CLOEXEC
	epoll_fd = epoll_create1 (EPOLL_CLOEXEC);
#else
	epoll_fd = epoll_create (256);
	fcntl (epoll_fd, F_SETFD, FD_CLOEXEC);
#endif

	if (epoll_fd == -1) {
#ifdef EPOOL_CLOEXEC
		g_error ("epoll_init: epoll (EPOLL_CLOEXEC) failed, error (%d) %s\n", errno, g_strerror (errno));
#else
		g_error ("epoll_init: epoll (256) failed, error (%d) %s\n", errno, g_strerror (errno));
#endif
		return FALSE;
	}

	event.events = EPOLLIN;
	event.data.fd = wakeup_pipe_fd;
	if (epoll_ctl (epoll_fd, EPOLL_CTL_ADD, event.data.fd, &event) == -1) {
		g_error ("epoll_init: epoll_ctl () failed, error (%d) %s", errno, g_strerror (errno));
		close (epoll_fd);
		return FALSE;
	}

	epoll_events = g_new0 (struct epoll_event, EPOLL_NEVENTS);

	return TRUE;
}

static gboolean
epoll_can_register_fd (int fd)
{
	return TRUE;
}

static void
epoll_register_fd (gint fd, gint events, gboolean is_new)
{
	struct epoll_event event;

#ifndef EPOLLONESHOT
/* it was only defined on android in May 2013 */
#define EPOLLONESHOT 0x40000000
#endif

	event.data.fd = fd;
	event.events = EPOLLONESHOT;
	if ((events & EVENT_IN) != 0)
		event.events |= EPOLLIN;
	if ((events & EVENT_OUT) != 0)
		event.events |= EPOLLOUT;

	if (epoll_ctl (epoll_fd, is_new ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, event.data.fd, &event) == -1)
		g_error ("epoll_register_fd: epoll_ctl(%s) failed, error (%d) %s", is_new ? "EPOLL_CTL_ADD" : "EPOLL_CTL_MOD", errno, g_strerror (errno));
}

static void
epoll_remove_fd (gint fd)
{
	if (epoll_ctl (epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1)
			g_error ("epoll_remove_fd: epoll_ctl (EPOLL_CTL_DEL) failed, error (%d) %s", errno, g_strerror (errno));
}

static gint
epoll_event_wait (void (*callback) (gint fd, gint events, gpointer user_data), gpointer user_data)
{
	gint i, ready;

	memset (epoll_events, 0, sizeof (struct epoll_event) * EPOLL_NEVENTS);

	mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NO_GC);

	MONO_ENTER_GC_SAFE;
	ready = epoll_wait (epoll_fd, epoll_events, EPOLL_NEVENTS, -1);
	MONO_EXIT_GC_SAFE;

	mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NONE);

	if (ready == -1) {
		switch (errno) {
		case EINTR:
			ready = 0;
			break;
		default:
			g_error ("epoll_event_wait: epoll_wait () failed, error (%d) %s", errno, g_strerror (errno));
			break;
		}
	}

	if (ready == -1)
		return -1;

	for (i = 0; i < ready; ++i) {
		gint fd, events = 0;

		fd = epoll_events [i].data.fd;
		if (epoll_events [i].events & (EPOLLIN | EPOLLERR | EPOLLHUP))
			events |= EVENT_IN;
		if (epoll_events [i].events & (EPOLLOUT | EPOLLERR | EPOLLHUP))
			events |= EVENT_OUT;

		callback (fd, events, user_data);
	}

	return 0;
}

static ThreadPoolIOBackend backend_epoll = {
	epoll_init,
	epoll_can_register_fd,
	epoll_register_fd,
	epoll_remove_fd,
	epoll_event_wait,
};

#endif
