lib_uloop.c

/*
 * Copyright (C) 2022 Jo-Philipp Wich <jo@mein.io>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/**
 * # OpenWrt uloop event loop
 *
 * The `uloop` binding provides functions for integrating with the OpenWrt
 * {@link https://github.com/openwrt/libubox/blob/master/uloop.h uloop library}.
 *
 * Functions can be individually imported and directly accessed using the
 * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import}
 * syntax:
 *
 *   ```javascript
 *   import { init, handle, timer, interval, process, signal, task, run } from 'uloop';
 *
 *   init();
 *
 *   handle(…);
 *   timer(…);
 *   interval(…);
 *   process(…);
 *   signal(…);
 *   task(…);
 *
 *   run();
 *   ```
 *
 * Alternatively, the module namespace can be imported using a wildcard import
 * statement:
 *
 *   ```javascript
 *   import * as uloop from 'uloop';
 *
 *   uloop.init();
 *
 *   uloop.handle(…);
 *   uloop.timer(…);
 *   uloop.interval(…);
 *   uloop.process(…);
 *   uloop.signal(…);
 *   uloop.task(…);
 *
 *   uloop.run();
 *   ```
 *
 * Additionally, the uloop binding namespace may also be imported by invoking
 * the `ucode` interpreter with the `-luloop` switch.
 *
 * @module uloop
 */

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>

#include <libubox/uloop.h>

#include "ucode/module.h"
#include "ucode/platform.h"

#define ok_return(expr) do { last_error = 0; return (expr); } while(0)
#define err_return(err) do { last_error = err; return NULL; } while(0)

static uc_resource_type_t *timer_type, *handle_type, *process_type, *task_type, *pipe_type;

#ifdef HAVE_ULOOP_INTERVAL
static uc_resource_type_t *interval_type;
#endif

#ifdef HAVE_ULOOP_SIGNAL
static uc_resource_type_t *signal_type;
#endif

static uc_value_t *object_registry;

static int last_error = 0;

static size_t
uc_uloop_reg_add(uc_value_t *obj, uc_value_t *cb)
{
	size_t i = 0;

	while (ucv_array_get(object_registry, i))
		i += 2;

	ucv_array_set(object_registry, i + 0, ucv_get(obj));
	ucv_array_set(object_registry, i + 1, ucv_get(cb));

	return i;
}

static bool
uc_uloop_reg_remove(size_t i)
{
	if (i + 1 >= ucv_array_length(object_registry))
		return false;

	ucv_array_set(object_registry, i + 0, NULL);
	ucv_array_set(object_registry, i + 1, NULL);

	return true;
}

static bool
uc_uloop_reg_invoke(uc_vm_t *vm, size_t i, uc_value_t *arg)
{
	uc_value_t *obj = ucv_array_get(object_registry, i + 0);
	uc_value_t *cb = ucv_array_get(object_registry, i + 1);

	if (!ucv_is_callable(cb))
		return false;

	uc_vm_stack_push(vm, ucv_get(obj));
	uc_vm_stack_push(vm, ucv_get(cb));
	uc_vm_stack_push(vm, ucv_get(arg));

	if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE) {
		uloop_end();

		return false;
	}

	ucv_put(uc_vm_stack_pop(vm));

	return true;
}

/**
 * Retrieves the last error message.
 *
 * This function retrieves the last error message generated by the uloop event loop.
 * If no error occurred, it returns `null`.
 *
 * @function module:uloop#error
 *
 * @returns {?string}
 * Returns the last error message as a string, or `null` if no error occurred.
 *
 * @example
 * // Retrieve the last error message
 * const errorMessage = uloop.error();
 *
 * if (errorMessage)
 *     printf(`Error message: ${errorMessage}\n`);
 * else
 *     printf("No error occurred\n");
 */
static uc_value_t *
uc_uloop_error(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *errmsg;

	if (last_error == 0)
		return NULL;

	errmsg = ucv_string_new(strerror(last_error));
	last_error = 0;

	return errmsg;
}

/**
 * Initializes the uloop event loop.
 *
 * This function initializes the uloop event loop, allowing subsequent
 * usage of uloop functionalities. It takes no arguments.
 *
 * Returns `true` on success.
 * Returns `null` if an error occurred during initialization.
 *
 * @function module:uloop#init
 *
 * @returns {?boolean}
 * Returns `true` on success, `null` on error.
 *
 * @example
 * // Initialize the uloop event loop
 * const success = uloop.init();
 *
 * if (success)
 *     printf("uloop event loop initialized successfully\n");
 * else
 *     die(`Initialization failure: ${uloop.error()}\n`);
 */
static uc_value_t *
uc_uloop_init(uc_vm_t *vm, size_t nargs)
{
	int rv = uloop_init();

	if (rv == -1)
		err_return(errno);

	ok_return(ucv_boolean_new(true));
}

/**
 * Runs the uloop event loop.
 *
 * This function starts running the uloop event loop, allowing it to handle
 * scheduled events and callbacks. If a timeout value is provided and is
 * non-negative, the event loop will run for that amount of milliseconds
 * before returning. If the timeout is omitted or negative, the event loop
 * runs indefinitely until explicitly stopped.
 *
 * @function module:uloop#run
 *
 * @param {number} [timeout=-1]
 * Optional. The timeout value in milliseconds for running the event loop.
 * Defaults to -1, indicating an indefinite run.
 *
 * @returns {?boolean}
 * Returns `true` on success, `null` on error.
 *
 * @example
 * // Run the uloop event loop indefinitely
 * const success = uloop.run();
 * if (success)
 *     printf("uloop event loop ran successfully\n");
 * else
 *     die(`Error occurred during uloop execution: ${uloop.error()}\n`);
 *
 * // Run the uloop event loop for 1000 milliseconds
 * const success = uloop.run(1000);
 * if (success)
 *     printf("uloop event loop ran successfully\n");
 * else
 *     die(`Error occurred during uloop execution: ${uloop.error()}\n`);
 */
static uc_value_t *
uc_uloop_run(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *timeout = uc_fn_arg(0);
	int t, rv;

	errno = 0;
	t = timeout ? (int)ucv_int64_get(timeout) : -1;

	if (errno)
		err_return(errno);

	rv = uloop_run_timeout(t);

	ok_return(ucv_int64_new(rv));
}

/**
 * Checks if the uloop event loop is currently shutting down.
 *
 * This function checks whether the uloop event loop is currently in the process
 * of shutting down.
 *
 * @function module:uloop#cancelling
 *
 * @returns {boolean}
 * Returns `true` if uloop is currently shutting down, `false` otherwise.
 *
 * @example
 * // Check if the uloop event loop is shutting down
 * const shuttingDown = uloop.cancelling();
 * if (shuttingDown)
 *     printf("uloop event loop is currently shutting down\n");
 * else
 *     printf("uloop event loop is not shutting down\n");
 */
static uc_value_t *
uc_uloop_cancelling(uc_vm_t *vm, size_t nargs)
{
	ok_return(ucv_boolean_new(uloop_cancelling()));
}

/**
 * Checks if the uloop event loop is currently running.
 *
 * This function checks whether the uloop event loop is currently started
 * and running.
 *
 * @function module:uloop#running
 *
 * @returns {boolean}
 * Returns `true` if the event loop is currently running, `false` otherwise.
 *
 * @example
 * // Check if the uloop event loop is running
 * const isRunning = uloop.running();
 * if (isRunning)
 *     printf("uloop event loop is currently running\n");
 * else
 *     printf("uloop event loop is not running\n");
 */
static uc_value_t *
uc_uloop_running(uc_vm_t *vm, size_t nargs)
{
	bool prev = uloop_cancelled;
	bool active;

	uloop_cancelled = true;
	active = uloop_cancelling();
	uloop_cancelled = prev;

	ok_return(ucv_boolean_new(active));
}

/**
 * Halts the uloop event loop.
 *
 * This function halts the uloop event loop, stopping its execution and
 * preventing further processing of scheduled events and callbacks.
 *
 * Expired timeouts and already queued event callbacks are still run to
 * completion.
 *
 * @function module:uloop#end
 *
 * @returns {void}
 * This function does not return any value.
 *
 * @example
 * // Halt the uloop event loop
 * uloop.end();
 */
