/*************************************************************************************************/
/*!
 *  \file   wsf_queue.c
 *
 *  \brief  General purpose queue service.
 *
 *          $Date: 2017-01-06 13:47:48 -0600 (Fri, 06 Jan 2017) $
 *          $Revision: 10864 $
 *
 *  Copyright (c) 2009-2017 ARM Ltd., all rights reserved.
 *  ARM Ltd. confidential and proprietary.
 *
 *  IMPORTANT.  Your use of this file is governed by a Software License Agreement
 *  ("Agreement") that must be accepted in order to download or otherwise receive a
 *  copy of this file.  You may not use or copy this file for any purpose other than
 *  as described in the Agreement.  If you do not agree to all of the terms of the
 *  Agreement do not use this file and delete all copies in your possession or control;
 *  if you do not have a copy of the Agreement, you must contact ARM Ltd. prior
 *  to any use, copying or further distribution of this software.
 */
/*************************************************************************************************/

#include "wsf_types.h"
#include "wsf_queue.h"
#include "wsf_assert.h"
#include "wsf_cs.h"

/**************************************************************************************************
  Macros
**************************************************************************************************/

/* Get next queue element */
#define WSF_QUEUE_NEXT(p)               (((wsfQueueElem_t *)(p))->pNext)

/**************************************************************************************************
  Data Types
**************************************************************************************************/

/* Queue element */
typedef struct wsfQueueElem_tag
{
  struct wsfQueueElem_tag *pNext;      /* pointer to next element */
} wsfQueueElem_t;

/*************************************************************************************************/
/*!
 *  \fn     WsfQueueEnq
 *
 *  \brief  Enqueue and element to the tail of a queue.
 *
 *  \param  pQueue    Pointer to queue.
 *  \param  pElem     Pointer to element.
 *
 *  \return None.
 */
/*************************************************************************************************/
void WsfQueueEnq(wsfQueue_t *pQueue, void *pElem)
{
  WSF_CS_INIT(cs);

  WSF_ASSERT(pQueue != NULL);
  WSF_ASSERT(pElem != NULL);

  /* initialize next pointer */
  WSF_QUEUE_NEXT(pElem) = NULL;

  /* enter critical section */
  WSF_CS_ENTER(cs);

  /* if queue empty */
  if (pQueue->pHead == NULL)
  {
    pQueue->pHead = pElem;
    pQueue->pTail = pElem;
  }
  /* else enqueue element to the tail of queue */
  else
  {
    WSF_QUEUE_NEXT(pQueue->pTail) = pElem;
    pQueue->pTail = pElem;
  }

  /* exit critical section */
  WSF_CS_EXIT(cs);
}

/*************************************************************************************************/
/*!
 *  \fn     WsfQueueDeq
 *
 *  \brief  Dequeue and element from the head of a queue.
 *
 *  \param  pQueue    Pointer to queue.
 *
 *  \return Pointer to element that has been dequeued or NULL if queue is empty.
 */
/*************************************************************************************************/
void *WsfQueueDeq(wsfQueue_t *pQueue)
{
  wsfQueueElem_t  *pElem;

  WSF_CS_INIT(cs);

  WSF_ASSERT(pQueue != NULL);

  /* enter critical section */
  WSF_CS_ENTER(cs);

  pElem = pQueue->pHead;

  /* if queue is not empty */
  if (pElem != NULL)
  {
    /* set head to next element in queue */
    pQueue->pHead = WSF_QUEUE_NEXT(pElem);

    /* check for empty queue */
    if (pQueue->pHead == NULL)
    {
      pQueue->pTail = NULL;
    }
  }

  /* exit critical section */
  WSF_CS_EXIT(cs);

  return pElem;
}

/*************************************************************************************************/
/*!
 *  \fn     WsfQueuePush
 *
 *  \brief  Push and element to the head of a queue.
 *
 *  \param  pQueue    Pointer to queue.
 *  \param  pElem     Pointer to element.
 *
 *  \return None.
 */
/*************************************************************************************************/
void WsfQueuePush(wsfQueue_t *pQueue, void *pElem)
{
  WSF_CS_INIT(cs);

  WSF_ASSERT(pQueue != NULL);
  WSF_ASSERT(pElem != NULL);

  /* enter critical section */
  WSF_CS_ENTER(cs);

  /* else push element to head of queue */
  WSF_QUEUE_NEXT(pElem) = pQueue->pHead;

  /* if queue was empty set tail */
  if (pQueue->pHead == NULL)
  {
    pQueue->pTail = pElem;
  }

  /* set head */
  pQueue->pHead = pElem;

  /* exit critical section */
  WSF_CS_EXIT(cs);
}

