/**
  ******************************************************************************
  * @file    stm_queue.c
  * @author  MCD Application Team
  * @brief   Queue management
  ******************************************************************************
   * @attention
  *
  * <h2><center>&copy; Copyright (c) 2019 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
 */

/* Includes ------------------------------------------------------------------*/

/* Includes ------------------------------------------------------------------*/
#include "utilities_common.h"

#include "stm_queue.h"

/* Private define ------------------------------------------------------------*/
/* Private typedef -------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
#define MOD(X,Y) (((X) >= (Y)) ? ((X)-(Y)) : (X))

/* Private variables ---------------------------------------------------------*/
/* Global variables ----------------------------------------------------------*/
/* Extern variables ----------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/* Public functions ----------------------------------------------------------*/

/**
  * @brief   Initilialze queue structure .
  * @note   This function is used to initialize the global queue structure.
  * @param  q: pointer on queue structure to be initialised
  * @param  queueBuffer: pointer on Queue Buffer
  * @param  queueSize:  Size of Queue Buffer
  * @param  elementSize: Size of an element in the queue. if =0, the queue will manage variable sizze elements
  * @retval   always 0
  */
int CircularQueue_Init(queue_t *q, uint8_t* queueBuffer, uint32_t queueSize, uint16_t elementSize, uint8_t optionFlags)
{
  q->qBuff = queueBuffer;
  q->first = 0;
  q->last = 0; /* queueSize-1; */
  q->byteCount = 0;
  q->elementCount = 0;
  q->queueMaxSize = queueSize;
  q->elementSize = elementSize;
  q->optionFlags = optionFlags;

   if ((optionFlags & CIRCULAR_QUEUE_SPLIT_IF_WRAPPING_FLAG) && q-> elementSize)
   {
    /* can not deal with splitting at the end of buffer with fixed size element */
    return -1;
  }
  return 0;
}

/**
  * @brief   Add  element to the queue .
  * @note   This function is used to add one or more  element(s) to the Circular Queue .
  * @param  q: pointer on queue structure   to be handled
  * @param  X; pointer on element(s) to be added
  * @param  elementSize:  Size of element to be added to the queue. Only used if the queue manage variable size elements
  * @param  nbElements:  number of elements in the in buffer pointed by x
  * @retval  pointer on last element just added to the queue, NULL if the element to be added do not fit in the queue (too big)
  */
