/**
 * @file
 * @brief io_context: I/O routines interface
 * @copyright (C) 2012-2019, Samsung Electronics Co., Ltd.
**/

#pragma once

#include <stdint.h>
#include <sys/types.h>
#include <sys/uio.h>

#include <dso_defs.h>

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

struct io_context;

#define IO_POLL_INPUT      (UINT32_C(1) << UINT32_C(0)) /** handle is readable **/
#define IO_POLL_OUTPUT     (UINT32_C(1) << UINT32_C(1)) /** handle is writable **/
#define IO_POLL_URGENT     (UINT32_C(1) << UINT32_C(2)) /** out-of-band data available **/
#define IO_POLL_HANGUP     (UINT32_C(1) << UINT32_C(3)) /** remote peer one-way shutdown **/
#define IO_POLL_ERROR      (UINT32_C(1) << UINT32_C(4)) /** undetermined connection error **/
#define IO_POLL_MASK       (IO_POLL_INPUT \
                           |IO_POLL_OUTPUT \
                           |IO_POLL_URGENT \
                           |IO_POLL_HANGUP \
                           |IO_POLL_ERROR)

#define IO_FLAG_BLOCK      (UINT32_C(1) << UINT32_C(0x00)) /** operate in blocking mode **/
#define IO_FLAG_OOB        (UINT32_C(1) << UINT32_C(0x01)) /** operate over out-of-band channel **/
#define IO_FLAG_EOF        (UINT32_C(1) << UINT32_C(0x02)) /** remote peer shutdown indicator **/
#define IO_FLAG_EDGE       (UINT32_C(1) << UINT32_C(0x03)) /** edge-triggered mode switch **/
#define IO_FLAG_ONESHOT    (UINT32_C(1) << UINT32_C(0x04)) /** oneshot mode switch **/
#define IO_FLAG_RXLIMIT    (UINT32_C(1) << UINT32_C(0x05)) /** modify/query receive limit **/
#define IO_FLAG_TXLIMIT    (UINT32_C(1) << UINT32_C(0x06)) /** modify/query transmit limit **/
#define IO_FLAG_ERROR      (UINT32_C(1) << UINT32_C(0x1E)) /** undetermined connection error **/
#define IO_FLAG_IWD        (UINT32_C(1) << UINT32_C(0x1F)) /** interworld socket indicator **/

/** @brief I/O event postscriptum **/
struct io_hooks {
    /**
     * @brief Input event callback
     * @param id handle identifier
     * @param uctx user-defined context
     * @param flags I/O flags
     *
     * It is a programming error to simply close handle here.
     * Handle can be ejected from the I/O context instance though.
     * To release resources, including handle, use cleanup hook.
     * The latter is called automatically upon handle ejection.
     * In case this hook returns negative value, handle is ejected as well.
     *
     * IO_FLAG_OOB
     *     This flag indicates presence of out-of-band data.
     *     It doesn't imply that regular data is not available as well.
     *     The caller might need to check regular data as well.
     *     The most reliable way is to check in a non-blocking mode.
     *
     * IO_FLAG_EOF
     *     This flag indicates that peer performed shutdown.
     *     It doesn't imply that data is not available at this moment.
     *     The caller might need to check data in a non-blocking mode.
     *     This includes both regular and out-of-band data channels.
     *
     * IO_FLAG_BLOCK
     *     This flag indicates that handle operates in blocking mode by default.
     *     It doesn't imply that operations should be always in this mode.
     *     That is, this flag is a hint, inherited upon injection, nothing more.
     *
     * IO_FLAG_IWD
     *     This flag is active on client connections to IWd servers.
     *     It is provided to simplify protocol-agnostic code.
    **/
    int (*const input)(int id, void *uctx, uint32_t flags);

    /**
     * @brief Output event callback
     * @param id handle identifier
     * @param uctx user-defined context
     * @param flags I/O flags
     *
     * It is a programming error to simply close handle here.
     * Handle can be ejected from the I/O context instance though.
     * To release resources, including handle, use cleanup hook.
     * The latter is called automatically upon handle ejection.
     * In case this hook returns negative value, handle is ejected as well.
     *
     * IO_FLAG_BLOCK
     *     This flag indicates that handle operates in blocking mode by default.
     *     It doesn't imply that operations should be always in this mode.
     *     That is, this flag is a hint, inherited upon injection, nothing more.
     *
     * IO_FLAG_IWD
     *     This flag is active on client connections to IWd servers.
     *     It is provided to simplify protocol-agnostic code.
    **/
    int (*const output)(int id, void *uctx, uint32_t flags);

