// Author: Ershov V.
// Modified: Sychov. A / 12/2020
#ifndef RING_BUFFER_H
#define RING_BUFFER_H

#ifdef __cplusplus
extern "C" {
#endif
#define RING_BUFFER_HC
#include "my_assert.h"
#include <stdbool.h>
#include <stdint.h>

#ifndef RINGBUF_ASSERT
    #define RINGBUF_ASSERT(CONDITION) my_assert(CONDITION)
#endif

#ifndef RING_BUFFER_RC_TYPE
    #define RING_BUFFER_RC_TYPE int16_t
#endif

#ifndef RING_BUFFER_SIZE_TYPE
    #define RING_BUFFER_SIZE_TYPE int16_t
#endif

#ifndef RING_BUFFER_ELEMENT_TYPE
    #define RING_BUFFER_ELEMENT_TYPE unsigned char
#endif

#ifndef RING_BUFFER_ELEMENT_SIZE
    #define RING_BUFFER_ELEMENT_SIZE sizeof(RING_BUFFER_ELEMENT_TYPE)
#endif


typedef RING_BUFFER_RC_TYPE (*pfRingbuf_predicate_t)(
        void * context,
        RING_BUFFER_ELEMENT_TYPE element);

typedef void (*pfRingbuf_visitor_t)(void * context,
                                    RING_BUFFER_ELEMENT_TYPE element);


typedef enum ringbuf_rc_t
{
    RINGBUF_RC_NO_ERROR  =  0,
    RINGBUF_RC_BAD_PARAM = -1,
    RINGBUF_RC_NO_DATA   = -2,
    RINGBUF_RC_OVERFLOW  = -3
}ringbuf_rc_t;


typedef struct ringbuf_t
{
    RING_BUFFER_ELEMENT_TYPE * buffer;
    RING_BUFFER_SIZE_TYPE capacity;

    RING_BUFFER_SIZE_TYPE size;

    RING_BUFFER_SIZE_TYPE writeIndex;
    RING_BUFFER_SIZE_TYPE readIndex;
}ringbuf_t;


///
/// \brief Initializing ring buffer.
/// \param self Pointer to the ring buffer.
/// \param buffer Pointer to the memory block for the ring buffer.
/// \param capacity Size of the memory block.
/// \return RINGBUF_RC_NO_ERROR if success, error code otherwise.
///
RING_BUFFER_RC_TYPE ringbuf_init(ringbuf_t * self,
                                 void * buffer,
                                 RING_BUFFER_SIZE_TYPE capacity);

///
/// \brief Getting data with size \p from ring buffer to buffer \p buffer
/// without extracting them.
/// \param self Pointer to the ring buffer.
/// \param buffer Pointer to the buffer for storing data.
/// \param size Capacity of the buffer for storing data.
/// \param offset Reading offset from the begining of the ringbuf (amount of bytes to skip).
/// \return size of received data or error code.
///
RING_BUFFER_RC_TYPE ringbuf_peek_offset(const ringbuf_t * self,
                                 void * buffer,
                                 RING_BUFFER_SIZE_TYPE size,
                                 RING_BUFFER_SIZE_TYPE offset);

///
/// \brief Getting data with size \p from ring buffer to buffer \p buffer
/// without extracting them.
/// \param self Pointer to the ring buffer.
/// \param buffer Pointer to the buffer for storing data.
/// \param size Capacity of the buffer for storing data.
/// \return size of received data or error code.
///
static inline RING_BUFFER_RC_TYPE ringbuf_peek(const ringbuf_t * self,
                                 void * buffer,
                                 RING_BUFFER_SIZE_TYPE size)
{
   return ringbuf_peek_offset( self, buffer, size, 0 );
}

///
/// \brief Extracting data with size \p from ring buffer to buffer \p buffer.
/// \param self Pointer to the ring buffer.
/// \param buffer Pointer to the buffer for storing data.
/// \param size Capacity of the buffer for storing data.
/// \return size of received data or error code.
///
RING_BUFFER_RC_TYPE ringbuf_read(ringbuf_t * self,
                                 void * buffer,
                                 RING_BUFFER_SIZE_TYPE size);

///
/// \brief Writing data in \p buffer with size \p size into the end of a ring
/// buffer.
/// \param self Pointer to the ring buffer.
/// \param buffer Pointer to the data for writing.
/// \param size Size of data for writing.
/// \return Size of written data or error code.
///
RING_BUFFER_RC_TYPE ringbuf_write(ringbuf_t * self,
                                  const void * buffer,
                                  RING_BUFFER_SIZE_TYPE size);

///
/// \brief Writing data in \p buffer with size \p size into the front of a ring
/// buffer.
/// \param self Pointer to the ring buffer.
/// \param buffer Pointer to the data for writing.
/// \param size Size of data for writing.
/// \return Size of written data or error code.
///
RING_BUFFER_RC_TYPE ringbuf_write_front(ringbuf_t * self,
                                        const void * buffer,
                                        RING_BUFFER_SIZE_TYPE size);

///
/// \brief Getting the first element in ring buffer without extracting it.
/// \param self Pointer to the ring buffer.
/// \return The first element or error code.
///
RING_BUFFER_RC_TYPE ringbuf_front(const ringbuf_t * self);

///
/// \brief Getting the last element in ring buffer without extracting it.
/// \param self Pointer to the ring buffer.
/// \return The last element or error code.
///
RING_BUFFER_RC_TYPE ringbuf_back(const ringbuf_t * self);

///
/// \brief Adding element to the end of ring buffer.
/// \param self Pointer to the ring buffer.
/// \param element Element for adding.
/// \return Size of element or error code.
///
RING_BUFFER_RC_TYPE ringbuf_push(ringbuf_t * self,
                                 RING_BUFFER_ELEMENT_TYPE element);

///
/// \brief Extracting the first element from a ring buffer.
/// \param self Pointer to the ring buffer.
/// \return Extracted element or error code.
///
RING_BUFFER_RC_TYPE ringbuf_pop(ringbuf_t * self);

//RING_BUFFER_RC_TYPE ringbuf_push_front(ringbuf_t * self,
//                                       RING_BUFFER_ELEMENT_TYPE element);
//RING_BUFFER_RC_TYPE ringbuf_pop_front(rb_t * rb);

//RING_BUFFER_RC_TYPE ringbuf_push_back(ringbuf_t * self,
//                                      RING_BUFFER_ELEMENT_TYPE element);
//RING_BUFFER_RC_TYPE ringbuf_pop_back(ringbuf_t * self);

///
/// \brief Direct access to the underlying array.
/// \param self Pointer to the ring buffer.
/// \return Pointer to the underlying array.
///
static inline const void * ringbuf_buffer_const(const ringbuf_t * self)
{
    RINGBUF_ASSERT(self);
    return self->buffer;
}

///
/// \brief Direct access to the underlying array.
/// \param self Pointer to the ring buffer.
/// \return Pointer to the underlying array.
///
static inline void * ringbuf_buffer(ringbuf_t * self)
{
    RINGBUF_ASSERT(self);
    return self->buffer;
}

///
/// \brief Getting the sprecified by \p index element from a ring buffer.
/// \param self Pointer to the ring buffer.
/// \param index Position of the element to return.
/// \return Requested element or error code.
///
RING_BUFFER_RC_TYPE ringbuf_at(const ringbuf_t * self,
                               RING_BUFFER_SIZE_TYPE index);

///
/// \brief Checking whether the container is full.
/// \param self Pointer to the ring buffer.
/// \return true if conrainer if full, false otherwise.
///
static inline bool ringbuf_is_full(const ringbuf_t * self)
{
    RINGBUF_ASSERT(self);
    return self->size >= self->capacity;
}

///
/// \brief Checking whether the container is empty.
/// \param self Pointer to the ring buffer.
/// \return true if conrainer if empty, false otherwise.
///
static inline bool ringbuf_is_empty(const ringbuf_t * self)
{
    RINGBUF_ASSERT(self);
    return !self->size;
}

///
/// \brief Returns the number of elements that can be held in an underlying
/// storage.
/// \param self Pointer to the ring buffer.
/// \return number of elements.
///
static inline RING_BUFFER_RC_TYPE ringbuf_capacity(const ringbuf_t * self)
{
    RINGBUF_ASSERT(self);
    return self->capacity;
}

///
/// \brief Returns the number of elements in a ring buffer.
/// \param self Pointer to the ring buffer.
/// \return number of elements.
///
static inline RING_BUFFER_RC_TYPE ringbuf_size(const ringbuf_t * self)
{
    RINGBUF_ASSERT(self);
    return self->size;
}

///
/// \brief Returns free space in an underlying ring buffer storage.
/// \param self Pointer to the ring buffer.
/// \return number of elements.
///
static inline RING_BUFFER_RC_TYPE ringbuf_free_space(const ringbuf_t * self)
{
    RINGBUF_ASSERT(self);
    return self->capacity - self->size;
}

///
/// \brief Clears the contents of ring buffer.
/// \param self Pointer to the ring buffer.
///
void ringbuf_clear(ringbuf_t * self);

///
/// \brief Erases the first \p count element from the begging (front) of a
/// ring buffer.
/// \param self Pointer to the ring buffer.
/// \param count Number of elements to remove.
/// \return Number of removed elements.
///
RING_BUFFER_RC_TYPE ringbuf_erase(ringbuf_t * self,
                                  RING_BUFFER_SIZE_TYPE count);

///
/// \brief Copyies elements from \p rhs ring buffer to the end of \p lhs ring
/// buffer.
/// \param lhs Pointer to the ring buffer. Destination.
/// \param rhs Pointer to the ring buffer. Source.
/// \return number of copied elements or error code.
///
RING_BUFFER_RC_TYPE ringbuf_copy(ringbuf_t * lhs, ringbuf_t * rhs);

///
/// \brief Moving elements from \p rhs ring buffer to the end of \p lhs ring
/// buffer.
/// \note After moving procedure rhs buffer is empty (== uninitialized).
/// \param lhs Pointer to the ring buffer. Destination.
/// \param rhs Pointer to the ring buffer. Source.
/// \return number of moved elements or error code.
///
RING_BUFFER_RC_TYPE ringbuf_move(ringbuf_t * lhs, ringbuf_t * rhs);

///
/// \brief Realizes search algorithm for a ring buffer container.
/// \param self Pointer to the ring buffer.
/// \param context Pointer to the user context.
/// \param predicate Pointer to the user search functor.
/// \return index of found element or error code.
///
RING_BUFFER_RC_TYPE ringbuf_find(ringbuf_t * self,
                                 void * context,
                                 pfRingbuf_predicate_t predicate);

///
/// \brief Realized visitor algorithm for a ring buffer container.
/// \param self Pointer to the ring buffer.
/// \param context Pointer to the user context.
/// \param visitor Pointer to the visitor function. It is called on each
/// ring buffer element.
/// \return number of iterated elements or error code.
///
RING_BUFFER_RC_TYPE ringbuf_visit(ringbuf_t * self,
                                  void * context,
                                  pfRingbuf_visitor_t visitor);

//RING_BUFFER_RC_TYPE ringbuf_linearize(ringbuf_t * self);
//RING_BUFFER_RC_TYPE ringbuf_is_linearized(ringbuf_t * self);

#ifdef __cplusplus
}
#endif

#endif