/*************************************************************************************************/
/*!
 *  \fn     WsfQueueInsert
 *
 *  \brief  Insert an element into a queue.  This function is typically used when iterating
 *          over a queue.
 *
 *  \param  pQueue    Pointer to queue.
 *  \param  pElem     Pointer to element to be inserted.
 *  \param  pPrev     Pointer to previous element in the queue before element to be inserted.
 *                    Note:  set pPrev to NULL if pElem is first element in queue.
 *  \return None.
 */
/*************************************************************************************************/
void WsfQueueInsert(wsfQueue_t *pQueue, void *pElem, void *pPrev)
{
  WSF_CS_INIT(cs);

  WSF_ASSERT(pQueue != NULL);
  WSF_ASSERT(pElem != NULL);

  /* enter critical section */
  WSF_CS_ENTER(cs);

  /* if queue empty or inserting at tail */
  if (pQueue->pHead == NULL || pPrev == pQueue->pTail)
  {
    /* queue as normal */
    WsfQueueEnq(pQueue, pElem);
  }
  /* else if inserting at head */
  else if (pPrev == NULL)
  {
    /* push to head */
    WsfQueuePush(pQueue, pElem);
  }
  else
  {
    /* insert in middle of queue */
    WSF_QUEUE_NEXT(pElem) = WSF_QUEUE_NEXT(pPrev);
    WSF_QUEUE_NEXT(pPrev) = pElem;
  }

  /* exit critical section */
  WSF_CS_EXIT(cs);
}

/*************************************************************************************************/
/*!
 *  \fn     WsfQueueRemove
 *
 *  \brief  Remove an element from a queue.  This function is typically used when iterating
 *          over a queue.
 *
 *  \param  pQueue    Pointer to queue.
 *  \param  pElem     Pointer to element to be removed.
 *  \param  pPrev     Pointer to previous element in the queue before element to be removed.
 *                    Note:  set pPrev to NULL if pElem is first element in queue.
 *  \return None.
 */
/*************************************************************************************************/
void WsfQueueRemove(wsfQueue_t *pQueue, void *pElem, void *pPrev)
{
  WSF_CS_INIT(cs);

  WSF_ASSERT(pQueue != NULL);
  WSF_ASSERT(pQueue->pHead != NULL);
  WSF_ASSERT(pElem != NULL);

  /* enter critical section */
  WSF_CS_ENTER(cs);

  /* if first element */
  if (pElem == pQueue->pHead)
  {
    /* remove from head of queue */
    pQueue->pHead = WSF_QUEUE_NEXT(pElem);
  }
  else if (pPrev)
  {
    /* remove from middle of queue, pPrev will never be null */
    WSF_QUEUE_NEXT(pPrev) = WSF_QUEUE_NEXT(pElem);
  }

  /* if last element */
  if (pElem == pQueue->pTail)
  {
    /* update tail */
    pQueue->pTail = pPrev;
  }

  /* exit critical section */
  WSF_CS_EXIT(cs);
}

/*************************************************************************************************/
/*!
 *  \fn     WsfQueueCount
 *
 *  \brief  Count the number of elements in a queue.
 *
 *  \param  pQueue    Pointer to queue.
 *
 *  \return Number of elements in queue.
 */
/*************************************************************************************************/
uint16_t WsfQueueCount(wsfQueue_t *pQueue)
{
  wsfQueueElem_t  *pElem;
  uint16_t        count = 0;

  WSF_CS_INIT(cs);

  WSF_ASSERT(pQueue != NULL);

  /* enter critical section */
  WSF_CS_ENTER(cs);

  pElem = pQueue->pHead;

  /* iterate over queue */
  while (pElem != NULL)
  {
    count++;
    pElem = pElem->pNext;
  }

  /* exit critical section */
  WSF_CS_EXIT(cs);

  return count;
}

/*************************************************************************************************/
/*!
 *  \fn     WsfQueueEmpty
 *
 *  \brief  Return TRUE if queue is empty.
 *
 *  \param  pQueue    Pointer to queue.
 *
 *  \return TRUE if queue is empty, FALSE otherwise.
 */
/*************************************************************************************************/
bool_t WsfQueueEmpty(wsfQueue_t *pQueue)
{
  bool_t      empty;

  WSF_CS_INIT(cs);

  WSF_ASSERT(pQueue != NULL);

  /* enter critical section */
  WSF_CS_ENTER(cs);

  empty = (pQueue->pHead == NULL);

  /* exit critical section */
  WSF_CS_EXIT(cs);

  return empty;
}
