/**
 * @file lv_circle_buf.c
 *
 */

/*********************
 *      INCLUDES
 *********************/

#include "lv_assert.h"

#include "lv_circle_buf.h"
#include "lv_array.h"

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

struct _lv_circle_buf_t {
    lv_array_t array;
    uint32_t head;
    uint32_t tail;    /**< The next write position */

    bool inner_alloc; /**< true: the array is allocated by the buffer, false: the array is created from an external buffer */
};

/**********************
 *  STATIC PROTOTYPES
 **********************/

static void circle_buf_prepare_empty(lv_circle_buf_t * circle_buf);

/**********************
 *  GLOBAL VARIABLES
 **********************/

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

lv_circle_buf_t * lv_circle_buf_create(const uint32_t capacity, const uint32_t element_size)
{
    lv_circle_buf_t * circle_buf = lv_malloc(sizeof(lv_circle_buf_t));
    LV_ASSERT_MALLOC(circle_buf);

    if(circle_buf == NULL) {
        return NULL;
    }

    lv_array_init(&circle_buf->array, capacity, element_size);
    circle_buf->head = 0;
    circle_buf->tail = 0;
    circle_buf->inner_alloc = true;

    circle_buf_prepare_empty(circle_buf);

    return circle_buf;
}

lv_circle_buf_t * lv_circle_buf_create_from_buf(void * buf, const uint32_t capacity, const uint32_t element_size)
{
    LV_ASSERT_NULL(buf);

    lv_circle_buf_t * circle_buf = lv_malloc(sizeof(lv_circle_buf_t));
    LV_ASSERT_MALLOC(circle_buf);

    if(circle_buf == NULL) {
        return NULL;
    }

    lv_array_init_from_buf(&circle_buf->array, buf, capacity, element_size);
    circle_buf->head = 0;
    circle_buf->tail = 0;
    circle_buf->inner_alloc = false;

    circle_buf_prepare_empty(circle_buf);

    return circle_buf;
}

lv_circle_buf_t * lv_circle_buf_create_from_array(const lv_array_t * array)
{
    LV_ASSERT_NULL(array);
    if(array == NULL) {
        return NULL;
    }

    lv_circle_buf_t * circle_buf = lv_malloc(sizeof(lv_circle_buf_t));
    LV_ASSERT_MALLOC(circle_buf);

    if(circle_buf == NULL) {
        return NULL;
    }

    circle_buf->array = *array;
    circle_buf->head = 0;
    circle_buf->tail = 0;
    circle_buf->inner_alloc = false;

    circle_buf_prepare_empty(circle_buf);

    return circle_buf;
}

lv_result_t lv_circle_buf_resize(lv_circle_buf_t * circle_buf, const uint32_t capacity)
{
    LV_ASSERT_NULL(circle_buf);

    if(lv_array_resize(&circle_buf->array, capacity) == false) {
        return LV_RESULT_INVALID;
    }

    circle_buf->head = 0;
    circle_buf->tail = 0;

    circle_buf_prepare_empty(circle_buf);

    return LV_RESULT_OK;
}

void lv_circle_buf_destroy(lv_circle_buf_t * circle_buf)
{
    LV_ASSERT_NULL(circle_buf);

    lv_array_deinit(&circle_buf->array);

    lv_free(circle_buf);
}

uint32_t lv_circle_buf_size(const lv_circle_buf_t * circle_buf)
{
    LV_ASSERT_NULL(circle_buf);

    return circle_buf->tail - circle_buf->head;
}

uint32_t lv_circle_buf_capacity(const lv_circle_buf_t * circle_buf)
{
    LV_ASSERT_NULL(circle_buf);

    return lv_array_capacity(&circle_buf->array);
}

uint32_t lv_circle_buf_remain(const lv_circle_buf_t * circle_buf)
{
    LV_ASSERT_NULL(circle_buf);

    return lv_circle_buf_capacity(circle_buf) - lv_circle_buf_size(circle_buf);
}

bool lv_circle_buf_is_empty(const lv_circle_buf_t * circle_buf)
{
    LV_ASSERT_NULL(circle_buf);

    return !lv_circle_buf_size(circle_buf);
}

