// Author: Ershov V.
// Modified: Sychov. A / 12/2020
#include "ringbuffer/ring_buffer.h"

#include <stdlib.h>
#include <string.h>
#include "my_assert.h"

STATIC_ASSERT(sizeof(RING_BUFFER_RC_TYPE) >= sizeof(RING_BUFFER_RC_TYPE),
              error);
//see ringbuf_pop
STATIC_ASSERT(sizeof(RING_BUFFER_RC_TYPE) > RING_BUFFER_ELEMENT_SIZE, error);

//ToDo ring buffer with power of 2 buffer size

RING_BUFFER_RC_TYPE ringbuf_write_s(ringbuf_t * self,
                                    const RING_BUFFER_ELEMENT_TYPE * buffer,
                                    RING_BUFFER_SIZE_TYPE size);

static inline RING_BUFFER_SIZE_TYPE ringbuf_wr_index_to_tail_s(const ringbuf_t * self);
static inline RING_BUFFER_SIZE_TYPE ringbuf_rd_index_to_tail_s(const ringbuf_t * self);

static inline void ringbuf_add_index_s(RING_BUFFER_SIZE_TYPE * index,
                                       RING_BUFFER_SIZE_TYPE capacity,
                                       RING_BUFFER_SIZE_TYPE add);
static inline void ringbuf_add_write_index_s(ringbuf_t * self,
                                             RING_BUFFER_SIZE_TYPE add);
static inline void ringbuf_add_read_index_s(ringbuf_t * self,
                                             RING_BUFFER_SIZE_TYPE add);

static inline void ringbuf_reset_s(ringbuf_t * self);


RING_BUFFER_RC_TYPE ringbuf_init(ringbuf_t * self,
                                 void * buffer,
                                 RING_BUFFER_SIZE_TYPE capacity)
{
    RINGBUF_ASSERT(self);

    if(NULL == buffer || 0 >= capacity)
    {
        return RINGBUF_RC_BAD_PARAM;
    }

    memset(self, 0x00, sizeof(*self));
    self->buffer = buffer;
    self->capacity = capacity;

    return RINGBUF_RC_NO_ERROR;
}

RING_BUFFER_RC_TYPE ringbuf_peek_offset(const ringbuf_t * self,
                                 void * buffer,
                                 RING_BUFFER_SIZE_TYPE size,
                                 RING_BUFFER_SIZE_TYPE offset )
{
    RINGBUF_ASSERT(self && buffer);

    if(NULL == buffer || 0 >= size)
    {
        return RINGBUF_RC_BAD_PARAM;
    }

    char * buf = buffer;

    RING_BUFFER_RC_TYPE rc = self->size;

    if( offset >= rc )
    {
        return RINGBUF_RC_NO_DATA;
    }

    if(rc > size + offset)
    {
        rc = size;
    }
    else
    {
        size = rc - offset;
        rc -= offset;
    }

    RING_BUFFER_SIZE_TYPE readIndex = self->readIndex;

    ringbuf_add_index_s(&readIndex, self->capacity, offset);

    while(size)
    {
        RING_BUFFER_SIZE_TYPE bytes = self->capacity - readIndex;
        if(bytes > size)
        {
            bytes = size;
        }

        memcpy(buf, &self->buffer[readIndex], bytes);
        buf += bytes;
        size -= bytes;

        ringbuf_add_index_s(&readIndex, self->capacity, bytes);
    }

    return rc;
}