uint8_t* CircularQueue_Add(queue_t *q, uint8_t* x, uint16_t elementSize, uint32_t nbElements)
{

  uint8_t* ptr = NULL;                      /* fct return ptr to the element freshly added, if no room fct return NULL */
  uint16_t curElementSize = 0;              /* the size of the element currently  stored at q->last position */
  uint8_t  elemSizeStorageRoom  = 0 ;       /* Indicate the header (which contain only size) of element in case of varaibale size element (q->elementsize == 0) */
  uint32_t curBuffPosition;                  /* the current position in the queue buffer */
  uint32_t i;                               /* loop counter */
  uint32_t NbBytesToCopy = 0, NbCopiedBytes = 0 ; /* Indicators for copying bytes in queue */
  uint32_t eob_free_size;                         /* Eof End of Quque Buffer Free Size */
  uint8_t  wrap_will_occur = 0;                   /* indicate if a wrap around will occurs */
  uint8_t  wrapped_element_eob_size;              /* In case of Wrap around, indicate size of parta of element that fit at thened of the queuue  buffer */
  uint16_t overhead = 0;                          /* In case of CIRCULAR_QUEUE_SPLIT_IF_WRAPPING_FLAG or CIRCULAR_QUEUE_NO_WRAP_FLAG options,
                                                     indcate the size overhead that will be generated by adding the element with wrap management (split or no wrap ) */


  elemSizeStorageRoom  = (q->elementSize == 0) ? 2 : 0;
  /* retrieve the size of last element sored: the value stored at the beginning of the queue element if element size is variable otherwise take it from fixed element Size member */
  if (q->byteCount)
  {
    curElementSize = (q->elementSize == 0) ? q->qBuff[q->last] + ((q->qBuff[MOD((q->last+1), q->queueMaxSize)])<<8) + 2 : q->elementSize;
  }
  /* if queue element have fixed size , reset the elementSize arg with fixed element size value */
  if (q->elementSize > 0)
  {
    elementSize = q->elementSize;
  }

   eob_free_size = (q->last >= q->first) ? q->queueMaxSize - (q->last + curElementSize) : 0;

   /* check how many bytes of wrapped element (if anay) are at end of buffer */
   wrapped_element_eob_size = (((elementSize + elemSizeStorageRoom )*nbElements) < eob_free_size) ? 0 : (eob_free_size % (elementSize + elemSizeStorageRoom));
   wrap_will_occur  = wrapped_element_eob_size > elemSizeStorageRoom;

   overhead = (wrap_will_occur && (q->optionFlags & CIRCULAR_QUEUE_NO_WRAP_FLAG)) ? wrapped_element_eob_size : overhead;
   overhead = (wrap_will_occur && (q->optionFlags & CIRCULAR_QUEUE_SPLIT_IF_WRAPPING_FLAG)) ? elemSizeStorageRoom  : overhead;


  /* Store now the elements if ennough room for all elements */
  if (elementSize && ((q->byteCount + ((elementSize + elemSizeStorageRoom )*nbElements) + overhead) <= q->queueMaxSize))
  {
    /* loop to add all elements  */
    for (i=0; i < nbElements; i++)
    {
      q->last = MOD ((q->last + curElementSize),q->queueMaxSize);
      curBuffPosition = q->last;

      /* store the element  */
      /* store first the element size if element size is variable */
      if (q->elementSize == 0)
      {
        q->qBuff[curBuffPosition++]= elementSize & 0xFF;
        curBuffPosition = MOD(curBuffPosition, q->queueMaxSize);
        q->qBuff[curBuffPosition++]= (elementSize & 0xFF00) >> 8 ;
        curBuffPosition = MOD(curBuffPosition, q->queueMaxSize);
        q->byteCount += 2;
      }

      /* Identify number of bytes of copy takeing account possible wrap, in this case NbBytesToCopy will contains size that fit at end of the queue buffer */
      NbBytesToCopy = MIN((q->queueMaxSize-curBuffPosition),elementSize);
      /* check if no wrap (NbBytesToCopy == elementSize) or if Wrap and no spsicf option;
         In this case part of data will copied at the end of the buffer and the rest a the beginning */
      if ((NbBytesToCopy == elementSize) || ((NbBytesToCopy < elementSize) && (q->optionFlags == CIRCULAR_QUEUE_NO_FLAG)))
      {
        /* Copy First part (or emtire buffer ) from current position up to the end of the buffer queue (or before if enough room)  */
        memcpy(&q->qBuff[curBuffPosition],&x[i*elementSize],NbBytesToCopy);
        /* Adjust bytes count */
        q->byteCount += NbBytesToCopy;
        /* Wrap */
        curBuffPosition = 0;
        /* set NbCopiedBytes bytes with  ampount copied */
        NbCopiedBytes = NbBytesToCopy;
        /* set the rest to copy if wrao , if no wrap will be 0 */
        NbBytesToCopy = elementSize - NbBytesToCopy;
        /* set the current element Size, will be used to calaculate next last position at beginning of loop */
        curElementSize = (elementSize) + elemSizeStorageRoom ;
      }
      else if (NbBytesToCopy)  /* We have a wrap  to manage */
      {
       /* case of CIRCULAR_QUEUE_NO_WRAP_FLAG option */
         if (q->optionFlags & CIRCULAR_QUEUE_NO_WRAP_FLAG)
        {
          /* if element size are variable and NO_WRAP option, Invalidate end of buffer setting 0xFFFF size*/
          if (q->elementSize == 0)
          {
             q->qBuff[curBuffPosition-2] = 0xFF;
             q->qBuff[curBuffPosition-1] = 0xFF;
          }
          q->byteCount += NbBytesToCopy;  /* invalid data at the end of buffer are take into account in byteCount */
          /* No bytes coped a the end of buffer */
          NbCopiedBytes = 0;
          /* all element to be copied at the begnning of buffer */
          NbBytesToCopy = elementSize;
          /* Wrap */
          curBuffPosition = 0;
          /* if variable size element, invalidate end of buffer setting OxFFFF in element header (size) */
          if (q->elementSize == 0)
          {
            q->qBuff[curBuffPosition++] = NbBytesToCopy & 0xFF;
            q->qBuff[curBuffPosition++] = (NbBytesToCopy & 0xFF00) >> 8 ;
            q->byteCount += 2;
          }

        }
        /* case of CIRCULAR_QUEUE_SPLIT_IF_WRAPPING_FLAG option */
        else if (q->optionFlags & CIRCULAR_QUEUE_SPLIT_IF_WRAPPING_FLAG)
        {
          if (q->elementSize == 0)
          {
            /* reset the size of current element to the nb bytes fitting at the end of buffer */
             q->qBuff[curBuffPosition-2] = NbBytesToCopy & 0xFF;
             q->qBuff[curBuffPosition-1] = (NbBytesToCopy & 0xFF00) >> 8 ;
             /* copy the bytes */
             memcpy(&q->qBuff[curBuffPosition],&x[i*elementSize],NbBytesToCopy);
             q->byteCount += NbBytesToCopy;
             /* set the number of copied bytes */
             NbCopiedBytes = NbBytesToCopy;
             /* set rest of data to be copied to begnning of buffer */
             NbBytesToCopy = elementSize - NbBytesToCopy;
             /* one element more dur to split in 2 elements */
             q->elementCount++;
             /* Wrap */
             curBuffPosition = 0;
             /* Set new size for rest of data */
             q->qBuff[curBuffPosition++] = NbBytesToCopy & 0xFF;
             q->qBuff[curBuffPosition++] = (NbBytesToCopy & 0xFF00) >> 8 ;
             q->byteCount += 2;
          }
          else
          {
            /* Should not occur */
            /* can not manage split Flag on Fixed size element */
            /* Buffer is corrupted */
            return NULL;
          }
        }
        curElementSize = (NbBytesToCopy) + elemSizeStorageRoom ;
        q->last = 0;
      }

      /* some remaining byte to copy */
      if (NbBytesToCopy)
      {
        memcpy(&q->qBuff[curBuffPosition],&x[(i*elementSize)+NbCopiedBytes],NbBytesToCopy);
        q->byteCount += NbBytesToCopy;
      }

      /* One more element */
      q->elementCount++;
    }

    ptr = q->qBuff + (MOD((q->last+elemSizeStorageRoom ),q->queueMaxSize));
  }
  /* for Breakpoint only...to remove */
  else
  {
    return NULL;
  }
  return ptr;
}