bool lv_circle_buf_is_full(const lv_circle_buf_t * circle_buf)
{
    LV_ASSERT_NULL(circle_buf);

    return !lv_circle_buf_remain(circle_buf);
}

void lv_circle_buf_reset(lv_circle_buf_t * circle_buf)
{
    LV_ASSERT_NULL(circle_buf);

    circle_buf->head = 0;
    circle_buf->tail = 0;
}

void * lv_circle_buf_head(const lv_circle_buf_t * circle_buf)
{
    LV_ASSERT_NULL(circle_buf);

    return lv_array_at(&circle_buf->array,
                       circle_buf->head % lv_circle_buf_capacity(circle_buf));
}

void * lv_circle_buf_tail(const lv_circle_buf_t * circle_buf)
{
    LV_ASSERT_NULL(circle_buf);

    return lv_array_at(&circle_buf->array,
                       circle_buf->tail % lv_circle_buf_capacity(circle_buf));
}

lv_result_t lv_circle_buf_read(lv_circle_buf_t * circle_buf, void * data)
{
    LV_ASSERT_NULL(circle_buf);

    if(lv_circle_buf_is_empty(circle_buf)) {
        circle_buf->head = 0;
        circle_buf->tail = 0;
        return LV_RESULT_INVALID;
    }

    lv_circle_buf_peek_at(circle_buf, 0, data);
    circle_buf->head++;

    return LV_RESULT_OK;
}

lv_result_t lv_circle_buf_write(lv_circle_buf_t * circle_buf, const void * data)
{
    LV_ASSERT_NULL(circle_buf);

    if(lv_circle_buf_is_full(circle_buf)) {
        return LV_RESULT_INVALID;
    }

    lv_array_assign(&circle_buf->array, circle_buf->tail % lv_circle_buf_capacity(circle_buf), data);
    circle_buf->tail++;

    return LV_RESULT_OK;
}

uint32_t lv_circle_buf_fill(lv_circle_buf_t * circle_buf, uint32_t count, lv_circle_buf_fill_cb_t fill_cb,
                            void * user_data)
{
    LV_ASSERT_NULL(circle_buf);
    LV_ASSERT_NULL(fill_cb);

    uint32_t filled = 0;
    while(count > 0 && !lv_circle_buf_is_full(circle_buf)) {
        void * data = lv_circle_buf_tail(circle_buf);
        if(fill_cb(data, circle_buf->array.element_size, (int32_t)filled, user_data) == LV_RESULT_OK) {
            circle_buf->tail++;
            filled++;
        }
        else break;

        count--;
    }

    return filled;
}

lv_result_t lv_circle_buf_skip(lv_circle_buf_t * circle_buf)
{
    LV_ASSERT_NULL(circle_buf);

    if(lv_circle_buf_is_empty(circle_buf)) {
        circle_buf->head = 0;
        circle_buf->tail = 0;
        return LV_RESULT_INVALID;
    }

    circle_buf->head++;

    return LV_RESULT_OK;
}

lv_result_t lv_circle_buf_peek(const lv_circle_buf_t * circle_buf, void * data)
{
    LV_ASSERT_NULL(circle_buf);
    LV_ASSERT_NULL(data);

    return lv_circle_buf_peek_at(circle_buf, 0, data);
}

lv_result_t lv_circle_buf_peek_at(const lv_circle_buf_t * circle_buf, const uint32_t index, void * data)
{
    LV_ASSERT_NULL(circle_buf);
    LV_ASSERT_NULL(data);

    const uint32_t real_index = (index % lv_circle_buf_size(circle_buf) + circle_buf->head) % lv_circle_buf_capacity(
                                    circle_buf);
    lv_memcpy(data, lv_array_at(&circle_buf->array, real_index), circle_buf->array.element_size);

    return LV_RESULT_OK;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

static void circle_buf_prepare_empty(lv_circle_buf_t * circle_buf)
{
    const uint32_t required = lv_array_capacity(&circle_buf->array) - lv_array_size(&circle_buf->array);
    for(uint32_t i = 0; i < required; i++) lv_array_push_back(&circle_buf->array, NULL);
}