static uc_value_t *
uc_uloop_end(uc_vm_t *vm, size_t nargs)
{
	uloop_end();

	ok_return(NULL);
}

/**
 * Stops the uloop event loop and cancels pending timeouts and events.
 *
 * This function immediately stops the uloop event loop, cancels all pending
 * timeouts and events, unregisters all handles, and deallocates associated
 * resources.
 *
 * @function module:uloop#done
 *
 * @returns {void}
 * This function does not return any value.
 *
 * @example
 * // Stop the uloop event loop and clean up resources
 * uloop.done();
 */
static uc_value_t *
uc_uloop_done(uc_vm_t *vm, size_t nargs)
{
	uloop_done();

	ok_return(NULL);
}


/**
 * Represents a uloop timer instance as returned by
 * {@link module:uloop#timer|timer()}.
 *
 * @class module:uloop.timer
 * @hideconstructor
 *
 * @see {@link module:uloop#timer|timer()}
 *
 * @example
 *
 * const timeout = uloop.timer(…);
 *
 * timeout.set(…);
 * timeout.remaining();
 * timeout.cancel();
 */
typedef struct {
	struct uloop_timeout timeout;
	size_t registry_index;
	uc_vm_t *vm;
} uc_uloop_timer_t;

static void
uc_uloop_timeout_clear(uc_uloop_timer_t **timer)
{
	/* drop registry entries and clear data to prevent reuse */
	uc_uloop_reg_remove((*timer)->registry_index);
	free(*timer);
	*timer = NULL;
}

/**
 * Rearms the uloop timer with the specified timeout.
 *
 * This method rearms the uloop timer with the specified timeout value,
 * allowing it to trigger after the specified amount of time. If no timeout
 * value is provided or if the provided value is negative, the timer remains
 * disabled until rearmed with a positive timeout value.
 *
 * @function module:uloop.timer#set
 *
 * @param {number} [timeout=-1]
 * Optional. The timeout value in milliseconds until the timer expires.
 * Defaults to -1, which disables the timer until rearmed with a positive timeout.
 *
 * @returns {?boolean}
 * Returns `true` on success, `null` on error, such as an invalid timeout argument.
 *
 * @example
 * const timeout = uloop.timer(…);
 *
 * // Rearm the uloop timer with a timeout of 1000 milliseconds
 * timeout.set(1000);
 *
 * // Disable the uloop timer
 * timeout.set();
 */
static uc_value_t *
uc_uloop_timer_set(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_timer_t **timer = uc_fn_this("uloop.timer");
	uc_value_t *timeout = uc_fn_arg(0);
	int t, rv;

	if (!timer || !*timer)
		err_return(EINVAL);

	errno = 0;
	t = timeout ? (int)ucv_int64_get(timeout) : -1;

	if (errno)
		err_return(errno);

	rv = uloop_timeout_set(&(*timer)->timeout, t);

	ok_return(ucv_boolean_new(rv == 0));
}

/**
 * Returns the number of milliseconds until the uloop timer expires.
 *
 * This method returns the remaining time until the uloop timer expires. If
 * the timer is not armed (i.e., disabled), it returns -1.
 *
 * @function module:uloop.timer#remaining
 *
 * @returns {number}
 * The number of milliseconds until the timer expires, or -1 if the timer is not armed.
 *
 * @example
 * // Get the remaining time until the uloop timer expires (~500ms)
 * const remainingTime = timer.remaining();
 * if (remainingTime !== -1)
 *     printf("Time remaining until timer expires: %d ms\n", remainingTime);
 * else
 *     printf("Timer is not armed\n");
 */
static uc_value_t *
uc_uloop_timer_remaining(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_timer_t **timer = uc_fn_this("uloop.timer");
	int64_t rem;

	if (!timer || !*timer)
		err_return(EINVAL);

#ifdef HAVE_ULOOP_TIMEOUT_REMAINING64
	rem = uloop_timeout_remaining64(&(*timer)->timeout);
#else
	rem = (int64_t)uloop_timeout_remaining(&(*timer)->timeout);
#endif

	ok_return(ucv_int64_new(rem));
}

/**
 * Cancels the uloop timer, disarming it and removing it from the event loop.
 *
 * This method destroys the uloop timer and releases its associated resources.
 *
 * @function module:uloop.timer#cancel
 *
 * @returns {boolean}
 * Returns `true` on success.
 *
 * @example
 * // Cancel the uloop timer
 * timer.cancel();
 */
static uc_value_t *
uc_uloop_timer_cancel(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_timer_t **timer = uc_fn_this("uloop.timer");
	int rv;

	if (!timer || !*timer)
		err_return(EINVAL);

	rv = uloop_timeout_cancel(&(*timer)->timeout);

	uc_uloop_timeout_clear(timer);

	ok_return(ucv_boolean_new(rv == 0));
}

static void
uc_uloop_timer_cb(struct uloop_timeout *timeout)
{
	uc_uloop_timer_t *timer = (uc_uloop_timer_t *)timeout;

	uc_uloop_reg_invoke(timer->vm, timer->registry_index, NULL);
}

/**
 * Creates a timer instance for scheduling callbacks.
 *
 * This function creates a timer instance for scheduling callbacks to be
 * executed after a specified timeout duration. It takes an optional timeout
 * parameter, which defaults to -1, indicating that the timer is initially not
 * armed and can be enabled later by invoking the `.set(timeout)` method on the
 * instance.
 *
 * A callback function must be provided to be executed when the timer expires.
 *
 * @function module:uloop#timer
 *
 * @param {number} [timeout=-1]
 * Optional. The timeout duration in milliseconds. Defaults to -1, indicating
 * the timer is not initially armed.
 *
 * @param {Function} callback
 * The callback function to be executed when the timer expires.
 *
 * @returns {?module:uloop.timer}
 * Returns a timer instance for scheduling callbacks.
 * Returns `null` when the timeout or callback arguments are invalid.
 *
 * @example
 * // Create a timer with a callback to be executed after 1000 milliseconds
 * const myTimer = uloop.timer(1000, () => {
 *     printf("Timer expired!\n");
 * });
 *
 * // Later enable the timer with a timeout of 500 milliseconds
 * myTimer.set(500);
 */
static uc_value_t *
uc_uloop_timer(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *timeout = uc_fn_arg(0);
	uc_value_t *callback = uc_fn_arg(1);
	uc_uloop_timer_t *timer;
	uc_value_t *res;
	int t;

	errno = 0;
	t = timeout ? ucv_int64_get(timeout) : -1;

	if (errno)
		err_return(errno);

	if (!ucv_is_callable(callback))
		err_return(EINVAL);

	timer = xalloc(sizeof(*timer));
	timer->timeout.cb = uc_uloop_timer_cb;
	timer->vm = vm;

	if (t >= 0)
		uloop_timeout_set(&timer->timeout, t);

	res = uc_resource_new(timer_type, timer);

	timer->registry_index = uc_uloop_reg_add(res, callback);

	ok_return(res);
}


/**
 * Represents a uloop handle instance as returned by
 * {@link module:uloop#handle|handle()}.
 *
 * @class module:uloop.handle
 * @hideconstructor
 *
 * @see {@link module:uloop#handle|handle()}
 *
 * @example
 *
 * const handle = uloop.handle(…);
 *
 * handle.fileno();
 * handle.handle();
 *
 * handle.delete();
 */
typedef struct {
	struct uloop_fd fd;
	size_t registry_index;
	uc_value_t *handle;
	uc_vm_t *vm;
} uc_uloop_handle_t;

static void
uc_uloop_handle_clear(uc_uloop_handle_t **handle)
{
	/* drop registry entries and clear data to prevent reuse */
	uc_uloop_reg_remove((*handle)->registry_index);
	ucv_put((*handle)->handle);
	free(*handle);
	*handle = NULL;
}

