/*
    ChibiOS - Copyright (C) 2006,2007,2008,2009,2010,2011,2012,2013,2014,
              2015,2016,2017,2018,2019,2020,2021 Giovanni Di Sirio.

    This file is part of ChibiOS.

    ChibiOS is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation version 3 of the License.

    ChibiOS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * @file    chlists.h
 * @brief   Lists and Queues header.
 *
 * @addtogroup os_lists
 * @{
 */

#ifndef CHLISTS_H
#define CHLISTS_H

/*===========================================================================*/
/* Module constants.                                                         */
/*===========================================================================*/

/*===========================================================================*/
/* Module pre-compile time settings.                                         */
/*===========================================================================*/

/*===========================================================================*/
/* Derived constants and error checks.                                       */
/*===========================================================================*/

/*===========================================================================*/
/* Module data structures and types.                                         */
/*===========================================================================*/

/**
 * @brief   Type of a generic single link list header and element.
 */
typedef struct ch_list ch_list_t;

/**
 * @brief   Structure representing a generic single link list header
 *          and element.
 */
struct ch_list {
  ch_list_t             *next;      /**< @brief Next in the list/queue.     */
};

/**
 * @brief   Type of a generic bidirectional linked list header and element.
 */
typedef struct ch_queue ch_queue_t;

/**
 * @brief   Structure representing a generic bidirectional linked list header
 *          and element.
 */
struct ch_queue {
  ch_queue_t            *next;      /**< @brief Next in the list/queue.     */
  ch_queue_t            *prev;      /**< @brief Previous in the queue.      */
};

/**
 * @brief   Type of a generic priority-ordered bidirectional linked list
 *          header and element.
 */
typedef struct ch_priority_queue ch_priority_queue_t;

/**
 * @brief   Structure representing a generic priority-ordered bidirectional
 *          linked list header and element.
 * @note    Link fields are void pointers in order to avoid aliasing issues.
 */
struct ch_priority_queue {
  ch_priority_queue_t   *next;      /**< @brief Next in the queue.          */
  ch_priority_queue_t   *prev;      /**< @brief Previous in the queue.      */
  tprio_t               prio;       /**< @brief Priority of this element.   */
};

/**
 * @brief   Type of a generic bidirectional linked delta list
 *          header and element.
 */
typedef struct ch_delta_list ch_delta_list_t;

/**
 * @brief   Delta list element and header structure.
 */
struct ch_delta_list {
  ch_delta_list_t       *next;      /**< @brief Next in the delta list.     */
  ch_delta_list_t       *prev;      /**< @brief Previous in the delta list. */
  sysinterval_t         delta;      /**< @brief Time interval from previous.*/
};

/*===========================================================================*/
/* Module macros.                                                            */
/*===========================================================================*/

/**
 * @brief   Data part of a static queue object initializer.
 * @details This macro should be used when statically initializing a
 *          queue that is part of a bigger structure.
 *
 * @param[in] name      the name of the queue variable
 */
#define __CH_QUEUE_DATA(name) {(ch_queue_t *)&name, (ch_queue_t *)&name}

/**
 * @brief   Static queue object initializer.
 * @details Statically initialized queues require no explicit
 *          initialization using @p queue_init().
 *
 * @param[in] name      the name of the queue variable
 */
#define CH_QUEUE_DECL(name)                                                 \
    ch_queue_t name = __CH_QUEUE_DATA(name)

/*===========================================================================*/
/* External declarations.                                                    */
/*===========================================================================*/

/* Early function prototypes required by the following headers.*/
#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
}
#endif

/*===========================================================================*/
/* Module inline functions.                                                  */
/*===========================================================================*/

/**
 * @brief   List initialization.
 *
 * @param[out] lp       pointer to the list header
 *
 * @notapi
 */
static inline void ch_list_init(ch_list_t *lp) {

  lp->next = lp;
}

