/**
* Copyright (C) Mellanox Technologies Ltd. 2001-2014.  ALL RIGHTS RESERVED.
*
* See file LICENSE for terms.
*/

#ifndef UCS_QUEUE_H_
#define UCS_QUEUE_H_

#include "queue_types.h"

#include <ucs/debug/assert.h>
#include <stddef.h>


/**
 * Initialize a queue.
 *
 * @param queue  Queue to initialize.
 */
static inline void ucs_queue_head_init(ucs_queue_head_t *queue)
{
#ifdef __clang_analyzer__
    queue->head  = (ucs_queue_elem_t*)(void*)queue;
#endif
    queue->ptail = &queue->head;
}

/**
 * @return Queue length.
 */
static inline size_t ucs_queue_length(ucs_queue_head_t *queue)
{
    ucs_queue_elem_t **pelem;
    size_t length;

    length = 0;
    for (pelem = &queue->head; pelem != queue->ptail; pelem = &(*pelem)->next) {
        ++length;
    }
    return length;
}

/**
 * @return Whether the given element is the tail element in the queue.
 */
static inline int
ucs_queue_is_tail(ucs_queue_head_t *queue, ucs_queue_elem_t *elem)
{
    return queue->ptail == &elem->next;
}

/**
 * @return Whether the queue is empty.
 */
static inline int ucs_queue_is_empty(const ucs_queue_head_t *queue)
{
    return queue->ptail == &queue->head;
}

/**
 * Enqueue an element to the tail of the queue.
 *
 * @param queue  Queue to add to.
 * @param elem   Element to add.
 */
static inline void ucs_queue_push(ucs_queue_head_t *queue, ucs_queue_elem_t *elem)
{
    *queue->ptail = elem;
    queue->ptail = &elem->next;
#if UCS_ENABLE_ASSERT
    elem->next = NULL; /* For sanity check below */
#endif
}

/**
 * Add an element to the head of the queue.
 *
 * @param queue  Queue to add to.
 * @param elem   Element to add.
 */
static inline void ucs_queue_push_head(ucs_queue_head_t *queue,
                                       ucs_queue_elem_t *elem)
{
    elem->next = queue->head;
    queue->head = elem;
    if (ucs_queue_is_empty(queue)) {
        queue->ptail = &elem->next;
    }
}

/**
 * Dequeue an element from the head of the queue, assuming the queue is not empty.
 *
 * @param queue  Non-empty queue to pull from.
 * @return  Element from the head of the queue.
 */
static inline ucs_queue_elem_t *ucs_queue_pull_non_empty(ucs_queue_head_t *queue)
{
    ucs_queue_elem_t *elem;

    elem = queue->head;
    queue->head = elem->next;
    if (ucs_queue_is_tail(queue, elem)) {
        queue->ptail = &queue->head;
    }
    return elem;
}

/**
 * Delete an element.
 * The element must be valid when deleting it.
 * After the call, iter points to the next element, and the element may be released.
 */
static inline void ucs_queue_del_iter(ucs_queue_head_t *queue, ucs_queue_iter_t iter)
{
    ucs_assert((iter != NULL) && (*iter != NULL));

    if (ucs_queue_is_tail(queue, *iter)) {
        queue->ptail = iter; /* deleting the last element */
        *iter = NULL;        /* make *ptail point to NULL */
    } else {
        *iter = (*iter)->next;
    }

    /* Sanity check */
    ucs_assertv((queue->head != NULL) || ucs_queue_is_empty(queue),
                "head=%p ptail=%p &head=%p iter=%p", queue->head, queue->ptail,
                &queue->head, iter);

    /* If the queue is empty, head must point to null */
    ucs_assertv(!ucs_queue_is_empty(queue) || (queue->head == NULL),
                "head=%p ptail=%p &head=%p iter=%p", queue->head, queue->ptail,
                &queue->head, iter);
}

/**
 * Dequeue an element from the head of the queue.
 *
 * @param queue  Queue to pull from.
 * @return  Element from the head of the queue, or NULL if the queue is empty.
 */
static inline ucs_queue_elem_t *ucs_queue_pull(ucs_queue_head_t *queue)
{
    if (ucs_queue_is_empty(queue))
        return NULL;
    return ucs_queue_pull_non_empty(queue);
}

/**
 * Insert all elements from one queue to another queue, leaving the first queue
 * empty.
 *
 * @param queue     Queue to push elements to.
 * @param new_elems Queue of elements to add.
 */