/**
 * Returns the file descriptor number.
 *
 * This method returns the file descriptor number associated with the underlying
 * handle, which might refer to a socket or file instance.
 *
 * @function module:uloop.handle#fileno
 *
 * @returns {number}
 * The file descriptor number associated with the handle.
 *
 * @example
 * // Get the file descriptor number associated with the uloop handle
 * const fd = handle.fileno();
 * printf("File descriptor number: %d\n", fd);
 */
static uc_value_t *
uc_uloop_handle_fileno(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_handle_t **handle = uc_fn_this("uloop.handle");

	if (!handle || !*handle)
		err_return(EINVAL);

	ok_return(ucv_int64_new((*handle)->fd.fd));
}

/**
 * Returns the underlying file or socket instance.
 *
 * This method returns the underlying file or socket instance associated with
 * the uloop handle.
 *
 * @function module:uloop.handle#handle
 *
 * @returns {module:fs.file|module:fs.proc|module:socket.socket}
 * The underlying file or socket instance associated with the handle.
 *
 * @example
 * // Get the associated file or socket instance
 * const fileOrSocket = handle.handle();
 * printf("Handle: %s\n", fileOrSocket); // e.g. <socket 0x5> or <fs.proc …>
 */
static uc_value_t *
uc_uloop_handle_handle(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_handle_t **handle = uc_fn_this("uloop.handle");

	if (!handle || !*handle)
		err_return(EINVAL);

	ok_return(ucv_get((*handle)->handle));
}

/**
 * Unregisters the uloop handle.
 *
 * This method unregisters the uloop handle from the uloop event loop and frees
 * any associated resources. After calling this method, the handle instance
 * should no longer be used.
 *
 * @function module:uloop.handle#delete
 *
 * @returns {void}
 * This function does not return a value.
 *
 * @example
 * // Unregister the uloop handle and free associated resources
 * handle.delete();
 * printf("Handle deleted successfully\n");
 */
static uc_value_t *
uc_uloop_handle_delete(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_handle_t **handle = uc_fn_this("uloop.handle");
	int rv;

	if (!handle || !*handle)
		err_return(EINVAL);

	rv = uloop_fd_delete(&(*handle)->fd);

	uc_uloop_handle_clear(handle);

	if (rv != 0)
		err_return(errno);

	ok_return(ucv_boolean_new(true));
}

static void
uc_uloop_handle_cb(struct uloop_fd *fd, unsigned int flags)
{
	uc_uloop_handle_t *handle = (uc_uloop_handle_t *)fd;
	uc_value_t *f = ucv_uint64_new(flags);

	uc_uloop_reg_invoke(handle->vm, handle->registry_index, f);
	ucv_put(f);
}

static int
get_fd(uc_vm_t *vm, uc_value_t *val)
{
	uc_value_t *fn;
	int64_t n;
	int fd;

	fn = ucv_property_get(val, "fileno");

	if (ucv_is_callable(fn)) {
		uc_vm_stack_push(vm, ucv_get(val));
		uc_vm_stack_push(vm, ucv_get(fn));

		if (uc_vm_call(vm, true, 0) == EXCEPTION_NONE)  {
			val = uc_vm_stack_pop(vm);
		}
		else {
			errno = EBADF;
			val = NULL;
		}
	}
	else {
		ucv_get(val);
	}

	n = ucv_int64_get(val);

	if (errno) {
		fd = -1;
	}
	else if (n < 0 || n > (int64_t)INT_MAX) {
		errno = EBADF;
		fd = -1;
	}
	else {
		fd = (int)n;
	}

	ucv_put(val);

	return fd;
}

/**
 * Creates a handle instance for monitoring file descriptor events.
 *
 * This function creates a handle instance for monitoring events on a file
 * descriptor, file, or socket. It takes the file or socket handle, a callback
 * function to be invoked when the specified IO events occur, and bitwise OR-ed
 * flags of IO events (`ULOOP_READ`, `ULOOP_WRITE`) that the callback should be
 * invoked for.
 *
 * @function module:uloop#handle
 *
 * @param {number|module:fs.file|module:fs.proc|module:socket.socket} handle
 * The file handle (descriptor number, file or socket instance).
 *
 * @param {Function} callback
 * The callback function to be invoked when the specified IO events occur.
 *
 * @param {number} events
 * Bitwise OR-ed flags of IO events (`ULOOP_READ`, `ULOOP_WRITE`) that the
 * callback should be invoked for.
 *
 * @returns {?module:uloop.handle}
 * Returns a handle instance for monitoring file descriptor events.
 * Returns `null` when the handle, callback or signal arguments are invalid.
 *
 * @example
 * // Create a handle for monitoring read events on file descriptor 3
 * const myHandle = uloop.handle(3, (events) => {
 *     if (events & ULOOP_READ)
 *         printf("Read event occurred!\n");
 * }, uloop.ULOOP_READ);
 *
 * // Check socket for writability
 * const sock = socket.connect("example.org", 80);
 * uloop.handle(sock, (events) => {
 *     sock.send("GET / HTTP/1.0\r\n\r\n");
 * }, uloop.ULOOP_WRITE)
 */
static uc_value_t *
uc_uloop_handle(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *fileno = uc_fn_arg(0);
	uc_value_t *callback = uc_fn_arg(1);
	uc_value_t *flags = uc_fn_arg(2);
	uc_uloop_handle_t *handle;
	uc_value_t *res;
	int fd, ret;
	uint64_t f;

	fd = get_fd(vm, fileno);

	if (fd == -1)
		err_return(errno);

	f = ucv_uint64_get(flags);

	if (errno)
		err_return(errno);

	if (f == 0 || f > (uint64_t)UINT_MAX)
		err_return(EINVAL);

	if (!ucv_is_callable(callback))
		err_return(EINVAL);

	handle = xalloc(sizeof(*handle));
	handle->fd.fd = fd;
	handle->fd.cb = uc_uloop_handle_cb;
	handle->handle = ucv_get(fileno);
	handle->vm = vm;

	ret = uloop_fd_add(&handle->fd, (unsigned int)f);

	if (ret != 0) {
		free(handle);
		err_return(errno);
	}

	res = uc_resource_new(handle_type, handle);

	handle->registry_index = uc_uloop_reg_add(res, callback);

	ok_return(res);
}


/**
 * Represents a uloop process instance as returned by
 * {@link module:uloop#process|process()}.
 *
 * @class module:uloop.process
 * @hideconstructor
 *
 * @see {@link module:uloop#process|process()}
 *
 * @example
 *
 * const proc = uloop.process(…);
 *
 * proc.pid();
 *
 * proc.delete();
 */
typedef struct {
	struct uloop_process process;
	size_t registry_index;
	uc_vm_t *vm;
} uc_uloop_process_t;

static void
uc_uloop_process_clear(uc_uloop_process_t **process)
{
	/* drop registry entries and clear data to prevent reuse */
	uc_uloop_reg_remove((*process)->registry_index);
	*process = NULL;
}

/**
 * Returns the process ID.
 *
 * This method returns the process ID (PID) of the operating system process
 * launched by {@link module:uloop#process|process().
 *
 * @function module:uloop.process#pid
 *
 * @returns {number}
 * The process ID (PID) of the associated launched process.
 *
 * @example
 * const proc = uloop.process(…);
 *
 * printf("Process ID: %d\n", proc.pid());
 */
static uc_value_t *
uc_uloop_process_pid(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_process_t **process = uc_fn_this("uloop.process");

	if (!process || !*process)
		err_return(EINVAL);

	ok_return(ucv_int64_new((*process)->process.pid));
}

/**
 * Unregisters the process from uloop.
 *
 * This method unregisters the process from the uloop event loop and releases
 * any associated resources. However, note that the operating system process
 * itself is not terminated by this method.
 *
 * @function module:uloop.process#delete
 *
 * @returns {boolean}
 * Returns `true` on success.
 *
 * @example
 * const proc = uloop.process(…);
 *
 * proc.delete();
 */
static uc_value_t *
uc_uloop_process_delete(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_process_t **process = uc_fn_this("uloop.process");
	int rv;

	if (!process || !*process)
		err_return(EINVAL);

	rv = uloop_process_delete(&(*process)->process);

	uc_uloop_process_clear(process);

	if (rv != 0)
		err_return(EINVAL);

	ok_return(ucv_boolean_new(true));
}

