/**
* Copyright (c) NVIDIA CORPORATION & AFFILIATES, 2001-2016. ALL RIGHTS RESERVED.
* Copyright (C) ARM Ltd. 2016-2017.  ALL RIGHTS RESERVED.
*
* See file LICENSE for terms.
*/

#ifndef UCS_CALLBACKQ_H
#define UCS_CALLBACKQ_H

#include <ucs/datastruct/list.h>
#include <ucs/sys/compiler_def.h>
#include <ucs/type/status.h>
#include <stddef.h>
#include <stdint.h>

BEGIN_C_DECLS

/** @file callbackq.h */

/*
 * Thread-safe callback queue:
 *  - only one thread can dispatch
 *  - any thread can add and remove
 *  - add/remove operations are O(1)
 */

#define UCS_CALLBACKQ_FAST_COUNT   7     /* Max. number of fast-path callbacks */
#define UCS_CALLBACKQ_ID_NULL      (-1)  /* Invalid callback identifier */


/*
 * Forward declarations
 */
typedef struct ucs_callbackq       ucs_callbackq_t;
typedef struct ucs_callbackq_elem  ucs_callbackq_elem_t;


/**
 * Callback which can be placed in a queue.
 *
 * @param [in] arg  User-defined argument for the callback.
 *
 * @return Count of how much "work" was done by the callback. For example, zero
 *         means that no work was done, and any nonzero value means that something
 *         was done.
 */
typedef unsigned (*ucs_callback_t)(void *arg);


/**
 * Callback queue element predicate.
 *
 * @param [in] elem  Callback queue element to check.
 * @param [in] arg   User-defined argument.
 *
 * @return Predicate result value - nonzero means "true", zero means "false".
 */
typedef int (*ucs_callbackq_predicate_t)(const ucs_callbackq_elem_t *elem,
                                         void *arg);


/**
 * @ingroup UCS_RESOURCE
 * Callback flags
 */
enum ucs_callbackq_flags {
    UCS_CALLBACKQ_FLAG_FAST        = UCS_BIT(0), /**< Fast-path (best effort) */
    UCS_CALLBACKQ_FLAG_ONESHOT     = UCS_BIT(1)  /**< Call the callback only once
                                                      (cannot be used with FAST) */
};


/**
 * Callback queue element.
 */
struct ucs_callbackq_elem {
    ucs_callback_t                 cb;       /**< Callback function */
    void                           *arg;     /**< Function argument */
    unsigned                       flags;    /**< Callback flags */
    int                            id;       /**< Callback id */
};


/**
 * A queue of callback to execute
 */
struct ucs_callbackq {
    /**
     * Array of fast-path element, the last is reserved as a sentinel to mark
     * array end.
     */
    ucs_callbackq_elem_t           fast_elems[UCS_CALLBACKQ_FAST_COUNT + 1];

    /**
     * Private data, which we don't want to expose in API to avoid pulling
     * more header files
     */
    char                           priv[72];
};


/**
 * Initialize the callback queue.
 *
 * @param  [in] cbq      Callback queue to initialize.
 */
ucs_status_t ucs_callbackq_init(ucs_callbackq_t *cbq);


/**
 * Clean up the callback queue and release associated memory.
 *
 * @param  [in] cbq      Callback queue to clean up.
 */
void ucs_callbackq_cleanup(ucs_callbackq_t *cbq);


/**
 * Add a callback to the queue.
 * This is *not* safe to call while another thread might be dispatching callbacks.
 * However, it can be used from the dispatch context (e.g a callback may use this
 * function to add another callback).
 *
 * @param  [in] cbq      Callback queue to add the callback to.
 * @param  [in] cb       Callback to add.
 * @param  [in] arg      User-defined argument for the callback.
 * @param  [in] flags    Flags for the callback, from  @ref ucs_callbackq_flags.
 *
 * @return Unique identifier of the callback in the queue.
 */
int ucs_callbackq_add(ucs_callbackq_t *cbq, ucs_callback_t cb, void *arg,
                      unsigned flags);


/**
 * Remove a callback from the queue immediately.
 * This is *not* safe to call while another thread might be dispatching callbacks.
 * However, it can be used from the dispatch context (e.g a callback may use this
 * function to remove itself or another callback). In this case, the callback may
 * still be dispatched once after this function returned.
 *
 * @param  [in] cbq      Callback queue to remove the callback from.
 * @param  [in] id       Callback identifier to remove.
 */
void ucs_callbackq_remove(ucs_callbackq_t *cbq, int id);


/**
 * Add a callback to the queue.
 * This can be used from any context and any thread, including but not limited to:
 * - A callback can add another callback.
 * - A thread can add a callback while another thread is dispatching callbacks.
 *
 * @param  [in] cbq      Callback queue to add the callback to.
 * @param  [in] cb       Callback to add.
 * @param  [in] arg      User-defined argument for the callback.
 * @param  [in] flags    Flags for the callback, from  @ref ucs_callbackq_flags.
 *
 * @return Unique identifier of the callback in the queue.
 */
int ucs_callbackq_add_safe(ucs_callbackq_t *cbq, ucs_callback_t cb, void *arg,
                           unsigned flags);


/**
 * Remove a callback from the queue in a safe but lazy fashion. The callback will
 * be removed at some point in the near future.
 * This can be used from any context and any thread, including but not limited to:
 * - A callback can remove another callback or itself.
 * - A thread can't remove a callback while another thread is dispatching callbacks.
 *
 * @param  [in] cbq      Callback queue to remove the callback from.
 * @param  [in] id       Callback identifier to remove.
 */
void ucs_callbackq_remove_safe(ucs_callbackq_t *cbq, int id);


/**
 * Remove all callbacks from the queue for which the given predicate returns
 * "true" (nonzero) value.
 * This is *not* safe to call while another thread might be dispatching callbacks.
 * However, it can be used from the dispatch context (e.g a callback may use this
 * function to remove itself or another callback). In this case, the callback may
 * still be dispatched once after this function returned.
 *
 * @param  [in] cbq       Callback queue.
 * @param  [in] pred      Predicate to check candidates for removal.
 * @param  [in] arg       User-defined argument for the predicate.
 */
void ucs_callbackq_remove_if(ucs_callbackq_t *cbq, ucs_callbackq_predicate_t pred,
                             void *arg);


/**
 * Dispatch callbacks from the callback queue.
 * Must be called from single thread only.
 *
 * @param  [in] cbq      Callback queue to dispatch callbacks from.

 * @return Sum of all return values from the dispatched callbacks.
 */
static inline unsigned ucs_callbackq_dispatch(ucs_callbackq_t *cbq)
{
    ucs_callbackq_elem_t *elem;
    ucs_callback_t cb;
    unsigned count;

    count = 0;
    for (elem = cbq->fast_elems; (cb = elem->cb) != NULL; ++elem) {
        count += cb(elem->arg);
    }
    return count;
}

END_C_DECLS

#endif