RING_BUFFER_RC_TYPE ringbuf_read(ringbuf_t * self,
                                 void * buffer,
                                 RING_BUFFER_SIZE_TYPE size)
{
    RINGBUF_ASSERT(self);

    if(NULL == buffer || 0 >= size)
    {
        return RINGBUF_RC_BAD_PARAM;
    }

    char * buf = buffer;

    RING_BUFFER_RC_TYPE rc = self->size;
    if(rc > size)
    {
        rc = size;
    }
    else
    {
        size = rc;
    }

    while(size)
    {
        RING_BUFFER_SIZE_TYPE bytes = ringbuf_rd_index_to_tail_s(self);
        if(bytes > size)
        {
            bytes = size;
        }

        memcpy(buf, &self->buffer[self->readIndex], bytes);
        buf += bytes;
        size -= bytes;
        ringbuf_add_read_index_s(self, bytes);
    }
    self->size -= rc;

    #if 0 // refactoring
    RING_BUFFER_SIZE_TYPE tail = ringbuf_bytes_to_tail_s(self);
    if(tail < size)
    {
        memcpy(buffer, &self->buffer[self->readIndex], tail);
        buffer += tail;
        size -= tail;
        self->readIndex = 0;
    }

    if(size)
    {
        memcpy(buffer, &self->buffer[self->readIndex], size);
    }

    ringbuf_add_read_index_s(self, size);
    self->size -= rc;
    #endif

    return rc;
}

RING_BUFFER_RC_TYPE ringbuf_write(ringbuf_t * self,
                                  const void * buffer,
                                  RING_BUFFER_SIZE_TYPE size)
{
    RINGBUF_ASSERT(self);

    if(NULL == buffer || 0 >= size)
    {
        return RINGBUF_RC_BAD_PARAM;
    }

    RING_BUFFER_RC_TYPE rc = ringbuf_free_space(self);
    #if 0 // refactoring
    if(rc > size)
    {
        rc = size;
    }
    else
    {
        size = rc;
    }
    #endif
    if(size > rc)
    {
        size = rc;
    }

    return ringbuf_write_s(self, buffer, size);

    #if 0 // refactoring
    while(size)
    {
        RING_BUFFER_SIZE_TYPE bytes = ringbuf_wr_index_to_tail_s(self);
        if(bytes > size)
        {
            bytes = size;
        }

        memcpy(&self->buffer[self->writeIndex], buffer, bytes);
        buffer += bytes;
        size -= bytes;
        ringbuf_add_write_index_s(self, bytes);
    }
    self->size += rc;
    #endif

    #if 0 // refactoring
    RING_BUFFER_SIZE_TYPE tail = ringbuf_space_to_tail_s(self);
    if(tail < size)
    {
        memcpy(&self->buffer[self->writeIndex], buffer, tail);
        buffer += tail;
        size -= tail;
        self->writeIndex = 0;
    }

    if(size)
    {
        memcpy(&self->buffer[self->writeIndex], buffer, size);
    }

    ringbuf_add_write_index_s(self, size);
    self->size += rc;
    #endif

    //return rc;
}

RING_BUFFER_RC_TYPE ringbuf_write_front(ringbuf_t * self,
                                        const void * buffer,
                                        RING_BUFFER_SIZE_TYPE size)
{
    RINGBUF_ASSERT(self);

    if(NULL == buffer || 0 >= size)
    {
        return RINGBUF_RC_BAD_PARAM;
    }

    const char * buf = buffer;

    RING_BUFFER_RC_TYPE rc = ringbuf_free_space(self);
    if(size > rc)
    {
        size = rc;
    }

    // Caching return value.
    rc = size;

    // Using read index instead of write index because it is a front writing.

    // Shifting read index into initial position.
    self->readIndex -= size;
    if(self->readIndex < 0)
    {
        self->readIndex += self->capacity;
    }

    RING_BUFFER_SIZE_TYPE readIndex = self->readIndex;
    while(size)
    {
        RING_BUFFER_SIZE_TYPE bytes = ringbuf_rd_index_to_tail_s(self);
        if(bytes > size)
        {
            bytes = size;
        }

        memcpy(&self->buffer[readIndex], buf, size);
        buf += bytes;
        size -= bytes;

        ringbuf_add_index_s(&readIndex, self->capacity, bytes);
    }
    self->size += rc;

    return rc;
}

