#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "drv/ringbuffer.h"

#define min(a, b)  (((a) < (b)) ? (a) : (b))

/**
  * \brief  Removes the entire FIFO contents.
  * \param  [in] fifo: The fifo to be emptied.
  * \return None.
  */
void ringbuffer_reset(ringbuffer_t *fifo)
{
    fifo->write = 0U;
    fifo->read = 0U;
    fifo->data_len = 0U;
}

/**
  * \brief  Returns the size of the FIFO in bytes.
  * \param  [in] fifo: The fifo to be used.
  * \return The size of the FIFO.
  */
static inline uint32_t ringbuffer_size(ringbuffer_t *fifo)
{
    return fifo->size;
}

/**
  * \brief  Returns the number of used bytes in the FIFO.
  * \param  [in] fifo: The fifo to be used.
  * \return The number of used bytes.
  */
uint32_t ringbuffer_len(ringbuffer_t *fifo)
{
    return fifo->data_len;
}

/**
  * \brief  Returns the number of bytes available in the FIFO.
  * \param  [in] fifo: The fifo to be used.
  * \return The number of bytes available.
  */
uint32_t ringbuffer_avail(ringbuffer_t *fifo)
{
    return ringbuffer_size(fifo) - ringbuffer_len(fifo);
}

/**
  * \brief  Is the FIFO empty?
  * \param  [in] fifo: The fifo to be used.
  * \retval true:      Yes.
  * \retval false:     No.
  */
bool ringbuffer_is_empty(ringbuffer_t *fifo)
{
    return (ringbuffer_len(fifo) == 0U);
}

/**
  * \brief  Is the FIFO full?
  * \param  [in] fifo: The fifo to be used.
  * \retval true:      Yes.
  * \retval false:     No.
  */
bool ringbuffer_is_full(ringbuffer_t *fifo)
{
    return (ringbuffer_avail(fifo) == 0U);
}

/**
  * \brief  Puts some data into the FIFO.
  * \param  [in] fifo: The fifo to be used.
  * \param  [in] in:   The data to be added.
  * \param  [in] len:  The length of the data to be added.
  * \return The number of bytes copied.
  * \note   This function copies at most @len bytes from the @in into
  *         the FIFO depending on the free space, and returns the number
  *         of bytes copied.
  */
uint32_t ringbuffer_in(ringbuffer_t *fifo, const void *datptr, uint32_t len)
{
    uint32_t writelen = 0U, tmplen = 0U;

    if (ringbuffer_is_full(fifo)) {
        writelen = 0U;
    } else {
        tmplen = fifo->size - fifo->data_len;
        writelen = (tmplen > len) ? len : tmplen;

        if (fifo->write < fifo->read) {
            memcpy((void *)&fifo->buffer[fifo->write], (void *)datptr, writelen);
        } else {
            tmplen = fifo->size - fifo->write;

            if (writelen <= tmplen) {
                memcpy((void *)&fifo->buffer[fifo->write], (void *)datptr, writelen);
            } else {
                memcpy((void *)&fifo->buffer[fifo->write], (void *)datptr, tmplen);
                memcpy((void *)fifo->buffer, (uint8_t *)datptr + tmplen, writelen - tmplen);
            }
        }

        fifo->write = (fifo->write + writelen) % fifo->size;
        fifo->data_len += writelen;
    }

    return writelen;
}

/**
  * \brief  Gets some data from the FIFO.
  * \param  [in] fifo: The fifo to be used.
  * \param  [in] out:  Where the data must be copied.
  * \param  [in] len:  The size of the destination buffer.
  * \return The number of copied bytes.
  * \note   This function copies at most @len bytes from the FIFO into
  *         the @out and returns the number of copied bytes.
  */
uint32_t ringbuffer_out(ringbuffer_t *fifo, void *outbuf, uint32_t len)
{
    uint32_t readlen = 0U, tmplen = 0U;

    if (ringbuffer_is_empty(fifo)) {
        readlen = 0U;
    } else {
        readlen = (len > fifo->data_len) ? fifo->data_len : len;
        tmplen = fifo->size - fifo->read;

        if (NULL != outbuf) {
            if (readlen <= tmplen) {
                memcpy((void *)outbuf, (void *)&fifo->buffer[fifo->read], readlen);
            } else {
                memcpy((void *)outbuf, (void *)&fifo->buffer[fifo->read], tmplen);
                memcpy((uint8_t *)outbuf + tmplen, (void *)fifo->buffer, readlen - tmplen);
            }
        }

        fifo->read = (fifo->read + readlen) % fifo->size;
        fifo->data_len -= readlen;
    }

    return readlen;
}