/**
 * @brief   Evaluates to @p true if the specified list is empty.
 *
 * @param[in] lp        pointer to the list header
 * @return              The status of the list.
 *
 * @notapi
 */
static inline bool ch_list_isempty(ch_list_t *lp) {

  return (bool)(lp->next == lp);
}

/**
 * @brief   Evaluates to @p true if the specified list is not empty.
 *
 * @param[in] lp        pointer to the list header
 * @return              The status of the list.
 *
 * @notapi
 */
static inline bool ch_list_notempty(ch_list_t *lp) {

  return (bool)(lp->next != lp);
}

/**
 * @brief   Pushes an element on top of a stack list.
 *
 * @param[in] lp    the pointer to the list header
 * @param[in] p     the pointer to the element to be inserted in the list
 *
 * @notapi
 */
static inline void ch_list_link(ch_list_t *lp, ch_list_t *p) {

  p->next = lp->next;
  lp->next = p;
}

/**
 * @brief   Pops an element from the top of a stack list and returns it.
 * @pre     The list must be non-empty before calling this function.
 *
 * @param[in] lp        the pointer to the list header
 * @return              The removed element pointer.
 *
 * @notapi
 */
static inline ch_list_t *ch_list_unlink(ch_list_t *lp) {

  ch_list_t *p = lp->next;
  lp->next = p->next;

  return p;
}

/**
 * @brief   Queue initialization.
 *
 * @param[out] qp       pointer to the queue header
 *
 * @notapi
 */
static inline void ch_queue_init(ch_queue_t *qp) {

  qp->next = qp;
  qp->prev = qp;
}

/**
 * @brief   Evaluates to @p true if the specified queue is empty.
 *
 * @param[in] qp        pointer to the queue header
 * @return              The status of the queue.
 *
 * @notapi
 */
static inline bool ch_queue_isempty(const ch_queue_t *qp) {

  return (bool)(qp->next == qp);
}

/**
 * @brief   Evaluates to @p true if the specified queue is not empty.
 *
 * @param[in] qp        pointer to the queue header
 * @return              The status of the queue.
 *
 * @notapi
 */
static inline bool ch_queue_notempty(const ch_queue_t *qp) {

  return (bool)(qp->next != qp);
}

/**
 * @brief   Inserts an element into a queue.
 *
 * @param[in] qp        the pointer to the queue header
 * @param[in] p         the pointer to the element to be inserted in the queue
 *
 * @notapi
 */
static inline void ch_queue_insert(ch_queue_t *qp, ch_queue_t *p) {

  p->next       = qp;
  p->prev       = qp->prev;
  p->prev->next = p;
  qp->prev      = p;
}

/**
 * @brief   Removes the first-out element from a queue and returns it.
 * @note    If the queue is priority ordered then this function returns the
 *          element with the highest priority.
 *
 * @param[in] qp        the pointer to the queue list header
 * @return              The removed element pointer.
 *
 * @notapi
 */
static inline ch_queue_t *ch_queue_fifo_remove(ch_queue_t *qp) {
  ch_queue_t *p = qp->next;

  qp->next       = p->next;
  qp->next->prev = qp;

  return p;
}

/**
 * @brief   Removes the last-out element from a queue and returns it.
 * @note    If the queue is priority ordered then this function returns the
 *          element with the lowest priority.
 *
 * @param[in] qp    the pointer to the queue list header
 * @return          The removed element pointer.
 *
 * @notapi
 */
static inline ch_queue_t *ch_queue_lifo_remove(ch_queue_t *qp) {
  ch_queue_t *p = qp->prev;

  qp->prev       = p->prev;
  qp->prev->next = qp;

  return p;
}

/**
 * @brief   Removes an element from a queue and returns it.
 * @details The element is removed from the queue regardless of its relative
 *          position and regardless the used insertion method.
 *
 * @param[in] p         the pointer to the element to be removed from the queue
 * @return              The removed element pointer.
 *
 * @notapi
 */