RING_BUFFER_RC_TYPE ringbuf_front(const ringbuf_t * self)
{
    RINGBUF_ASSERT(self);

    if(!self->size)
    {
        return RINGBUF_RC_NO_DATA;
    }

    return self->buffer[self->readIndex];
}

RING_BUFFER_RC_TYPE ringbuf_back(const ringbuf_t * self)
{
    RINGBUF_ASSERT(self);

    if(!self->size)
    {
        return RINGBUF_RC_NO_DATA;
    }

    RING_BUFFER_SIZE_TYPE readIndex = self->readIndex;
    ringbuf_add_index_s(&readIndex, self->capacity, self->size);

    return self->buffer[readIndex];
}

RING_BUFFER_RC_TYPE ringbuf_push(ringbuf_t * self,
                                 RING_BUFFER_ELEMENT_TYPE element)
{
    RINGBUF_ASSERT(self);

    if(!ringbuf_free_space(self))
    {
        return RINGBUF_RC_OVERFLOW;
    }

    self->buffer[self->writeIndex] = element;
    ringbuf_add_write_index_s(self, 1);
    ++self->size;

    return 1;
}

RING_BUFFER_RC_TYPE ringbuf_pop(ringbuf_t * self)
{
    RINGBUF_ASSERT(self);

    if(!self->size)
    {
        return RINGBUF_RC_NO_DATA;
    }

    RING_BUFFER_RC_TYPE rc = self->buffer[self->readIndex];
    ringbuf_add_read_index_s(self, 1);
    --self->size;

    return rc;
}

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

//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);

RING_BUFFER_RC_TYPE ringbuf_at(const ringbuf_t * self, RING_BUFFER_SIZE_TYPE index)
{
    RINGBUF_ASSERT(self);

    if(index >= self->size)
    {
        return RINGBUF_RC_BAD_PARAM;
    }

    RING_BUFFER_SIZE_TYPE readIndex = self->readIndex;
    ringbuf_add_index_s(&readIndex, self->capacity, index);

    return self->buffer[readIndex];
}

void ringbuf_clear(ringbuf_t * self)
{
    RINGBUF_ASSERT(self);

    self->size = 0;
    self->writeIndex = 0;
    self->readIndex = 0;
}

RING_BUFFER_RC_TYPE ringbuf_erase(ringbuf_t * self, RING_BUFFER_SIZE_TYPE count)
{
    RINGBUF_ASSERT(self);

    RING_BUFFER_RC_TYPE rc = self->size;
    if(rc > count)
    {
        rc = count;
    }
    else
    {
        count = rc;
    }

    ringbuf_add_read_index_s(self, rc);
    self->size -= rc;

    return rc;
}

RING_BUFFER_RC_TYPE ringbuf_copy(ringbuf_t * lhs, ringbuf_t * rhs)
{
    if(NULL == lhs || NULL == rhs)
    {
        return RINGBUF_RC_BAD_PARAM;
    }

    if(ringbuf_free_space(lhs) < rhs->size)
    {
        return RINGBUF_RC_OVERFLOW;
    }

    RING_BUFFER_SIZE_TYPE size = rhs->size;
    RING_BUFFER_SIZE_TYPE readIndex = rhs->readIndex;

    while(size)
    {
        RING_BUFFER_SIZE_TYPE bytes = rhs->capacity - readIndex;
        if(bytes > size)
        {
            bytes = size;
        }
        size -= ringbuf_write_s(lhs, &rhs->buffer[readIndex], bytes);
        ringbuf_add_index_s(&readIndex, rhs->capacity, bytes);
    }

    return rhs->size;
}

RING_BUFFER_RC_TYPE ringbuf_move(ringbuf_t * lhs, ringbuf_t * rhs)
{
    if(NULL == lhs || NULL == rhs)
    {
        return RINGBUF_RC_BAD_PARAM;
    }

    memcpy(lhs, rhs, sizeof(*lhs));
    ringbuf_reset_s(rhs);

    return RINGBUF_RC_NO_ERROR;
}

