#ifndef EINA_THREAD_QUEUE_H_
#define EINA_THREAD_QUEUE_H_

/**
 * @addtogroup Eina_Thread_Queue_Group Thread Queue Group
 * @ingroup Eina
 *
 * @brief These functions provide simple zero-copy message queues for threads
 *
 * @{
 *
 * @since 1.11
 */

/**
 * @typedef Eina_Thread_Queue
 *
 * This is a uni-directional zero-copy thread message queue specifically
 * designed with the idea of sending large volumes of messages with no
 * copies from one thread to another (or from/to the mainloop). The idea
 * is that a thread queue is created and then one or more threads send
 * messages in one end and fetch messages off the other end. If you set a
 * parent message queue to 1 or more queues, then this parent will wake up
 * with a sub queue message, indicating which child queue woke up. This can
 * be used to implement the ability to listen to multiple queues at once.
 *
 * @since 1.11
 */
typedef struct _Eina_Thread_Queue Eina_Thread_Queue;

/**
 * @typedef Eina_Thread_Queue_Msg
 *
 * This is the minimal header of every message to be put into an Eina
 * Thread Queue. Every message has at least this header at the start of the
 * message data, with payload following. You would put this structure as
 * the first struct member of every message type you have, like
 * Eina_Thread_Queue_Msg_Sub does. Messages are always 8 byte aligned within
 * message memory to ensure alignment of all types.
 *
 * @since 1.11
 */
typedef struct _Eina_Thread_Queue_Msg Eina_Thread_Queue_Msg;

/**
 * @typedef Eina_Thread_Queue_Msg_Sub
 *
 * This is a special message type for Eina Thread Queues that have child
 * queues. This is the only Message type for a parent message queue and
 * it indicates which child queue was woken up with a new message to read.
 * When this message is retrieved, the caller should then also fetch the
 * message from the indicated child queue too.
 *
 * @since 1.11
 */
typedef struct _Eina_Thread_Queue_Msg_Sub Eina_Thread_Queue_Msg_Sub;

struct _Eina_Thread_Queue_Msg
{
   int size; /*< The size of the message in bytes, including this header */
};

struct _Eina_Thread_Queue_Msg_Sub
{
   Eina_Thread_Queue_Msg  head; /*< Standard header on all messages */
   Eina_Thread_Queue     *queue; /*< The child queue that woke up and needs a message fetched from it */
};

/**
 * @brief Creates a new thread queue.
 *
 * @return A valid new thread queue, or NULL on failure
 *
 * @since 1.11
 */
EINA_API Eina_Thread_Queue *
eina_thread_queue_new(void);

/**
 * @brief Frees a thread queue.
 *
 * This frees a thread queue. It must no longer be in use by anything waiting
 * on messages or sending them. Any pending messages will be freed without
 * being processed by a listener.
 *
 * @param[in] thq The thread queue to free
 *
 * @since 1.11
 */
EINA_API void
eina_thread_queue_free(Eina_Thread_Queue *thq) EINA_ARG_NONNULL(1);

/**
 * @brief Allocates a message to send down a thread queue.
 *
 * @param[in,out] thq The thread queue to allocate the message on
 * @param[in] size The size, in bytes, of the message, including standard header
 * @param[out] allocref A pointer to store a general reference handle for the message
 * @return A pointer to the message data to fill in
 *
 * This allocates space for a new message on the message queue, but does not
 * actually trigger the send. For that you will need to call
 * eina_thread_queue_send_done() to complete the send and trigger the other
 * side. Every message must at least be a Eina_Thread_Queue_Msg in size and
 * have this structure as the first member (first N bytes) of the message.
 *
 * @since 1.11
 */
EINA_API void *
eina_thread_queue_send(Eina_Thread_Queue *thq, int size, void **allocref) EINA_ARG_NONNULL(1, 3);

/**
 * @brief Finishes sending the allocated message.
 *
 * @param[in,out] thq The thread queue the message was placed on
 * @param[in,out] allocref The allocref returned by eina_thread_queue_send()
 *
 * This completes the send and triggers the thread queue to wake up any
 * listeners.
 *
 * @since 1.11
 */
EINA_API void
eina_thread_queue_send_done(Eina_Thread_Queue *thq, void *allocref) EINA_ARG_NONNULL(1, 2);

/**
 * @brief Fetches a message from a thread queue.
 *
 * @param[in,out] thq The thread queue to fetch the message from
 * @param[out] allocref A pointer to store a general reference handle for the message
 * @return A pointer to the message data
 *
 * This will fetch the next message to read from the thread queue and return
 * a pointer to it. The message is guaranteed to have an initial
 * Eina_Thread_Queue_Msg member that will indicate size of the message as
 * a whole. This function will wait, if no messages are available to read
 * and block until a new message comes in, then return. When the message is
 * finished with, the caller must use eina_thread_queue_wait_done() to indicate
 * they are done.
 *
 * @since 1.11
 */
