/**
 * Copyright (c) 2016 - 2020 Nordic Semiconductor ASA and Luxoft Global Operations Gmbh.
 *
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * 
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#ifndef SYS_LIST_H_INCLUDED
#define SYS_LIST_H_INCLUDED

/** @file
 * This file contains declarations of the doubly linked list primitives and necessary types.
 * This implementation is Linux-proven and used in the memory management module.
 *
 * @defgroup sys_list Doubly linked list API.
 * @ingroup sys_15_4
 * @{
 * @brief Module to declare the doubly linked list API.
 */

/**
 * Internal list "head" struct.
 */
struct sys_list_head
{
    struct sys_list_head * next;
    struct sys_list_head * prev;
};

typedef struct sys_list_head sys_list_head_t;


/**
 * @brief Initializes a list by variable name.
 * @warning this macro assumes that a list "head" (sys_list_head_t) variable
 * with name \a name is already created.
 *
 * @param[inout] name The "head" struct name.
 */
#define LIST_HEAD_INIT(name) { &(name), &(name) }

/**
 * @brief Defines and initializes a new list.
 * @details A call to this macro creates a new variable with the given name and
 * initializes it as a list "head".
 *
 * @param[inout] name The "head" struct name.
 */
#define LIST_HEAD(name) sys_list_head_t name = { &(name), &(name) }


/**
 * @brief Initializes a list by pointer.
 *
 * @param[inout] ptr Pointer to a list.
 */
#define INIT_LIST_HEAD(ptr)                         \
    do                                              \
    {                                               \
        (ptr)->prev = (ptr);                        \
        (ptr)->next = (ptr);                        \
    } while (0)


/**
 * @brief Checks if a list is empty.
 *
 * @param[in] sys_list_head Pointer to a list.
 * @return 0 if not empty, non-zero otherwise.
 */
#define IS_EMPTY(sys_list_head) (sys_list_head)->next == (sys_list_head)


/**
 * @brief Adds a new item to the list between \a l_prev and \a l_next elements.
 * @warning This routine assumes that \a l_next is next to \a l_prev in the list.
 * @note This is an internal helper routine which is not intended to be used by the user.
 *
 * @param[in] l_prev Pointer to the previous element.
 * @param[in] l_next Pointer to the next element.
 * @param[in] l_new  Pointer to a new element.
 */
static inline void sys_ll_list_add(sys_list_head_t * l_prev,
                                   sys_list_head_t * l_next,
                                   sys_list_head_t * l_new)
{
    l_new->prev = l_prev;
    l_prev->next = l_new;
    l_next->prev = l_new;
    l_new->next = l_next;
}

/**
 * @brief Deletes an element between \a l_prev and \a l_next elements.
 * @warning This macro assumes that \a l_next is next to \a l_prev in the list.
 * @note This is an internal helper routine which is not intended to be used by the user.
 *
 * @param[in] l_prev Pointer to the previous element.
 * @param[in] l_next Pointer to the next element.
 */
static inline void sys_ll_list_del(sys_list_head_t * l_next,
                                   sys_list_head_t * l_prev)
{
    l_next->prev = l_prev;
    l_prev->next = l_next;
}

/**
 * @brief Function for adding a new item to the head of the list.
 *
 * @param[in] new  Pointer to a new element.
 * @param[in] head Pointer to the list head.
 */
static inline void sys_list_add(sys_list_head_t * new, sys_list_head_t * head)
{
    sys_ll_list_add(head, head->next, new);
}


/**
 * @brief Function for adding a new item to the tail of the list.
 *
 * @param[in] new  Pointer to a new element.
 * @param[in] head Pointer to the list head.
 */
static inline void sys_list_add_tail(sys_list_head_t * new, sys_list_head_t * head)
{
    sys_ll_list_add(head->prev, head, new);
}


/**
 * @brief Function for deleting an entry from list.
 *
 * @param[in] entry The element to delete from the list.
 */
static inline void sys_list_del(sys_list_head_t * entry)
{
    sys_ll_list_del(entry->next, entry->prev);
}


/**
 * @brief Function for deleting an entry from the list and reinitializing it.
 *
 * @param[in] entry The element to delete from the list.
 */
static inline void sys_list_del_init(sys_list_head_t * entry)
{
    sys_ll_list_del(entry->next, entry->prev);
    INIT_LIST_HEAD(entry);
}


/**
 * @brief Function for testing if a list is empty.
 
 * @param[in] head The list to test.
 * @return 0 if not empty, non-zero otherwise.
 */
static inline unsigned int sys_list_empty(sys_list_head_t * head)
{
    return IS_EMPTY(head);
}


/**
 * @brief Sets a pointer to a variable to the parent structure pointer using a
 * pointer to a field in this structure.
 *
 * @note This is a version of @ref GET_PARENT_BY_FIELD() extended by setting to a variable.
 *
 * @param[out] ll_ret_var Variable pointer name to return.
 * @param[in] ll_ptr      Pointer to the structure field.
 * @param[in] ll_type     Name of the parent structure.
 * @param[in] ll_member   Name of the structure field.
 */
#define SYS_LIST_ENTRY(ll_ret_var, ll_ptr, ll_type, ll_member)       \
    do                                                               \
    {                                                                \
        size_t  p = (size_t) ll_ptr;                                 \
        size_t  off = offsetof(ll_type, ll_member);                  \
        ll_ret_var = (ll_type *) (p - off);                          \
    } while (0)


/**
 * @brief Iterates through the list.
 * @note Use @ref SYS_LIST_FOR_EACH_SAFE() for thread-safe cases.
 *
 * @param[out] pos Iterator variable.
 * @param[in] head Pointer to the list head.
 */
#define SYS_LIST_FOR_EACH(pos, head)                                 \
    for (pos = ((head)->next);                                       \
         ((pos) != (head));                                          \
         pos = (pos)->next)


/**
 * @brief Thread-safe version of @ref SYS_LIST_FOR_EACH().
 *
 * @param[out] ll_pos   Iterator variable.
 * @param[out] ll_pos_n Temporary iterator variable (next entry).
 * @param[in] ll_head   Pointer to the list head.
 */
#define SYS_LIST_FOR_EACH_SAFE(ll_pos, ll_pos_n, ll_head)            \
    for (ll_pos = (ll_head)->next, ll_pos_n = (ll_head)->next->next; \
        (ll_pos) != (ll_head);                                       \
         ll_pos = ll_pos_n, ll_pos_n = ll_pos->next)

/** @} */

#endif /* SYS_LIST_H_INCLUDED */