RING_BUFFER_RC_TYPE ringbuf_find(ringbuf_t * self,
                                 void * context,
                                 pfRingbuf_predicate_t predicate)
{
    RINGBUF_ASSERT(self);

    if(NULL == predicate)
    {
        return RINGBUF_RC_BAD_PARAM;
    }

    RING_BUFFER_SIZE_TYPE index = 0;
    RING_BUFFER_SIZE_TYPE size = self->size;
    RING_BUFFER_SIZE_TYPE readIndex = self->readIndex;

    while(size--)
    {
        if(predicate(context, self->buffer[readIndex]))
        {
            goto FEND;
        }
        ringbuf_add_index_s(&readIndex, self->capacity, 1);
        ++index;
    }
    index = RINGBUF_RC_NO_DATA;

FEND:
    return index;
}

RING_BUFFER_RC_TYPE ringbuf_visit(ringbuf_t * self,
                                  void * context,
                                  pfRingbuf_visitor_t visitor)
{
    RINGBUF_ASSERT(self);

    if(NULL == visitor)
    {
        return RINGBUF_RC_BAD_PARAM;
    }

    RING_BUFFER_SIZE_TYPE size = self->size;
    RING_BUFFER_SIZE_TYPE readIndex = self->readIndex;

    while(size--)
    {
        visitor(context, self->buffer[readIndex]);
        ringbuf_add_index_s(&readIndex, self->capacity, 1);
    }

    return self->size;
}

RING_BUFFER_RC_TYPE ringbuf_write_s(ringbuf_t * self,
                                    const RING_BUFFER_ELEMENT_TYPE * buffer,
                                    RING_BUFFER_SIZE_TYPE size)
{
    RINGBUF_ASSERT(self && buffer && size >= 0);

    RING_BUFFER_RC_TYPE rc = size;

    while(size)
    {
        RING_BUFFER_SIZE_TYPE bytes = ringbuf_wr_index_to_tail_s(self);
        if(bytes > size)
        {
            bytes = size;
        }

        memcpy(&self->buffer[self->writeIndex], buffer, bytes);
        buffer += bytes;
        size -= bytes;
        ringbuf_add_write_index_s(self, bytes);
    }
    self->size += rc;

    return rc;
}

static inline RING_BUFFER_SIZE_TYPE ringbuf_wr_index_to_tail_s(const ringbuf_t * self)
{
    RINGBUF_ASSERT(self);
    return self->capacity - self->writeIndex;
}

static inline RING_BUFFER_SIZE_TYPE ringbuf_rd_index_to_tail_s(const ringbuf_t * self)
{
    RINGBUF_ASSERT(self);
    return self->capacity - self->readIndex;
}

static inline void ringbuf_add_index_s(RING_BUFFER_SIZE_TYPE * index,
                                       RING_BUFFER_SIZE_TYPE capacity,
                                       RING_BUFFER_SIZE_TYPE add)
{
    RINGBUF_ASSERT(index && capacity > 0 && add >= 0);

    *index += add;
    if(*index >= capacity)
    {
        *index -= capacity;
    }
}

static inline void ringbuf_add_write_index_s(ringbuf_t * self,
                                             RING_BUFFER_SIZE_TYPE add)
{
    RINGBUF_ASSERT(self);
    ringbuf_add_index_s(&self->writeIndex, self->capacity, add);
}

static inline void ringbuf_add_read_index_s(ringbuf_t * self,
                                            RING_BUFFER_SIZE_TYPE add)
{
    RINGBUF_ASSERT(self);
    ringbuf_add_index_s(&self->readIndex, self->capacity, add);
}

static inline void ringbuf_reset_s(ringbuf_t * self)
{
    RINGBUF_ASSERT(self);
    memset(self, 0x00, sizeof(*self));
}