/**
  * @brief  Remove element from  the queue and copy it in provided buffer
  * @note   This function is used to remove and element from  the Circular Queue .
  * @param  q: pointer on queue structure  to be handled
  * @param  elementSize: Pointer to return Size of element to be removed
  * @param  buffer: destination buffer where to copy element
  * @retval Pointer on removed element. NULL if queue was empty
  */
uint8_t* CircularQueue_Remove_Copy(queue_t *q, uint16_t* elementSize, uint8_t* buffer)
{
   return NULL;
}



/**
  * @brief  Remove element from  the queue.
  * @note   This function is used to remove and element from  the Circular Queue .
  * @param  q: pointer on queue structure  to be handled
  * @param  elementSize: Pointer to return Size of element to be removed (ignored if NULL)
  * @retval Pointer on removed element. NULL if queue was empty
  */
uint8_t* CircularQueue_Remove(queue_t *q, uint16_t* elementSize)
{
  uint8_t  elemSizeStorageRoom = 0;
  uint8_t* ptr= NULL;
  elemSizeStorageRoom = (q->elementSize == 0) ? 2 : 0;
  uint16_t eltSize = 0;
  if (q->byteCount > 0)
  {
    /* retrieve element Size */
    eltSize = (q->elementSize == 0) ? q->qBuff[q->first] + ((q->qBuff[MOD((q->first+1), q->queueMaxSize)])<<8) : q->elementSize;

     if ((q->optionFlags & CIRCULAR_QUEUE_NO_WRAP_FLAG) && !(q->optionFlags & CIRCULAR_QUEUE_SPLIT_IF_WRAPPING_FLAG))
     {
       if (((eltSize == 0xFFFF) && q->elementSize == 0 ) ||
           ((q->first > q->last) && q->elementSize && ((q->queueMaxSize - q->first) < q->elementSize)))
       {
          /* all data from current position up to the end of buffer are invalid */
          q->byteCount -= (q->queueMaxSize - q->first);
          /* Adjust first element pos */
          q->first = 0;
          /* retrieve the right size after the wrap [if variable size element] */
          eltSize = (q->elementSize == 0) ? q->qBuff[q->first] + ((q->qBuff[MOD((q->first+1), q->queueMaxSize)])<<8) : q->elementSize;
       }
     }

    /* retrieve element */
    ptr = q->qBuff + (MOD((q->first + elemSizeStorageRoom), q->queueMaxSize));

    /* adjust byte count */
    q->byteCount -= (eltSize + elemSizeStorageRoom) ;

    /* Adjust q->first */
    if (q->byteCount > 0)
    {
      q->first = MOD((q->first+ eltSize + elemSizeStorageRoom ), q->queueMaxSize);
    }
    /* adjust element count */
    --q->elementCount;
  }
  if (elementSize != NULL)
  {
    *elementSize = eltSize;
  }
  return ptr;
}