    /**
     * @brief Hangup event callback
     * @param id handle identifier
     * @param uctx user-defined context
     * @param flags I/O flags
     *
     * It is a programming error to simply close handle here.
     * Handle can be ejected from the I/O context instance though.
     * To release resources, including handle, use cleanup hook.
     * The latter is called automatically upon handle ejection.
     * In case this hook returns negative value, handle is ejected as well.
     *
     * The main use case for this hook is to handle connection hangup.
     * For example, this handle can be used to restart the task which exited.
     * Another use case is journaling clients which got disconnected.
     * Do not ever attempt to use this handle as cleanup substitution!
     *
     * IO_FLAG_IWD
     *     This flag is active on client connections to IWd servers.
     *     It is provided to simplify protocol-agnostic code.
    **/
    int (*const hangup)(int id, void *uctx, uint32_t flags);

    /**
     * @brief Cleanup callback
     * @param id handle identifier
     * @param uctx user-defined context
     * @param flags I/O flags
     *
     * This hook is called once I/O context lost track of the handle.
     * It is therefore perfectly legal to release resources here.
     * At this point, handle identifier can be closed and context can be freed.
     *
     * IO_FLAG_IWD
     *     This flag is active on client connections to IWd servers.
     *     It is provided to simplify protocol-agnostic code.
    **/
    void (*const cleanup)(int id, void *uctx, uint32_t flags);
};

struct io_options {
    size_t rxlimit; /** Rx limit in bytes **/
    size_t txlimit; /** Tx limit in bytes **/
};

/** @brief I/O event postscriptum **/
struct io_event {
    int32_t id;     /** handle identifier **/
    uint32_t flags; /** event flags **/
    void *uctx;     /** user-defined context **/
    void *hook;     /** opaque I/O hook **/
};

/** @brief I/O message **/
struct io_message {
    void *cred_data;         /** credentials data **/
    size_t cred_size;        /** credentials size **/
    struct iovec *io_vector; /** I/O chunks vector **/
    int *id_vector;          /** identifiers vector **/
    size_t io_count;         /** count of I/O chunks **/
    size_t id_count;         /** identifiers count **/
};

DSO_EXPORT
int io_context(struct io_context **io);

/**
 * @param Wait for events of interest and report them
 * @param id handle identifier
 * @param events events of interest (IO_POLL_* mask)
 * @param result events that occurred (IO_POLL_* mask)
 * @param timeout timeout in nanoseconds
**/
DSO_EXPORT
int io_poll(int id, uint32_t events, uint32_t *result, int64_t timeout);

/**
 * @brief Wait for an I/O event and handle it
 * @param ioctx I/O context
 * @param timeout timeout in nanoseconds
 * @param event optional event postscriptum
 * @return I/O hook result or negative error code
 *
 * The event argument is optional and used to help caller differentiate events.
 * This is a postfactum report; that is, event has already been handled.
 * Most scenarios don't need it; in some exceptional cases, it can be useful though.
**/
DSO_EXPORT
int io_await(struct io_context *ioctx, int64_t timeout, struct io_event *event);

/**
 * @brief Inject handle identifier
 * @param ioctx I/O context instance
 * @param id handle identifier
 * @param uctx user-defined context
 * @param flags I/O flags
 * @param hooks I/O hooks
 * @return zero or negative error code
 *
 * IO_FLAG_OOB
 *    If input hook is present, handle events on out-of-band channel.
 *    In this case, if out-of-band data is available, it is noted in hook.
 *    If input hook is not present, report an error.
 *
 * IO_FLAG_EDGE
 *    If this flag is active, operate in edge-triggered mode.
 *    That is, handle events are not reported unless handle changes its state.
 *    Do not employ this mode unless you really understand how it works.
 *
 * IO_FLAG_ONESHOT
 *    If this flag is active, deactivate handle once all simultaneous events are handled.
 *
 * Note that using IO_FLAG_ONESHOT will mark the handle as inactive after any event.
 * Such handles must be rearmed again with subsequent io_inject calls.
**/
DSO_EXPORT
int io_inject(struct io_context *ioctx, int id,
        void *uctx, uint32_t flags, const struct io_hooks *hooks);

/**
 * @brief Eject handle identifier
 * @param ioctx I/O context instance
 * @param id handle identifier
 * @return zero or negative error code
**/
DSO_EXPORT
int io_eject(struct io_context *ioctx, int id);