EINA_API void *
eina_thread_queue_wait(Eina_Thread_Queue *thq, void **allocref) EINA_ARG_NONNULL(1, 2);

/**
 * @brief Finishes fetching a message from a thread queue.
 *
 * @param[in,out] thq The thread queue the message was fetched from
 * @param[in,out] allocref The allocref returned by eina_thread_queue_wait()
 *
 * This should be used after eina_thread_queue_wait() or
 * eina_thread_queue_poll() to indicate the caller is done with the message.
 *
 * @since 1.11
 */
EINA_API void
eina_thread_queue_wait_done(Eina_Thread_Queue *thq, void *allocref) EINA_ARG_NONNULL(1, 2);

/**
 * @brief Fetches a message from a thread queue, but return immediately if there is none with NULL.
 *
 * @param[in,out] thq The thread queue to fetch the message from
 * @param[out] allocref A pointer to store a general reference handle for the message
 * @return A pointer to the message data
 *
 * This is the same as eina_thread_queue_wait(), but if no messages are
 * available for reading, it immediately returns NULL to the caller, without
 * waiting for a new message to arrive.
 *
 * @see eina_thread_queue_wait()
 *
 * @since 1.11
 */
EINA_API void *
eina_thread_queue_poll(Eina_Thread_Queue *thq, void **allocref) EINA_ARG_NONNULL(1, 2);

/**
 * @brief Gets the number of messages on a queue as yet unfetched.
 *
 * @param[in] thq The thread queue to query for pending count
 * @return The number of messages waiting to be fetched
 *
 * This returns the number of messages waiting to be fetched with
 * eina_thread_queue_wait() or eina_thread_queue_poll().
 *
 * @since 1.11
 */
EINA_API int
eina_thread_queue_pending_get(const Eina_Thread_Queue *thq) EINA_ARG_NONNULL(1);

/**
 * @brief Sets the parent of a thread queue (make this one a child).
 *
 * @param[in,out] thq The thread queue to alter the parent of
 * @param[in] thq_parent The new parent to set
 *
 * This sets the parent queue where messages will be reported to. This is
 * how you can listen to multiple queues at once - set multiple queues to
 * have the same parent and then just wait on that one parent. This should
 * be done before any messages are read from or written to the queue. To unset
 * a parent, just set the parent to NULL.
 *
 * @since 1.11
 */
EINA_API void
eina_thread_queue_parent_set(Eina_Thread_Queue *thq, Eina_Thread_Queue *thq_parent) EINA_ARG_NONNULL(1);

/**
 * @brief Gets the parent of a thread queue.
 *
 * @param[in] thq The thread queue to get the parent of
 * @return The parent thread queue
 *
 * This gets the parent set by eina_thread_queue_parent_get(). If no parent
 * is set, NULL is returned.
 *
 * @see eina_thread_queue_parent_set()
 *
 * @since 1.11
 */
EINA_API Eina_Thread_Queue *
eina_thread_queue_parent_get(const Eina_Thread_Queue *thq) EINA_ARG_NONNULL(1);

/**
 * @brief Sets a file descriptor to write a byte to on a message send.
 *
 * @param[in,out] thq The thread queue to set the file descriptor of
 * @param[in] fd The fd to set, or -1 to unset it
 *
 * This sets a file descriptor to write to when a message is written to the
 * thread queue. This can be used to glue a thread queue to something like
 * an Ecore_Pipe that can wake up the mainloop and call a callback whenever
 * data is available on the pipe. The number of bytes available will be
 * the number of messages to fetch from the associated thread queue.
 *
 * You should set this up before anything writes to or reads from this
 * thread queue.
 *
 * @since 1.11
 */
EINA_API void
eina_thread_queue_fd_set(Eina_Thread_Queue *thq, int fd) EINA_ARG_NONNULL(1);

/**
 * @brief Gets the file descriptor written to on message sends.
 *
 * @param[in] thq The thread queue to get the file descriptor of
 * @return The file descriptor set (or -1 if none is set).
 *
 * This returns the file descriptor set by eina_thread_queue_fd_set() and
 * by default returns -1 (no fd set).
 *
 * @see eina_thread_queue_fd_set()
 *
 * @since 1.11
 */
EINA_API int
eina_thread_queue_fd_get(const Eina_Thread_Queue *thq) EINA_ARG_NONNULL(1);

/**
 * @}
 */
#endif
