/** \file
 *  \brief This file implements a circular buffer.
 *
 */

/* Include Files */
#include <stdbool.h>
#include <stdint.h>
#include <project.h>

#include "FreeRTOS.h"
#include "task.h"

#include "UTIL_CircularBuffer.h"

/* Local Definitions */

/* Public Functions */

/* Public Data */

/* ******************* Module Level Information ********************* */

/* Private Function Prototypes */

/* Private Data */

/* Module Level Code */

//! Increments a value using modular arithmetic.
/*!
 *  \param  value    the value to be incremented
 *  \param  modulus  the modulus to use
 *  \return          (value + 1) modulo <i>modulus</i>
 */
inline uint16_t ModuloIncrement(const uint16_t value, const uint16_t modulus)
{
   uint16_t nextValue = value + 1;
   if (nextValue >= modulus)
   {
      nextValue = 0;
   }
   return (nextValue);
}

//! Initializes the circular buffer, and clears the flags.
/*!
 *  \param  this    pointer to the circular buffer in question
 *  \param  buffer  pointer to the memory allocated to store this circular buffer
 *  \param  size    size (in bytes) of this circular buffer
 */
void UTIL_InitCircularBuffer(UTIL_CircularBuffer_T * const this, uint8_t * buffer, uint16_t size)
{
    this->buffer = buffer;
    this->size = size;
	this->head = 0;
	this->tail = 0;
	this->count = 0;
	// Note that there is no need to zero out the actual buffer,
	// since it will be overwritten when values are added.
}

//! Adds a value to the end of the circular buffer.
/*!
 *  If the buffer is full, the value is dropped and the overflow flag is set.
 *
 *  \param  this  pointer to the circular buffer in question
 *  \param  value the value to be added to the buffer
 */
UTIL_CircularBufferResult_T UTIL_PushToCircularBuffer(UTIL_CircularBuffer_T * const this, uint8_t value)
{
    UTIL_CircularBufferResult_T result = UTIL_CIRCULARBUFFERRESULT_UNKNOWN;
    
    //UBaseType_t uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
    portDISABLE_INTERRUPTS();
    
    if (this->count < this->size)
    {
        this->buffer[this->head] = value;
        this->head = ModuloIncrement(this->head, this->size);
        this->count++;
        result = UTIL_CIRCULARBUFFERRESULT_SUCCESS;
    }
    else
    {
        result = UTIL_CIRCULARBUFFERRESULT_ERROR_OVERFLOW;
    }
    
    portENABLE_INTERRUPTS();
    //taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus);
    
    return result;
}

//! Retrieves a value from the beginning of the circular buffer (FIFO).
/*!
 *  If the buffer is empty, zero is returned and the underflow flag is set.
 *
 *  \param  this  pointer to the circular buffer in question
 *  \return       the oldest value in the buffer
 */
UTIL_CircularBufferResult_T UTIL_PopFromCircularBuffer(UTIL_CircularBuffer_T * const this, uint8_t * const value)
{
    UTIL_CircularBufferResult_T result = UTIL_CIRCULARBUFFERRESULT_UNKNOWN;
    
    //UBaseType_t uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
    portDISABLE_INTERRUPTS();

    if (this->count > 0)
    {
        *value = this->buffer[this->tail];
        this->tail = ModuloIncrement(this->tail, this->size);
        this->count--;
        result = UTIL_CIRCULARBUFFERRESULT_SUCCESS;
    }
    else
    {
        *value = 0;
    	result = UTIL_CIRCULARBUFFERRESULT_ERROR_UNDERFLOW;
    }

    portENABLE_INTERRUPTS();
    //taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus);
    
    return result;
}

//! Determines whether or not the circular buffer is empty.
/*!
 *  \param  this  pointer to the circular buffer in question
 *  \return       true if the buffer is empty; false otherwise
 */
bool UTIL_IsCircularBufferEmpty(UTIL_CircularBuffer_T * const this)
{
	bool result = false;

	if (this->count == 0)
	{
		result = true;
	}

	return result;
}

//! Determines whether or not the circular buffer is full.
/*!
 *  \param  this  pointer to the circular buffer in question
 *  \return       true if the buffer is full; false otherwise
 */
bool UTIL_IsCircularBufferFull(UTIL_CircularBuffer_T * const this)
{
	bool result = false;

	if (this->count >= this->size)
	{
		result = true;
	}

	return result;
}