static inline ch_queue_t *ch_queue_dequeue(ch_queue_t *p) {

  p->prev->next = p->next;
  p->next->prev = p->prev;

  return p;
}

/**
 * @brief   Priority queue initialization.
 * @note    The queue header priority is initialized to zero, all other
 *          elements in the queue are assumed to have priority greater
 *          than zero.
 *
 * @param[out] pqp      pointer to the priority queue header
 *
 * @notapi
 */
static inline void ch_pqueue_init(ch_priority_queue_t *pqp) {

  pqp->next = pqp;
  pqp->prev = pqp;
  pqp->prio = (tprio_t)0;
}

/**
 * @brief   Removes the highest priority element from a priority queue and
 *          returns it.
 *
 * @param[in] pqp       the pointer to the priority queue list header
 * @return              The removed element pointer.
 *
 * @notapi
 */
static inline ch_priority_queue_t *ch_pqueue_remove_highest(ch_priority_queue_t *pqp) {
  ch_priority_queue_t *p = pqp->next;

  pqp->next       = p->next;
  pqp->next->prev = pqp;

  return p;
}

/**
 * @brief   Inserts an element in the priority queue placing it behind
 *          its peers.
 * @details The element is positioned behind all elements with higher or
 *          equal priority.
 *
 * @param[in] pqp       the pointer to the priority queue list header
 * @param[in] p         the pointer to the element to be inserted in the queue
 * @return              The inserted element pointer.
 *
 * @notapi
 */
static inline ch_priority_queue_t *ch_pqueue_insert_behind(ch_priority_queue_t *pqp,
                                                           ch_priority_queue_t *p) {

  /* Scanning priority queue, the list is assumed to be mostly empty.*/
  do {
    pqp = pqp->next;
  } while (unlikely(pqp->prio >= p->prio));

  /* Insertion on prev.*/
  p->next       = pqp;
  p->prev       = pqp->prev;
  p->prev->next = p;
  pqp->prev     = p;

  return p;
}

/**
 * @brief   Inserts an element in the priority queue placing it ahead of
 *          its peers.
 * @details The element is positioned ahead of all elements with higher or
 *          equal priority.
 *
 * @param[in] pqp       the pointer to the priority queue list header
 * @param[in] p         the pointer to the element to be inserted in the queue
 * @return              The inserted element pointer.
 *
 * @notapi
 */
static inline ch_priority_queue_t *ch_pqueue_insert_ahead(ch_priority_queue_t *pqp,
                                                          ch_priority_queue_t *p) {

  /* Scanning priority queue, the list is assumed to be mostly empty.*/
  do {
    pqp = pqp->next;
  } while (unlikely(pqp->prio > p->prio));

  /* Insertion on prev.*/
  p->next       = pqp;
  p->prev       = pqp->prev;
  p->prev->next = p;
  pqp->prev     = p;

  return p;
}

/**
 * @brief   Delta list initialization.
 *
 * @param[out] dlhp    pointer to the delta list header
 *
 * @notapi
 */
static inline void ch_dlist_init(ch_delta_list_t *dlhp) {

  dlhp->next  = dlhp;
  dlhp->prev  = dlhp;
  dlhp->delta = (sysinterval_t)-1;
}

/**
 * @brief   Evaluates to @p true if the specified delta list is empty.
 *
 * @param[in] dlhp      pointer to the delta list header
 * @return              The status of the delta list.
 *
 * @notapi
 */
static inline bool ch_dlist_isempty(ch_delta_list_t *dlhp) {

  return (bool)(dlhp == dlhp->next);
}

/**
 * @brief   Evaluates to @p true if the specified queue is not empty.
 *
 * @param[in] dlhp      pointer to the delta list header
 * @return              The status of the delta list.
 *
 * @notapi
 */
static inline bool ch_dlist_notempty(ch_delta_list_t *dlhp) {

  return (bool)(dlhp != dlhp->next);
}

