/*
 * POSIX library for Lua 5.1, 5.2 & 5.3.
 * (c) Gary V. Vaughan <gary@vaughan.pe>, 2013-2015
 * (c) Reuben Thomas <rrt@sc3d.org> 2010-2013
 * (c) Natanael Copa <natanael.copa@gmail.com> 2008-2010
 * Clean up and bug fixes by Leo Razoumov <slonik.az@gmail.com> 2006-10-11
 * Luiz Henrique de Figueiredo <lhf@tecgraf.puc-rio.br> 07 Apr 2006 23:17:49
 * Based on original by Claudio Terra for Lua 3.x.
 * With contributions by Roberto Ierusalimschy.
 * With documentation from Steve Donovan 2012
 */
/***
 Synchronous I/O Multiplexing.

 Examine file descriptors for events, such as readyness for I/O.

@module posix.poll
*/

#include <config.h>

#include <poll.h>

#include "_helpers.c"


static struct {
	short       bit;
	const char *name;
} poll_event_map[] = {
#define MAP(_NAME) \
	{POLL##_NAME, #_NAME}
	MAP(IN),
	MAP(PRI),
	MAP(OUT),
	MAP(ERR),
	MAP(HUP),
	MAP(NVAL),
#undef MAP
};

#define PPOLL_EVENT_NUM (sizeof(poll_event_map) / sizeof(*poll_event_map))

static void
poll_events_createtable(lua_State *L)
{
	lua_createtable(L, 0, PPOLL_EVENT_NUM);
}

static short
poll_events_from_table(lua_State *L, int table)
{
	short events = 0;
	size_t i;

	/* Convert to absolute index */
	if (table < 0)
		table = lua_gettop(L) + table + 1;

	for (i = 0; i < PPOLL_EVENT_NUM; i++)
	{
		lua_getfield(L, table, poll_event_map[i].name);
		if (lua_toboolean(L, -1))
			events |= poll_event_map[i].bit;
		lua_pop(L, 1);
	}

	return events;
}

static void
poll_events_to_table(lua_State *L, int table, short events)
{
	size_t i;

	/* Convert to absolute index */
	if (table < 0)
		table = lua_gettop(L) + table + 1;

	for (i = 0; i < PPOLL_EVENT_NUM; i++)
	{
		lua_pushboolean(L, events & poll_event_map[i].bit);
		lua_setfield(L, table, poll_event_map[i].name);
	}
}

static nfds_t
poll_fd_list_check_table(lua_State *L, int table)
{
	nfds_t fd_num = 0;

	/*
	 * Assume table is an argument number.
	 * Should be an assert(table > 0).
	 */

	luaL_checktype(L, table, LUA_TTABLE);

	/* Nil key - the one before first */
	lua_pushnil(L);

	/* Push each key/value pair, popping previous key */
	while (lua_next(L, 1) != 0)
	{
		/* Verify the fd key */
		luaL_argcheck(L, lua_isinteger(L, -2), table,
					  "contains non-integer key(s)");

		/* Verify the table value */
		luaL_argcheck(L, lua_istable(L, -1), table,
					  "contains non-table value(s)");
		lua_getfield(L, -1, "events");
		luaL_argcheck(L, lua_istable(L, -1), table,
					  "contains invalid value table(s)");
		lua_pop(L, 1);
		lua_getfield(L, -1, "revents");
		luaL_argcheck(L, lua_isnil(L, -1) || lua_istable(L, -1), table,
					  "contains invalid value table(s)");
		lua_pop(L, 1);

		/* Remove value (but leave the key) */
		lua_pop(L, 1);

		/* Count the fds */
		fd_num++;
	}

	return fd_num;
}

static void
poll_fd_list_from_table(lua_State *L, int table, struct pollfd *fd_list)
{
	struct pollfd *pollfd = fd_list;

	/*
	 * Assume the table didn't change since
	 * the call to poll_fd_list_check_table
	 */

	/* Convert to absolute index */
	if (table < 0)
		table = lua_gettop(L) + table + 1;

	/* Nil key - the one before first */
	lua_pushnil(L);

	/* Push each key/value pair, popping previous key */
	while (lua_next(L, table) != 0)
	{
		/* Transfer the fd key */
		pollfd->fd = lua_tointeger(L, -2);

		/* Transfer "events" field from the value */
		lua_getfield(L, -1, "events");
		pollfd->events = poll_events_from_table(L, -1);
		lua_pop(L, 1);

		/* Remove value (but leave the key) */
		lua_pop(L, 1);

		/* Proceed to next fd */
		pollfd++;
	}
}

static void
poll_fd_list_to_table(lua_State *L, int table, const struct pollfd *fd_list)
{
	const struct pollfd *pollfd = fd_list;

	/*
	 * Assume the table didn't change since
	 * the call to poll_fd_list_check_table.
	 */

	/* Convert to absolute index */
	if (table < 0)
		table = lua_gettop(L) + table + 1;

	/* Nil key - the one before first */
	lua_pushnil(L);

	/* Push each key/value pair, popping previous key */
	while (lua_next(L, 1) != 0)
	{
		/* Transfer "revents" field to the value */
		lua_getfield(L, -1, "revents");
		if (lua_isnil(L, -1))
		{
			lua_pop(L, 1);
			poll_events_createtable(L);
			lua_pushvalue(L, -1);
			lua_setfield(L, -3, "revents");
		}
		poll_events_to_table(L, -1, pollfd->revents);
		lua_pop(L, 1);

		/* Remove value (but leave the key) */
		lua_pop(L, 1);

		/* Proceed to next fd */
		pollfd++;
	}
}


/***
Wait for events on multiple file descriptors.
@function poll
@tparam table fds list of file descriptors
@int[opt=-1] timeout maximum timeout in milliseconds, or -1 to block indefinitely
@treturn[1] int 0 if timed out, 1 if *fd* is ready
@return[2] nil
@treturn[2] string error message
@treturn[2] int errnum
@see poll(2)
@see rpoll
@see poll.lua
*/
static int
Ppoll(lua_State *L)
{
	struct pollfd *fd_list, static_fd_list[16];
	nfds_t fd_num = poll_fd_list_check_table(L, 1);
	int r, timeout = optint(L, 2, -1);
	checknargs(L, 2);

	fd_list = (fd_num <= sizeof(static_fd_list) / sizeof(*static_fd_list))
					? static_fd_list
					: lua_newuserdata(L, sizeof(*fd_list) * fd_num);


	poll_fd_list_from_table(L, 1, fd_list);

	r = poll(fd_list, fd_num, timeout);

	/* If any of the descriptors changed state */
	if (r > 0)
		poll_fd_list_to_table(L, 1, fd_list);

	return pushresult(L, r, NULL);
}


/***
Wait for some event on a file descriptor.
Based on [http://lua-users.org/lists/lua-l/2007-11/msg00346.html]().
@function rpoll
@int fd file descriptor
@int[opt=-1] timeout maximum timeout in milliseconds, or -1 to block indefinitely
@treturn[1] int 0 if timed out, 1 if *fd* is ready
@return[2] nil
@return[2] error message
@treturn[2] int errnum
@see poll
@usage
fh = io.open "one"
while true do
  r = rpoll (fh, 500)
  if r == 0 then
    print 'timeout'
  elseif r == 1 then
    print (fh:read ())
  else
    print "finish!"
    break
  end
end
*/
static int
Prpoll(lua_State *L)
{
	struct pollfd fds;
	int file = checkint(L, 1);
	int timeout = checkint(L, 2);
	checknargs(L, 2);
	fds.fd = file;
	fds.events = POLLIN;
	return pushresult(L, poll(&fds, 1, timeout), NULL);
}


static const luaL_Reg posix_poll_fns[] =
{
	LPOSIX_FUNC( Ppoll		),
	LPOSIX_FUNC( Prpoll		),
	{NULL, NULL}
};


LUALIB_API int
luaopen_posix_poll(lua_State *L)
{
	luaL_register(L, "posix.poll", posix_poll_fns);
	lua_pushliteral(L, "posix.poll for " LUA_VERSION " / " PACKAGE_STRING);
	lua_setfield(L, -2, "version");

	return 1;
}