/**
 * @brief Accept client connection
 * @param id handle identifier
 * @param flags I/O flags
 * @param cred_data credentials data
 * @param cred_size credentials size
 * @param ... (optional) custom options
 * @return handle identifier or negative error code
 *
 * IO_FLAG_BLOCK
 *     If this flag is active, connected client will operate in blocking mode.
 *
 * IO_FLAG_IWD
 *     This flag MUST be active if operation is performed on interworld socket.
 *
 * IO_FLAG_CUSTOM
 *     If this flag is present, the underlying socket is adjusted accordingly.
**/
DSO_EXPORT
int io_accept(int id, uint32_t flags, void *cred_data, size_t *cred_size, ...);

/**
 * @brief A quick factory for local or interworld server
 * @param ioctx I/O context instance
 * @param id server descriptor
 * @param uctx user-defined context
 * @param flags I/O flags
 * @param accept accept handler
 * @return handle identifier or negative error code
 *
 * IO_FLAG_BLOCK
 *     If this flag is active, connected clients will operate in blocking mode.
 *     At the same time, any client may opt to override this behavior in accept.
 *
 * IO_FLAG_IWD
 *     This flag MUST be active if operation is performed on interworld socket.
**/
DSO_EXPORT
int io_server(struct io_context *ioctx,
        int id, void *uctx, uint32_t flags,
        int (*accept)(int id, void const *cred_data, size_t cred_size,
            void **uctx, const struct io_hooks **client, uint32_t *flags));

/**
 * @brief Announce local or interworld server
 * @param path server path
 * @param flags I/O flags
 * @param timeout timeout in nanoseconds
 * @param ... (optional) custom options
 * @return handle identifier or negative error code
 *
 * IO_FLAG_BLOCK
 *     If this flag is active, operation will block until completion.
 *     The same effect can be achieved by employing a negative timeout.
 *
 * IO_FLAG_IWD
 *     This flag MUST be active if operation is performed on interworld socket.
 *
 * IO_FLAG_CUSTOM
 *     If this flag is present, the underlying socket is adjusted accordingly.
**/
DSO_EXPORT
int io_announce(const char *path, uint32_t flags, int64_t timeout, ...);

/**
 * @brief Dial local or interworld server
 * @param path server path
 * @param flags I/O flags
 * @param timeout timeout in nanoseconds
 * @param ... (optional) custom options
 * @return handle identifier or negative error code
 *
 * IO_FLAG_BLOCK
 *     If this flag is active, operation will block until completion.
 *     The same effect can be achieved by employing a negative timeout.
 *
 * IO_FLAG_IWD
 *     This flag MUST be active if operation is performed on interworld socket.
 *
 * IO_FLAG_CUSTOM
 *     If this flag is present, the underlying socket is adjusted accordingly.
**/
DSO_EXPORT
int io_dial(const char *path, uint32_t flags, int64_t timeout, ...);

/**
 * @brief Receive I/O message
 * @param id handle identifier
 * @param message I/O message
 * @param flags I/O flags
 * @return count of bytes received or negative error code
 *
 * IO_FLAG_BLOCK
 *     If this flag is active, operation will block until completion.
 *
 * IO_FLAG_OOB
 *     If this flag is active, operate over out-of-band I/O.
 *
 * IO_FLAG_IWD
 *     This flag MUST be active if operation is performed on interworld socket.
**/
DSO_EXPORT
ssize_t io_receive(int id, struct io_message *message, uint32_t flags);

/**
 * @brief Transmit I/O message
 * @param id handle identifier
 * @param message I/O message
 * @param flags I/O flags
 * @return count of bytes transferred or negative error code
 *
 * IO_FLAG_BLOCK
 *     If this flag is active, operation will block until completion.
 *
 * IO_FLAG_OOB
 *     If this flag is active, operate over out-of-band I/O.
 *
 * IO_FLAG_IWD
 *     This flag MUST be active if operation is performed on interworld socket.
**/
DSO_EXPORT
ssize_t io_transmit(int id, const struct io_message *message, uint32_t flags);

/**
 * @brief Query and/or update I/O options
 * @param id handle identifier
 * @param flags I/O flags
 * @param new_options (optional) new options to be applied
 * @param old_options (optional) old options to be saved
 * @return zero or negative error code
 *
 * IO_FLAG_IWD
 *     This flag MUST be active if operation is performed on interworld socket.
**/
DSO_EXPORT
int io_options(int id, uint32_t flags,
        const struct io_options *new_options,
        struct io_options *old_options);

/**
 * @brief Destroy I/O context
 * @param ioctx I/O context instance
**/
DSO_EXPORT
void io_destroy(struct io_context *ioctx);

#ifdef __cplusplus
} /* extern "C" */
#endif /* __cplusplus */