static void
uc_uloop_process_cb(struct uloop_process *proc, int exitcode)
{
	uc_uloop_process_t *process = (uc_uloop_process_t *)proc;
	uc_value_t *e = ucv_int64_new(exitcode >> 8);

	uc_uloop_reg_invoke(process->vm, process->registry_index, e);
	uc_uloop_process_clear(&process);
	ucv_put(e);
}

/**
 * Creates a process instance for executing external programs.
 *
 * This function creates a process instance for executing external programs.
 * It takes the executable path string, an optional string array as the argument
 * vector, an optional dictionary describing environment variables, and a
 * callback function to be invoked when the invoked process ends.
 *
 * @function module:uloop#process
 *
 * @param {string} executable
 * The path to the executable program.
 *
 * @param {string[]} [args]
 * Optional. An array of strings representing the arguments passed to the
 * executable.
 *
 * @param {Object<string, *>} [env]
 * Optional. A dictionary describing environment variables for the process.
 *
 * @param {Function} callback
 * The callback function to be invoked when the invoked process ends.
 *
 * @returns {?module:uloop.process}
 * Returns a process instance for executing external programs.
 * Returns `null` on error, e.g. due to `exec()` failure or invalid arguments.
 *
 * @example
 * // Create a process instance for executing 'ls' command
 * const myProcess = uloop.process("/bin/ls", ["-l", "/tmp"], null, (code) => {
 *     printf(`Process exited with code ${code}\n`);
 * });
 */
static uc_value_t *
uc_uloop_process(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *executable = uc_fn_arg(0);
	uc_value_t *arguments = uc_fn_arg(1);
	uc_value_t *env_arg = uc_fn_arg(2);
	uc_value_t *callback = uc_fn_arg(3);
	uc_uloop_process_t *process;
	uc_stringbuf_t *buf;
	char **argp, **envp;
	uc_value_t *res;
	pid_t pid;
	size_t i;

	if (ucv_type(executable) != UC_STRING ||
	    (arguments && ucv_type(arguments) != UC_ARRAY) ||
	    (env_arg && ucv_type(env_arg) != UC_OBJECT) ||
	    !ucv_is_callable(callback)) {
		err_return(EINVAL);
	}

	pid = fork();

	if (pid == -1)
		err_return(errno);

	if (pid == 0) {
		argp = calloc(ucv_array_length(arguments) + 2, sizeof(char *));
		envp = calloc(ucv_object_length(env_arg) + 1, sizeof(char *));

		if (!argp || !envp)
			_exit(-1);

		argp[0] = ucv_to_string(vm, executable);

		for (i = 0; i < ucv_array_length(arguments); i++)
			argp[i+1] = ucv_to_string(vm, ucv_array_get(arguments, i));

		i = 0;

		ucv_object_foreach(env_arg, envk, envv) {
			buf = xprintbuf_new();

			ucv_stringbuf_printf(buf, "%s=", envk);
			ucv_to_stringbuf(vm, buf, envv, false);

			envp[i++] = buf->buf;

			free(buf);
		}

		execvpe((const char *)ucv_string_get(executable),
		        (char * const *)argp, (char * const *)envp);

		_exit(-1);
	}

	process = xalloc(sizeof(*process));
	process->process.pid = pid;
	process->process.cb = uc_uloop_process_cb;
	process->vm = vm;

	uloop_process_add(&process->process);

	res = uc_resource_new(process_type, process);

	process->registry_index = uc_uloop_reg_add(res, callback);

	ok_return(res);
}


static bool
readall(int fd, void *buf, size_t len)
{
	ssize_t rlen;

	while (len > 0) {
		rlen = read(fd, buf, len);

		if (rlen == -1) {
			if (errno == EINTR)
				continue;

			return false;
		}

		if (rlen == 0) {
			errno = EINTR;

			return false;
		}

		buf += rlen;
		len -= rlen;
	}

	return true;
}

static bool
writeall(int fd, void *buf, size_t len)
{
	ssize_t wlen;

	while (len > 0) {
		wlen = write(fd, buf, len);

		if (wlen == -1) {
			if (errno == EINTR)
				continue;

			return false;
		}

		buf += wlen;
		len -= wlen;
	}

	return true;
}


/**
 * Represents a uloop task communication pipe instance, passed as sole argument
 * to the task function by {@link module:uloop#task|task()}.
 *
 * @class module:uloop.pipe
 * @hideconstructor
 *
 * @see {@link module:uloop#task|task()}
 *
 * @example *
 * const task = uloop.task((pipe) => {
 *     …
 *     pipe.send();
 *     …
 *     pipe.receive();
 *     …
 * }, …);
 */
typedef struct {
	int input;
	int output;
	bool has_sender;
	bool has_receiver;
} uc_uloop_pipe_t;

static uc_value_t *
uc_uloop_pipe_send_common(uc_vm_t *vm, uc_value_t *msg, int fd)
{
	uc_stringbuf_t *buf;
	size_t len;
	bool rv;

	buf = xprintbuf_new();

	printbuf_memset(buf, 0, 0, sizeof(len));
	ucv_to_stringbuf(vm, buf, msg, true);

	len = printbuf_length(buf);
	memcpy(buf->buf, &len, sizeof(len));

	rv = writeall(fd, buf->buf, len);

	printbuf_free(buf);

	if (!rv)
		err_return(errno);

	ok_return(ucv_boolean_new(true));
}

/**
 * Sends a serialized message to the task handle.
 *
 * This method serializes the provided message and sends it over the task
 * communication pipe. In the main thread, the message is deserialized and
 * passed as an argument to the output callback function registered with the
 * task handle.
 *
 * @function module:uloop.pipe#send
 *
 * @param {*} msg
 * The message to be serialized and sent over the pipe. It can be of arbitrary type.
 *
 * @returns {?boolean}
 * Returns `true` on success, indicating that the message was successfully sent
 * over the pipe. Returns `null` on error, such as when there's no output
 * callback registered with the task handle.
 *
 * @example
 * // Send a message over the uloop pipe
 * const success = pipe.send(message);
 *
 * if (success)
 *     printf("Message sent successfully\n");
 * else
 *     die(`Error sending message: ${uloop.error()}\n`);
 */
static uc_value_t *
uc_uloop_pipe_send(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_pipe_t **pipe = uc_fn_this("uloop.pipe");
	uc_value_t *msg = uc_fn_arg(0);

	if (!pipe || !*pipe)
		err_return(EINVAL);

	if (!(*pipe)->has_receiver)
		err_return(EPIPE);

	ok_return(uc_uloop_pipe_send_common(vm, msg, (*pipe)->output));
}

static bool
uc_uloop_pipe_receive_common(uc_vm_t *vm, int fd, uc_value_t **res, bool skip)
{
	enum json_tokener_error err = json_tokener_error_parse_eof;
	json_tokener *tok = NULL;
	json_object *jso = NULL;
	char buf[1024];
	ssize_t rlen;
	size_t len;

	*res = NULL;

	if (!readall(fd, &len, sizeof(len)))
		err_return(errno);

	/* message length 0 is special, means input requested on other pipe */
	if (len == 0)
		err_return(ENODATA);

	/* valid messages should be at least sizeof(len) plus one byte of payload */
	if (len <= sizeof(len))
		err_return(EINVAL);

	len -= sizeof(len);

	while (len > 0) {
		rlen = read(fd, buf, len < sizeof(buf) ? len : sizeof(buf));

		if (rlen == -1) {
			if (errno == EINTR)
				continue;

			goto read_fail;
		}

		/* premature EOF */
		if (rlen == 0) {
			errno = EPIPE;
			goto read_fail;
		}

		if (!skip) {
			if (!tok)
				tok = xjs_new_tokener();

			jso = json_tokener_parse_ex(tok, buf, rlen);
			err = json_tokener_get_error(tok);
		}

		len -= rlen;
	}

	if (!skip) {
		if (err == json_tokener_continue) {
			jso = json_tokener_parse_ex(tok, "\0", 1);
			err = json_tokener_get_error(tok);
		}

		json_tokener_free(tok);

		if (err != json_tokener_success) {
			errno = EINVAL;
			goto read_fail;
		}

		*res = ucv_from_json(vm, jso);

		json_object_put(jso);
	}

	return true;

read_fail:
	if (tok)
		json_tokener_free(tok);

	json_object_put(jso);
	err_return(errno);
}