/**
  * @brief  "Sense" first element of the queue, without removing it and copy it in provided buffer
  * @note   This function is used to return a pointer on the first element of the queue without removing it.
  * @param  q: pointer on queue structure  to be handled
  * @param  elementSize:  Pointer to return Size of element to be removed
  * @param  buffer: destination buffer where to copy element
  * @retval Pointer on sensed element. NULL if queue was empty
  */

uint8_t* CircularQueue_Sense_Copy(queue_t *q, uint16_t* elementSize, uint8_t* buffer)
{
    return NULL;
}


/**
  * @brief  "Sense" first element of the queue, without removing it.
  * @note   This function is used to return a pointer on the first element of the queue without removing it.
  * @param  q: pointer on queue structure  to be handled
  * @param  elementSize:  Pointer to return Size of element to be removed (ignored if NULL)
  * @retval Pointer on sensed element. NULL if queue was empty
  */
uint8_t* CircularQueue_Sense(queue_t *q, uint16_t* elementSize)
{
  uint8_t  elemSizeStorageRoom = 0;
  uint8_t* x= NULL;
  elemSizeStorageRoom = (q->elementSize == 0) ? 2 : 0;
  uint16_t eltSize = 0;
  uint32_t FirstElemetPos = 0;

  if (q->byteCount > 0)
  {
    FirstElemetPos = q->first;
    eltSize = (q->elementSize == 0) ? q->qBuff[q->first] + ((q->qBuff[MOD((q->first+1), q->queueMaxSize)])<<8) : q->elementSize;

    if ((q->optionFlags & CIRCULAR_QUEUE_NO_WRAP_FLAG) && !(q->optionFlags & CIRCULAR_QUEUE_SPLIT_IF_WRAPPING_FLAG))
    {
      if (((eltSize == 0xFFFF) && q->elementSize == 0 ) ||
          ((q->first > q->last) && q->elementSize && ((q->queueMaxSize - q->first) < q->elementSize)))

      {
        /* all data from current position up to the end of buffer are invalid */
        FirstElemetPos = 0; /* wrap to the begiining of buffer */

        /* retrieve the right size after the wrap [if variable size element] */
        eltSize = (q->elementSize == 0) ? q->qBuff[FirstElemetPos]+ ((q->qBuff[MOD((FirstElemetPos+1), q->queueMaxSize)])<<8) : q->elementSize;
      }
   }
   /* retrieve element */
    x = q->qBuff + (MOD((FirstElemetPos + elemSizeStorageRoom), q->queueMaxSize));
  }
  if (elementSize != NULL)
  {
    *elementSize = eltSize;
  }
  return x;
}

/**
  * @brief   Check if queue is empty.
  * @note    This function is used to to check if the queue is empty.
  * @param  q: pointer on queue structure  to be handled
  * @retval   TRUE (!0) if the queue is empyu otherwise FALSE (0)
  */
int CircularQueue_Empty(queue_t *q)
{
  int ret=FALSE;
  if (q->byteCount <= 0)
  {
    ret=TRUE;
  }
  return ret;
}

int CircularQueue_NbElement(queue_t *q)
{
  return q->elementCount;
}