static inline void ucs_queue_splice(ucs_queue_head_t *queue,
                                    ucs_queue_head_t *new_elems)
{
    if (!ucs_queue_is_empty(new_elems)) {
        *queue->ptail = new_elems->head;
        queue->ptail = new_elems->ptail;
        new_elems->ptail = &new_elems->head;
    }
}

/**
 * Convenience macro to pull from a non-empty queue and return the containing element.
 *
 * @param queue   Non-empty queue to pull from.
 * @param type    Container element type.
 * @param member  Queue element member inside the container.
 *
 * @return Pulled element.
 */
#define ucs_queue_pull_elem_non_empty(queue, type, member) \
    ucs_container_of(ucs_queue_pull_non_empty(queue), type, member)

/**
 * Convenience macro to get the head element of a non-empty queue.
 *
 * @param queue   Non-empty queue whose head element to get.
 * @param type    Container element type.
 * @param member  Queue element member inside the container.
 *
 * @return Head element.
 */
#define ucs_queue_head_elem_non_empty(queue, type, member) \
    ucs_container_of((queue)->head, type, member)

/**
 * Convenience macro to get the tail element of a non-empty queue.
 *
 * @param queue   Non-empty queue whose head element to get.
 * @param type    Container element type.
 * @param member  Queue element member inside the container.
 *
 * @return Head element.
 */
#define ucs_queue_tail_elem_non_empty(queue, type, member) \
    ucs_container_of((queue)->ptail, type, member)

/**
 * Iterate over queue elements. The queue must not be modified during the iteration.
 *
 * @param elem    Variable which will hold point to the element in the queue.
 * @param queue   Queue to iterate on.
 * @param member  Member inside 'elem' which is the queue link.
 */
#define ucs_queue_for_each(elem, queue, member) \
    /* we set `ptail` field to queue address to not subtract NULL pointer */ \
    for (*(queue)->ptail = (ucs_queue_elem_t*)(void*)(queue), \
             elem = ucs_container_of((queue)->head, ucs_typeof(*elem), member); \
         (UCS_PTR_BYTE_OFFSET(elem, ucs_offsetof(ucs_typeof(*elem), member)) != \
             (void*)(queue)); \
         elem = ucs_container_of(elem->member.next, ucs_typeof(*elem), member))

/**
 * Iterate over queue elements. The current element may be safely removed from
 * the queue using ucs_queue_del_iter().
 *
 * @param elem    Variable which will hold point to the element in the queue.
 * @param iter    Iterator variable. May be passed to ucs_queue_del_iter().
 * @param queue   Queue to iterate on.
 * @param member  Member inside 'elem' which is the queue link.
 */
#define ucs_queue_for_each_safe(elem, iter, queue, member) \
    for (iter = &(queue)->head, \
         elem = ucs_container_of(*iter, ucs_typeof(*elem), member); \
          iter != (queue)->ptail; \
          iter = (*iter == &elem->member) ? &(*iter)->next : iter, \
            elem = ucs_container_of(*iter, ucs_typeof(*elem), member))

/**
 * Iterate and extract elements from the queue while a condition is true.
 *
 * @param elem    Variable which will hold point to the element in the queue.
 * @param queue   Queue to iterate on.
 * @param member  Member inside 'elem' which is the queue link.
 * @param cond    Condition to continue iterating.
 *
 * TODO optimize
 */
#define ucs_queue_for_each_extract(elem, queue, member, cond) \
    for (elem = ucs_container_of((queue)->head, ucs_typeof(*elem), member); \
         \
         !ucs_queue_is_empty(queue) && (cond) && ucs_queue_pull_non_empty(queue); \
         \
         elem = ucs_container_of((queue)->head, ucs_typeof(*elem), member))


/*
 * Queue iteration
 */

static inline ucs_queue_iter_t ucs_queue_iter_begin(ucs_queue_head_t *q)
{
    return &q->head;
}

static inline ucs_queue_iter_t ucs_queue_iter_next(ucs_queue_iter_t i)
{
    return  &(*i)->next;
}

static inline int ucs_queue_iter_end(ucs_queue_head_t *q, ucs_queue_iter_t i)
{
    return i == q->ptail;
}

static inline void ucs_queue_remove(ucs_queue_head_t *queue, ucs_queue_elem_t *elem)
{
    ucs_queue_iter_t iter = ucs_queue_iter_begin(queue);

    while (!ucs_queue_iter_end(queue, iter)) {
        if (*iter == elem) {
            ucs_queue_del_iter(queue, iter);
            return;
        }
        iter = ucs_queue_iter_next(iter);
    }
}

#define ucs_queue_iter_elem(elem, iter, member) \
    ucs_container_of(*iter, typeof(*elem), member)

#endif