/**
 * Reads input from the task handle.
 *
 * This method reads input from the task communication pipe. The input callback
 * function registered with the task handle is invoked to return the input data,
 * which is then serialized, sent over the pipe, and deserialized by the receive
 * method.
 *
 * @function module:uloop.pipe#receive
 *
 * @returns {?*}
 * Returns the deserialized message read from the task communication pipe.
 * Returns `null` on error, such as when there's no input callback registered
 * on the task handle.
 *
 * @example
 * // Read input from the task communication pipe
 * const message = pipe.receive();
 *
 * if (message !== null)
 *     printf("Received message: %s\n", message);
 * else
 *     die(`Error receiving message: ${uloop.error()}\n`);
 */
static uc_value_t *
uc_uloop_pipe_receive(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_pipe_t **pipe = uc_fn_this("uloop.pipe");
	uc_value_t *rv;
	size_t len = 0;

	if (!pipe || !*pipe)
		err_return(EINVAL);

	if (!(*pipe)->has_sender)
		err_return(EPIPE);

	/* send zero-length message to signal input request */
	writeall((*pipe)->output, &len, sizeof(len));

	/* receive input message */
	uc_uloop_pipe_receive_common(vm, (*pipe)->input, &rv, false);

	return rv;
}

/**
 * Checks if the task handle provides input.
 *
 * This method checks if the task handle has an input callback  registered.
 * It returns a boolean value indicating whether an input callback is present.
 *
 * @function module:uloop.pipe#sending
 *
 * @returns {boolean}
 * Returns `true` if the remote task handle has an input callback
 * registered, otherwise returns `false`.
 *
 * @example
 * // Check if the remote task handle has an input callback
 * const hasInputCallback = pipe.sending();
 *
 * if (hasInputCallback)
 *     printf("Input callback is registered on task handle\n");
 * else
 *     printf("No input callback on the task handle\n");
 */
static uc_value_t *
uc_uloop_pipe_sending(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_pipe_t **pipe = uc_fn_this("uloop.pipe");

	if (!pipe || !*pipe)
		err_return(EINVAL);

	ok_return(ucv_boolean_new((*pipe)->has_sender));
}

/**
 * Checks if the task handle reads output.
 *
 * This method checks if the task handle has an output callback registered.
 * It returns a boolean value indicating whether an output callback is present.
 *
 * @function module:uloop.pipe#receiving
 *
 * @returns {boolean}
 * Returns `true` if the task handle has an output callback registered,
 * otherwise returns `false`.
 *
 * @example
 * // Check if the task handle has an output callback
 * const hasOutputCallback = pipe.receiving();
 *
 * if (hasOutputCallback)
 *     printf("Output callback is registered on task handle\n");
 * else
 *     printf("No output callback on the task handle\n");
 */
static uc_value_t *
uc_uloop_pipe_receiving(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_pipe_t **pipe = uc_fn_this("uloop.pipe");

	if (!pipe || !*pipe)
		err_return(EINVAL);

	ok_return(ucv_boolean_new((*pipe)->has_receiver));
}


/**
 * Represents a uloop task instance as returned by
 * {@link module:uloop#task|task()}.
 *
 * @class module:uloop.task
 * @hideconstructor
 *
 * @see {@link module:uloop#task|task()}
 *
 * @example
 *
 * const task = uloop.task(…);
 *
 * task.pid();
 * task.finished();
 *
 * task.kill();
 */
typedef struct {
	struct uloop_process process;
	struct uloop_fd output;
	size_t registry_index;
	bool finished;
	int input_fd;
	uc_vm_t *vm;
	uc_value_t *input_cb;
	uc_value_t *output_cb;
} uc_uloop_task_t;

static int
patch_devnull(int fd, bool write)
{
	int devnull = open("/dev/null", write ? O_WRONLY : O_RDONLY);

	if (devnull != -1) {
		dup2(fd, devnull);
		close(fd);
	}

	return devnull;
}

static void
uloop_fd_close(struct uloop_fd *fd) {
	if (fd->fd == -1)
		return;

	close(fd->fd);
	fd->fd = -1;
}

static void
uc_uloop_task_clear(uc_uloop_task_t **task)
{
	/* drop registry entries and clear data to prevent reuse */
	uc_uloop_reg_remove((*task)->registry_index);
	*task = NULL;
}

/**
 * Returns the process ID.
 *
 * This method returns the process ID (PID) of the underlying forked process
 * launched by {@link module:uloop#task|task().
 *
 * @function module:uloop.task#pid
 *
 * @returns {number}
 * The process ID (PID) of the forked task process.
 *
 * @example
 * const task = uloop.task(…);
 *
 * printf("Process ID: %d\n", task.pid());
 */
static uc_value_t *
uc_uloop_task_pid(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_task_t **task = uc_fn_this("uloop.task");

	if (!task || !*task)
		err_return(EINVAL);

	if ((*task)->finished)
		err_return(ESRCH);

	ok_return(ucv_int64_new((*task)->process.pid));
}

/**
 * Terminates the task process.
 *
 * This method terminates the task process. It sends a termination signal to
 * the task process, causing it to exit. Returns `true` on success, indicating
 * that the task process was successfully terminated. Returns `null` on error,
 * such as when the task process has already terminated.
 *
 * @function module:uloop.task#kill
 *
 * @returns {?boolean}
 * Returns `true` when the task process was successfully terminated.
 * Returns `null` on error, such as when the process has already terminated.
 *
 * @example
 * // Terminate the task process
 * const success = task.kill();
 *
 * if (success)
 *     printf("Task process terminated successfully\n");
 * else
 *     die(`Error terminating task process: ${uloop.error()}\n`);
 */
static uc_value_t *
uc_uloop_task_kill(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_task_t **task = uc_fn_this("uloop.task");
	int rv;

	if (!task || !*task)
		err_return(EINVAL);

	if ((*task)->finished)
		err_return(ESRCH);

	rv = kill((*task)->process.pid, SIGTERM);

	if (rv == -1)
		err_return(errno);

	ok_return(ucv_boolean_new(true));
}

/**
 * Checks if the task ran to completion.
 *
 * This method checks if the task function has already run to completion.
 * It returns a boolean value indicating whether the task function has finished
 * executing.
 *
 * @function module:uloop.task#finished
 *
 * @returns {boolean}
 * Returns `true` if the task function has already run to completion, otherwise
 * returns `false`.
 *
 * @example
 * // Check if the task function has finished executing
 * const isFinished = task.finished();
 *
 * if (isFinished)
 *     printf("Task function has finished executing\n");
 * else
 *     printf("Task function is still running\n");
 */
static uc_value_t *
uc_uloop_task_finished(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_task_t **task = uc_fn_this("uloop.task");

	if (!task || !*task)
		err_return(EINVAL);

	ok_return(ucv_boolean_new((*task)->finished));
}

static void
uc_uloop_task_output_cb(struct uloop_fd *fd, unsigned int flags)
{
	uc_uloop_task_t *task = container_of(fd, uc_uloop_task_t, output);
	uc_value_t *obj = ucv_array_get(object_registry, task->registry_index);
	uc_value_t *msg = NULL;

	if (flags & ULOOP_READ) {
		while (true) {
			if (!uc_uloop_pipe_receive_common(task->vm, fd->fd, &msg, !task->output_cb)) {
				/* input requested */
				if (last_error == ENODATA) {
					uc_vm_stack_push(task->vm, ucv_get(obj));
					uc_vm_stack_push(task->vm, ucv_get(task->input_cb));

					if (uc_vm_call(task->vm, true, 0) != EXCEPTION_NONE) {
						uloop_end();

						return;
					}

					msg = uc_vm_stack_pop(task->vm);
					uc_uloop_pipe_send_common(task->vm, msg, task->input_fd);
					ucv_put(msg);

					continue;
				}

				/* error */
				break;
			}

			if (task->output_cb) {
				uc_vm_stack_push(task->vm, ucv_get(obj));
				uc_vm_stack_push(task->vm, ucv_get(task->output_cb));
				uc_vm_stack_push(task->vm, msg);

				if (uc_vm_call(task->vm, true, 1) == EXCEPTION_NONE) {
					ucv_put(uc_vm_stack_pop(task->vm));
				}
				else {
					uloop_end();

					return;
				}
			}
			else {
				ucv_put(msg);
			}
		}
	}

	if (!fd->registered && task->finished) {
		close(task->input_fd);
		task->input_fd = -1;

		uloop_fd_close(&task->output);
		uloop_process_delete(&task->process);

		uc_uloop_task_clear(&task);
	}
}

