#ifndef _select_helper_h__
#define _select_helper_h__

/** select_helper **
 *
 * Portable convenience wrapper over the select() system call.
 * 
 * The benefits of using this module over standard select() are:
 * 
 *   - Uniform behavior across platforms. For example: on Windows, select() ignores the
 *     timeout and returns immediately when all fd_sets are empty, whereas on Linux,
 *     select() sleeps for the timeout duration even when all fd_sets are empty.
 *     This module enforces Linux-like behavior, even on Windows.
 * 
 *   - Improved timing accuracy. Dracal applications benefit from precise timing. This
 *     module automatically fetches the current system time after select() returns,
 *     giving the most accurate timestamp at which data was available. The rest of the
 *     application then has immediate access to the latest timestamp, stored in the module,
 *     without having to query the system clock. The alternative would be that each part
 *     of the application that needs the current time would issue its own system call. This
 *     would result in multiple expensive system calls, hurting performance.
 * 
 *   - Automatic bookkeeping of the fd_sets. Users of standard select() know that before
 *     each call, the "master" fd_sets must be kept separate from the actual fd_sets that
 *     are passed to select(), since the latter will only contain the selected fds after
 *     the call. This implies a copy of all fd_sets before each call to select(). This
 *     module does it automatically.
 * 
 *   - Convenient bookkeeping of the `nfds` argument to select().
 * 
 * The module uses a struct select_helper_data which holds the various fd_sets, the `nfds`
 * counter, as well as the timestamp of the latest return from a select() call.
 * 
 * The macro select_helper_nfds_update() should be used whenever adding a new file descriptor
 * to one of the fd_sets. It will ensure that the `nfds` counter is always the maximum fd + 1.
 * 
 * Last but not least: the select_helper() function replaces select().
 */

#ifdef _WIN32
    #include <winsock2.h>
#else
    #include <sys/select.h>
#endif

#include "timestamp.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
 * Struct which holds the various fd_sets, the `nfds` counter, as well as the timestamp of
 * the latest return from a select() call.
 * 
 * readfds: the master fd_set for reading
 * writefds: the master fd_set for writing
 * exceptfds: the master fd_set for exceptional conditions
 * 
 * Use the standard macros FD_CLR() FD_SET() FD_ISSET() FD_ZERO() on the master fd_sets.
 * 
 * readfds_ready: the fd_set retained by select() for reading
 * writefds_ready: the fd_set retained by select() for reading
 * exceptfds_ready: the fd_set retained by select() for exceptional conditions
 * 
 * It only makes sense to use FD_ISSET() on the "ready" fd_sets.
 * 
 * nfds: maximum file descriptor + 1. Use macro select_helper_nfds_update() to update.
 * 
 * timestamp: system time (microseconds from Epoch) immediately after select() returned
 */
typedef struct select_helper_data {

    int nfds;

    fd_set readfds;
    fd_set writefds;
    #ifdef _WIN32
        fd_set exceptfds;
    #endif

    fd_set readfds_ready;
    fd_set writefds_ready;
    #ifdef _WIN32
        fd_set exceptfds_ready;
    #endif

    struct timespec tp;
    int64_t timestamp;

} select_helper_data;

/**
 * Ensure that seldata->nfds is strictly greater than fd.
 * Call this macro every time you add a new fd to a master fd_set.
 */
#ifdef _WIN32
    #define select_helper_nfds_update(fd, seldata)
#else
    #define select_helper_nfds_update(fd, seldata) \
        if ((seldata)->nfds <= (fd)) { \
            (seldata)->nfds = (fd) + 1; \
        }
#endif

/**
 * Portable convenience wrapper over the select() system call.
 * 
 * Arguments:
 * 
 *   seldata: pointer to struct select_helper_data
 *   timeout: same as standard select()
 * 
 * Return: same as standard select()
 * 
 */
int select_helper(select_helper_data *seldata, struct timeval *timeout);


#ifdef __cplusplus
}
#endif

#endif