/**
 * @brief   Last element in the delta list check.
 *
 * @param[in] dlhp      pointer to the delta list header
 * @param[in] dlp       pointer to the delta list element
 *
 * @notapi
 */
static inline bool ch_dlist_islast(ch_delta_list_t *dlhp,
                                   ch_delta_list_t *dlp) {

  return (bool)(dlp->next == dlhp);
}

/**
 * @brief   Fist element in the delta list check.
 *
 * @param[in] dlhp      pointer to the delta list header
 * @param[in] dlp       pointer to the delta list element
 *
 * @notapi
 */
static inline bool ch_dlist_isfirst(ch_delta_list_t *dlhp,
                                    ch_delta_list_t *dlp) {

  return (bool)(dlhp->next == dlp);
}

/**
 * @brief   Inserts an element after another header element.
 *
 * @param[in] dlhp      pointer to the delta list header element
 * @param[in] dlp       element to be inserted after the header element
 * @param[in] delta     delta of the element to be inserted
 *
 * @notapi
 */
static inline void ch_dlist_insert_after(ch_delta_list_t *dlhp,
                                         ch_delta_list_t *dlp,
                                         sysinterval_t delta) {

  dlp->delta      = delta;
  dlp->prev       = dlhp;
  dlp->next       = dlp->prev->next;
  dlp->next->prev = dlp;
  dlhp->next      = dlp;
}

/**
 * @brief   Inserts an element before another header element.
 *
 * @param[in] dlhp      pointer to the delta list header element
 * @param[in] dlp       element to be inserted before the header element
 * @param[in] delta     delta of the element to be inserted
 *
 * @notapi
 */
static inline void ch_dlist_insert_before(ch_delta_list_t *dlhp,
                                          ch_delta_list_t *dlp,
                                          sysinterval_t delta) {

  dlp->delta      = delta;
  dlp->next       = dlhp;
  dlp->prev       = dlp->next->prev;
  dlp->prev->next = dlp;
  dlhp->prev      = dlp;
}

/**
 * @brief   Inserts an element in a delta list.
 *
 * @param[in] dlhp      pointer to the delta list header element
 * @param[in] dlep      element to be inserted before the header element
 * @param[in] delta     delta of the element to be inserted
 *
 * @notapi
 */
static inline void ch_dlist_insert(ch_delta_list_t *dlhp,
                                   ch_delta_list_t *dlep,
                                   sysinterval_t delta) {
  ch_delta_list_t *dlp;

  /* The delta list is scanned in order to find the correct position for
     this element. */
  dlp = dlhp->next;
  while (likely(dlp->delta < delta)) {
    /* Debug assert if the element is already in the list.*/
    chDbgAssert(dlp != dlep, "element already in list");

    delta -= dlp->delta;
    dlp = dlp->next;
  }

  /* The timer is inserted in the delta list.*/
  ch_dlist_insert_before(dlp, dlep, delta);

  /* Adjusting delta for the following element.*/
  dlp->delta -= delta;

  /* Special case when the inserted element is in last position in the list,
     the value in the header must be restored, just doing it is faster than
     checking then doing.*/
  dlhp->delta = (sysinterval_t)-1;
}

/**
 * @brief   Dequeues an element from the delta list.
 *
 * @param[in] dlhp      pointer to the delta list header
 *
 * @notapi
 */
static inline ch_delta_list_t *ch_dlist_remove_first(ch_delta_list_t *dlhp) {
  ch_delta_list_t *dlp = dlhp->next;

  dlhp->next       = dlp->next;
  dlhp->next->prev = dlhp;

  return dlp;
}

/**
 * @brief   Dequeues an element from the delta list.
 *
 * @param[in] dlp       pointer to the delta list element
 *
 * @notapi
 */
static inline ch_delta_list_t *ch_dlist_dequeue(ch_delta_list_t *dlp) {

  dlp->prev->next = dlp->next;
  dlp->next->prev = dlp->prev;

  return dlp;
}

#endif /* CHLISTS_H */

/** @} */