static void
uc_uloop_task_process_cb(struct uloop_process *proc, int exitcode)
{
	uc_uloop_task_t *task = container_of(proc, uc_uloop_task_t, process);

	task->finished = true;

	uc_uloop_task_output_cb(&task->output, ULOOP_READ);
}

/**
 * Creates a task instance for executing background tasks.
 *
 * This function creates a task instance for executing background tasks.
 * It takes the task function to be invoked as a background process,
 * an optional output callback function to be invoked when output is received
 * from the task, and an optional input callback function to be invoked
 * when input is required by the task.
 *
 * @function module:uloop#task
 *
 * @param {Function} taskFunction
 * The task function to be invoked as a background process.
 *
 * @param {Function} [outputCallback]
 * Optional. The output callback function to be invoked when output is received
 * from the task. It is invoked with the output data as the argument.
 *
 * @param {Function} [inputCallback]
 * Optional. The input callback function to be invoked when input is required
 * by the task. It is invoked with a function to send input to the task
 * as the argument.
 *
 * @returns {?module:uloop.task}
 * Returns a task instance for executing background tasks.
 * Returns `null` on error, e.g. due to fork failure or invalid arguments.
 *
 * @example
 * // Create a task instance for executing a background task
 * const myTask = uloop.task(
 *     (pipe) => {
 *         // Task logic
 *         pipe.send("Hello from the task\n");
 *         const input = pipe.receive();
 *         printf(`Received input from main thread: ${input}\n`);
 *     },
 *     (output) => {
 *         // Output callback, invoked when task function calls pipe.send()
 *         printf(`Received output from task: ${output}\n`);
 *     },
 *     () => {
 *         // Input callback, invoked when task function calls pipe.receive()
 *         return "Input from main thread\n";
 *     }
 * );
 */
static uc_value_t *
uc_uloop_task(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *func = uc_fn_arg(0);
	uc_value_t *output_cb = uc_fn_arg(1);
	uc_value_t *input_cb = uc_fn_arg(2);
	int outpipe[2] = { -1, -1 };
	int inpipe[2] = { -1, -1 };
	uc_value_t *res, *cbs, *p;
	uc_uloop_pipe_t *tpipe;
	uc_uloop_task_t *task;
	pid_t pid;
	int err;

	if (!ucv_is_callable(func) ||
	    (output_cb && !ucv_is_callable(output_cb)) ||
	    (input_cb && !ucv_is_callable(input_cb)))
	    err_return(EINVAL);

	if (pipe(outpipe) == -1 || pipe(inpipe) == -1) {
		err = errno;

		close(outpipe[0]); close(outpipe[1]);
		close(inpipe[0]); close(inpipe[1]);

		err_return(err);
	}

	pid = fork();

	if (pid == -1)
		err_return(errno);

	if (pid == 0) {
		uloop_done();

		patch_devnull(0, false);
		patch_devnull(1, true);
		patch_devnull(2, true);

		vm->output = fdopen(1, "w");

		close(inpipe[1]);
		close(outpipe[0]);

		tpipe = xalloc(sizeof(*tpipe));
		tpipe->input = inpipe[0];
		tpipe->output = outpipe[1];
		tpipe->has_sender = input_cb;
		tpipe->has_receiver = output_cb;

		p = uc_resource_new(pipe_type, tpipe);

		uc_vm_stack_push(vm, func);
		uc_vm_stack_push(vm, ucv_get(p));

		if (uc_vm_call(vm, false, 1) == EXCEPTION_NONE) {
			res = uc_vm_stack_pop(vm);
			uc_uloop_pipe_send_common(vm, res, tpipe->output);
			ucv_put(res);
		}

		ucv_put(p);

		_exit(0);
	}

	close(inpipe[0]);
	close(outpipe[1]);

	task = xalloc(sizeof(*task));
	task->process.pid = pid;
	task->process.cb = uc_uloop_task_process_cb;

	task->vm = vm;

	task->output.fd = outpipe[0];
	task->output.cb = uc_uloop_task_output_cb;
	task->output_cb = output_cb;
	uloop_fd_add(&task->output, ULOOP_READ);

	if (input_cb) {
		task->input_fd = inpipe[1];
		task->input_cb = input_cb;
	}
	else {
		task->input_fd = -1;
		close(inpipe[1]);
	}

	uloop_process_add(&task->process);

	res = uc_resource_new(task_type, task);

	cbs = ucv_array_new(NULL);
	ucv_array_set(cbs, 0, ucv_get(output_cb));
	ucv_array_set(cbs, 1, ucv_get(input_cb));

	task->registry_index = uc_uloop_reg_add(res, cbs);

	ok_return(res);
}


/**
 * Represents a uloop interval timer instance as returned by
 * {@link module:uloop#interval|interval()}.
 *
 * @class module:uloop.interval
 * @hideconstructor
 *
 * @see {@link module:uloop#interval|interval()}
 *
 * @example
 *
 * const intv = uloop.interval(…);
 *
 * intv.set(…);
 * intv.remaining();
 * intv.expirations();
 * intv.cancel();
 */
#ifdef HAVE_ULOOP_INTERVAL
typedef struct {
	struct uloop_interval interval;
	size_t registry_index;
	uc_vm_t *vm;
} uc_uloop_interval_t;

static void
uc_uloop_interval_clear(uc_uloop_interval_t **interval)
{
	/* drop registry entries and clear data to prevent reuse */
	uc_uloop_reg_remove((*interval)->registry_index);
	free(*interval);
	*interval = NULL;
}

/**
 * Rearms the uloop interval timer with the specified interval.
 *
 * This method rearms the interval timer with the specified interval value,
 * allowing it to trigger repeatedly after the specified amount of time. If no
 * interval value is provided or if the provided value is negative, the interval
 * remains disabled until rearmed with a positive interval value.
 *
 * @function module:uloop.interval#set
 *
 * @param {number} [interval=-1]
 * Optional. The interval value in milliseconds specifying when the interval
 * triggers again. Defaults to -1, which disables the interval until rearmed
 * with a positive interval value.
 *
 * @returns {?boolean}
 * Returns `true` on success, `null` on error, such as an invalid interval argument.
 *
 * @example
 * // Rearm the uloop interval with a interval of 1000 milliseconds
 * const success = interval.set(1000);
 *
 * if (success)
 *     printf("Interval rearmed successfully\n");
 * else
 *     printf("Error occurred while rearming interval: ${uloop.error()}\n");
 *
 * // Disable the uloop interval
 * const success = interval.set();
 *
 * if (success)
 *     printf("Interval disabled successfully\n");
 * else
 *     printf("Error occurred while disabling interval: ${uloop.error()}\n");
 */
static uc_value_t *
uc_uloop_interval_set(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_interval_t **interval = uc_fn_this("uloop.interval");
	uc_value_t *timeout = uc_fn_arg(0);
	int t, rv;

	if (!interval || !*interval)
		err_return(EINVAL);

	errno = 0;
	t = timeout ? (int)ucv_int64_get(timeout) : -1;

	if (errno)
		err_return(errno);

	rv = uloop_interval_set(&(*interval)->interval, t);

	ok_return(ucv_boolean_new(rv == 0));
}

/**
 * Returns the milliseconds until the next expiration.
 *
 * This method returns the remaining time until the uloop interval expires
 * and triggers again. If the interval is not armed (i.e., disabled),
 * it returns -1.
 *
 * @function module:uloop.interval#remaining
 *
 * @returns {number}
 * The milliseconds until the next expiration of the uloop interval, or -1 if
 * the interval is not armed.
 *
 * @example
 * // Get the milliseconds until the next expiration of the uloop interval
 * const remainingTime = interval.remaining();
 *
 * if (remainingTime !== -1)
 *     printf("Milliseconds until next expiration: %d\n", remainingTime);
 * else
 *     printf("Interval is not armed\n");
 */
static uc_value_t *
uc_uloop_interval_remaining(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_interval_t **interval = uc_fn_this("uloop.interval");

	if (!interval || !*interval)
		err_return(EINVAL);

	ok_return(ucv_int64_new(uloop_interval_remaining(&(*interval)->interval)));
}

/**
 * Returns number of times the interval timer fired.
 *
 * This method returns the number of times the uloop interval timer has expired
 * (fired) since it was instantiated.
 *
 * @function module:uloop.interval#expirations
 *
 * @returns {number}
 * The number of times the uloop interval timer has expired (fired).
 *
 * @example
 * // Get the number of times the uloop interval timer has expired
 * const expirations = interval.expirations();
 * printf("Number of expirations: %d\n", expirations);
 */
static uc_value_t *
uc_uloop_interval_expirations(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_interval_t **interval = uc_fn_this("uloop.interval");

	if (!interval || !*interval)
		err_return(EINVAL);

	ok_return(ucv_int64_new((*interval)->interval.expirations));
}

/**
 * Cancels the uloop interval.
 *
 * This method cancels the uloop interval, disarming it and removing it from the
 * event loop. Associated resources are released.
 *
 * @function module:uloop.interval#cancel
 *
 * @returns {boolean}
 * Returns `true` on success.
 *
 * @example
 * // Cancel the uloop interval
 * interval.cancel();
 */
static uc_value_t *
uc_uloop_interval_cancel(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_interval_t **interval = uc_fn_this("uloop.interval");
	int rv;

	if (!interval || !*interval)
		err_return(EINVAL);

	rv = uloop_interval_cancel(&(*interval)->interval);

	uc_uloop_interval_clear(interval);

	ok_return(ucv_boolean_new(rv == 0));
}

static void
uc_uloop_interval_cb(struct uloop_interval *uintv)
{
	uc_uloop_interval_t *interval = (uc_uloop_interval_t *)uintv;

	uc_uloop_reg_invoke(interval->vm, interval->registry_index, NULL);
}

/**
 * Creates an interval instance for scheduling repeated callbacks.
 *
 * This function creates an interval instance for scheduling repeated callbacks
 * to be executed at regular intervals. It takes an optional timeout parameter,
 * which defaults to -1, indicating that the interval is initially not armed
 * and can be armed later with the `.set(timeout)` method. A callback function
 * must be provided to be executed when the interval expires.
 *
 * @function module:uloop#interval
 *
 * @param {number} [timeout=-1]
 * Optional. The interval duration in milliseconds. Defaults to -1, indicating
 * the interval is not initially armed.
 *
 * @param {Function} callback
 * The callback function to be executed when the interval expires.
 *
 * @returns {?module:uloop.interval}
 * Returns an interval instance for scheduling repeated callbacks.
 * Returns `null` when the timeout or callback arguments are invalid.
 *
 * @example
 * // Create an interval with a callback to be executed every 1000 milliseconds
 * const myInterval = uloop.interval(1000, () => {
 *     printf("Interval callback executed!\n");
 * });
 *
 * // Later arm the interval to start executing the callback every 500 milliseconds
 * myInterval.set(500);
 */
static uc_value_t *
uc_uloop_interval(uc_vm_t *vm, size_t nargs)
{
	uc_value_t *timeout = uc_fn_arg(0);
	uc_value_t *callback = uc_fn_arg(1);
	uc_uloop_interval_t *interval;
	uc_value_t *res;
	int t;

	errno = 0;
	t = timeout ? ucv_int64_get(timeout) : -1;

	if (errno)
		err_return(errno);

	if (!ucv_is_callable(callback))
		err_return(EINVAL);

	interval = xalloc(sizeof(*interval));
	interval->interval.cb = uc_uloop_interval_cb;
	interval->vm = vm;

	if (t >= 0)
		uloop_interval_set(&interval->interval, t);

	res = uc_resource_new(interval_type, interval);

	interval->registry_index = uc_uloop_reg_add(res, callback);

	ok_return(res);
}
#endif


/**
 * Represents a uloop signal Unix process signal handler as returned by
 * {@link module:uloop#signal|signal()}.
 *
 * @class module:uloop.signal
 * @hideconstructor
 *
 * @see {@link module:uloop#signal|signal()}
 *
 * @example
 *
 * const sighandler = uloop.signal(…);
 *
 * sighandler.signo();
 * sighandler.delete();
 */
#ifdef HAVE_ULOOP_SIGNAL
typedef struct {
	struct uloop_signal signal;
	size_t registry_index;
	uc_vm_t *vm;
} uc_uloop_signal_t;

static void
uc_uloop_signal_clear(uc_uloop_signal_t **signal)
{
	/* drop registry entries and clear data to prevent reuse */
	uc_uloop_reg_remove((*signal)->registry_index);
	free(*signal);
	*signal = NULL;
}

/**
 * Returns the associated signal number.
 *
 * This method returns the signal number that this uloop signal handler is
 * configured to respond to.
 *
 * @function module:uloop.signal#signo
 *
 * @returns {number}
 * The signal number that this handler is responding to.
 *
 * @example
 * // Get the signal number that the uloop signal handler is responding to
 * const sighandler = uloop.signal("SIGINT", () => printf("Cought INT\n"));
 * printf("Signal number: %d\n", sighandler.signo());
 */
static uc_value_t *
uc_uloop_signal_signo(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_signal_t **signal = uc_fn_this("uloop.signal");

	if (!signal || !*signal)
		err_return(EINVAL);

	ok_return(ucv_int64_new((*signal)->signal.signo));
}

/**
 * Uninstalls the signal handler.
 *
 * This method uninstalls the signal handler, restoring the previous or default
 * handler for the signal, and releasing any associated resources.
 *
 * @function module:uloop.signal#delete
 *
 * @returns {boolean}
 * Returns `true` on success.
 *
 * @example
 * // Uninstall the signal handler and restore the previous/default handler
 * const sighandler = uloop.signal(…);
 * sighandler.delete();
 */
static uc_value_t *
uc_uloop_signal_delete(uc_vm_t *vm, size_t nargs)
{
	uc_uloop_signal_t **signal = uc_fn_this("uloop.signal");
	int rv;

	if (!signal || !*signal)
		err_return(EINVAL);

	rv = uloop_signal_delete(&(*signal)->signal);

	uc_uloop_signal_clear(signal);

	if (rv != 0)
		err_return(EINVAL);

	ok_return(ucv_boolean_new(true));
}

static void
uc_uloop_signal_cb(struct uloop_signal *usig)
{
	uc_uloop_signal_t *signal = (uc_uloop_signal_t *)usig;

	uc_uloop_reg_invoke(signal->vm, signal->registry_index, NULL);
}

static int
parse_signo(uc_value_t *sigspec)
{
	if (ucv_type(sigspec) == UC_STRING) {
		const char *signame = ucv_string_get(sigspec);

		if (!strncasecmp(signame, "SIG", 3))
			signame += 3;

		for (size_t i = 0; i < UC_SYSTEM_SIGNAL_COUNT; i++) {
			if (!uc_system_signal_names[i])
				continue;

			if (strcasecmp(uc_system_signal_names[i], signame))
				continue;

			return i;
		}
	}

	uc_value_t *signum = ucv_to_number(sigspec);
	int64_t signo = ucv_int64_get(signum);
	ucv_put(signum);

	if (signo < 1 || signo >= UC_SYSTEM_SIGNAL_COUNT)
		return -1;

	return signo;
}

/**
 * Creates a signal instance for handling Unix signals.
 *
 * This function creates a signal instance for handling Unix signals.
 * It takes the signal name string (with or without "SIG" prefix) or signal
 * number, and a callback function to be invoked when the specified Unix signal
 * is caught.
 *
 * @function module:uloop#signal
 *
 * @param {string|number} signal
 * The signal name string (with or without "SIG" prefix) or signal number.
 *
 * @param {Function} callback
 * The callback function to be invoked when the specified Unix signal is caught.
 *
 * @returns {?module:uloop.signal}
 * Returns a signal instance representing the installed signal handler.
 * Returns `null` when the signal or callback arguments are invalid.
 *
 * @example
 * // Create a signal instance for handling SIGINT
 * const mySignal = uloop.signal("SIGINT", () => {
 *     printf("SIGINT caught!\n");
 * });
 */
static uc_value_t *
uc_uloop_signal(uc_vm_t *vm, size_t nargs)
{
	int signo = parse_signo(uc_fn_arg(0));
	uc_value_t *callback = uc_fn_arg(1);
	uc_uloop_signal_t *signal;
	uc_value_t *res;

	if (signo == -1 || !ucv_is_callable(callback))
		err_return(EINVAL);

	signal = xalloc(sizeof(*signal));
	signal->signal.signo = signo;
	signal->signal.cb = uc_uloop_signal_cb;
	signal->vm = vm;

	uloop_signal_add(&signal->signal);

	res = uc_resource_new(signal_type, signal);

	signal->registry_index = uc_uloop_reg_add(res, callback);

	ok_return(res);
}
#endif


static const uc_function_list_t timer_fns[] = {
	{ "set",		uc_uloop_timer_set },
	{ "remaining",	uc_uloop_timer_remaining },
	{ "cancel",		uc_uloop_timer_cancel },
};

static const uc_function_list_t handle_fns[] = {
	{ "fileno",		uc_uloop_handle_fileno },
	{ "handle",		uc_uloop_handle_handle },
	{ "delete",		uc_uloop_handle_delete },
};

static const uc_function_list_t process_fns[] = {
	{ "pid",		uc_uloop_process_pid },
	{ "delete",		uc_uloop_process_delete },
};

static const uc_function_list_t task_fns[] = {
	{ "pid",		uc_uloop_task_pid },
	{ "kill",		uc_uloop_task_kill },
	{ "finished",	uc_uloop_task_finished },
};

static const uc_function_list_t pipe_fns[] = {
	{ "send",		uc_uloop_pipe_send },
	{ "receive",	uc_uloop_pipe_receive },
	{ "sending",	uc_uloop_pipe_sending },
	{ "receiving",	uc_uloop_pipe_receiving },
};

#ifdef HAVE_ULOOP_INTERVAL
static const uc_function_list_t interval_fns[] = {
	{ "set",		uc_uloop_interval_set },
	{ "remaining",	uc_uloop_interval_remaining },
	{ "expirations",
					uc_uloop_interval_expirations },
	{ "cancel",		uc_uloop_interval_cancel },
};
#endif

#ifdef HAVE_ULOOP_SIGNAL
static const uc_function_list_t signal_fns[] = {
	{ "signo",		uc_uloop_signal_signo },
	{ "delete",		uc_uloop_signal_delete },
};
#endif

static const uc_function_list_t global_fns[] = {
	{ "error",		uc_uloop_error },
	{ "init",		uc_uloop_init },
	{ "run",		uc_uloop_run },
	{ "timer",		uc_uloop_timer },
	{ "handle",		uc_uloop_handle },
	{ "process",	uc_uloop_process },
	{ "task",		uc_uloop_task },
	{ "cancelling",	uc_uloop_cancelling },
	{ "running",	uc_uloop_running },
	{ "done",		uc_uloop_done },
	{ "end",		uc_uloop_end },
#ifdef HAVE_ULOOP_INTERVAL
	{ "interval",	uc_uloop_interval },
#endif
#ifdef HAVE_ULOOP_SIGNAL
	{ "signal",		uc_uloop_signal },
#endif
};


static void close_timer(void *ud)
{
	uc_uloop_timer_t *timer = ud;

	if (!timer)
		return;

	uloop_timeout_cancel(&timer->timeout);
	free(timer);
}

static void close_handle(void *ud)
{
	uc_uloop_handle_t *handle = ud;

	if (!handle)
		return;

	uloop_fd_delete(&handle->fd);
	ucv_put(handle->handle);
	free(handle);
}

static void close_process(void *ud)
{
	uc_uloop_process_t *process = ud;

	if (!process)
		return;

	uloop_process_delete(&process->process);
	free(process);
}

static void close_task(void *ud)
{
	uc_uloop_task_t *task = ud;

	if (!task)
		return;

	uloop_process_delete(&task->process);
	uloop_fd_close(&task->output);

	if (task->input_fd != -1)
		close(task->input_fd);

	free(task);
}

static void close_pipe(void *ud)
{
	uc_uloop_pipe_t *pipe = ud;

	if (!pipe)
		return;

	close(pipe->input);
	close(pipe->output);

	free(pipe);
}

#ifdef HAVE_ULOOP_INTERVAL
static void close_interval(void *ud)
{
	uc_uloop_interval_t *interval = ud;

	if (!interval)
		return;

	uloop_interval_cancel(&interval->interval);
	free(interval);
}
#endif

#ifdef HAVE_ULOOP_SIGNAL
static void close_signal(void *ud)
{
	uc_uloop_signal_t *signal = ud;

	if (!signal)
		return;

	uloop_signal_delete(&signal->signal);
	free(signal);
}
#endif


static struct {
	struct uloop_fd ufd;
	uc_vm_t *vm;
} signal_handle;

static void
uc_uloop_vm_signal_cb(struct uloop_fd *ufd, unsigned int events)
{
	if (uc_vm_signal_dispatch(signal_handle.vm) != EXCEPTION_NONE)
		uloop_end();
}

void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
{
	int signal_fd;

	uc_function_list_register(scope, global_fns);

#define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))

	/**
	 * @typedef
	 * @name Event Mode Constants
	 * @description
	 * The `ULOOP_*` constants are passed as bitwise OR-ed number to the
	 * {@link module:uloop.handle#handle|handle()} function to specify the IO
	 * events that should be monitored on the given handle.
	 * @property {number} ULOOP_READ - File or socket is readable.
	 * @property {number} ULOOP_WRITE - File or socket is writable.
	 * @property {number} ULOOP_EDGE_TRIGGER - Enable edge-triggered event mode.
	 * @property {number} ULOOP_BLOCKING - Do not make descriptor non-blocking.
	 */
	ADD_CONST(ULOOP_READ);
	ADD_CONST(ULOOP_WRITE);
	ADD_CONST(ULOOP_EDGE_TRIGGER);
	ADD_CONST(ULOOP_BLOCKING);

	timer_type = uc_type_declare(vm, "uloop.timer", timer_fns, close_timer);
	handle_type = uc_type_declare(vm, "uloop.handle", handle_fns, close_handle);
	process_type = uc_type_declare(vm, "uloop.process", process_fns, close_process);
	task_type = uc_type_declare(vm, "uloop.task", task_fns, close_task);
	pipe_type = uc_type_declare(vm, "uloop.pipe", pipe_fns, close_pipe);

#ifdef HAVE_ULOOP_INTERVAL
	interval_type = uc_type_declare(vm, "uloop.interval", interval_fns, close_interval);
#endif

#ifdef HAVE_ULOOP_SIGNAL
	signal_type = uc_type_declare(vm, "uloop.signal", signal_fns, close_signal);
#endif

	object_registry = ucv_array_new(vm);

	uc_vm_registry_set(vm, "uloop.registry", object_registry);

	signal_fd = uc_vm_signal_notifyfd(vm);

	if (signal_fd != -1 && uloop_init() == 0) {
		signal_handle.vm = vm;
		signal_handle.ufd.cb = uc_uloop_vm_signal_cb;
		signal_handle.ufd.fd = signal_fd;

		uloop_fd_add(&signal_handle.ufd, ULOOP_READ);
	}
}