#include "etk_ringbuffer.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

/**
 * Get the element size for the specified data type
 * @param data_type The data type
 * @return Size in bytes, 0 for invalid type
 */
static int get_element_size(EtkRingBufDataType data_type)
{
    switch (data_type)
    {
    case ETK_RB_UINT16:
        return sizeof(uint16_t);
    case ETK_RB_UINT32:
        return sizeof(uint32_t);
    case ETK_RB_FLOAT:
        return sizeof(float);
    case ETK_RB_DOUBLE:
        return sizeof(double);
    default:
        return 0;
    }
}

/**
 * Initialize the ring buffer
 * @param rb Pointer to the ring buffer
 * @param buffer Buffer array
 * @param size Buffer size
 * @param data_type Data type
 */
void etk_ringbuffer_init(etk_ringbuffer_t *rb, void *buffer, int size, EtkRingBufDataType data_type)
{
    assert(rb != NULL && buffer != NULL && size > 0);

    rb->buffer       = buffer;
    rb->size         = size;
    rb->write_index  = 0;
    rb->count        = 0;
    rb->data_type    = data_type;
    rb->element_size = get_element_size(data_type);

    // Verify data type is valid
    assert(rb->element_size > 0);
}

/**
 * Generic write function for ring buffer
 * @param rb Pointer to the ring buffer
 * @param value Pointer to the value to write
 */
static void etk_ringbuffer_write_generic(etk_ringbuffer_t *rb, void *value)
{
    assert(rb != NULL && value != NULL && rb->buffer != NULL);

    char *buffer_ptr = (char *)rb->buffer;
    char *write_ptr  = buffer_ptr + (rb->write_index * rb->element_size);
    memcpy(write_ptr, value, rb->element_size);

    rb->write_index = (rb->write_index + 1) % rb->size;
    if (rb->count < rb->size)
        rb->count++;
}

/**
 * Write a uint16_t value to the ring buffer
 * @param rb Pointer to the ring buffer
 * @param value Value to write
 */
void etk_ringbuffer_write_u16(etk_ringbuffer_t *rb, uint16_t value)
{
    if (rb == NULL || rb->data_type != ETK_RB_UINT16)
        return;
    etk_ringbuffer_write_generic(rb, &value);
}

/**
 * Write a uint32_t value to the ring buffer
 * @param rb Pointer to the ring buffer
 * @param value Value to write
 */
void etk_ringbuffer_write_u32(etk_ringbuffer_t *rb, uint32_t value)
{
    if (rb == NULL || rb->data_type != ETK_RB_UINT32)
        return;
    etk_ringbuffer_write_generic(rb, &value);
}

/**
 * Write a float value to the ring buffer
 * @param rb Pointer to the ring buffer
 * @param value Value to write
 */
void etk_ringbuffer_write_f32(etk_ringbuffer_t *rb, float value)
{
    if (rb == NULL || rb->data_type != ETK_RB_FLOAT)
        return;
    etk_ringbuffer_write_generic(rb, &value);
}

/**
 * Write a double value to the ring buffer
 * @param rb Pointer to the ring buffer
 * @param value Value to write
 */
void etk_ringbuffer_write_f64(etk_ringbuffer_t *rb, double value)
{
    if (rb == NULL || rb->data_type != ETK_RB_DOUBLE)
        return;
    etk_ringbuffer_write_generic(rb, &value);
}

/**
 * Get the number of elements in the ring buffer
 * @param rb Pointer to the ring buffer
 * @return Number of elements
 */
int etk_ringbuffer_get_count(const etk_ringbuffer_t *rb)
{
    return (rb == NULL) ? 0 : rb->count;
}

/**
 * Generic read function for ring buffer
 * @param rb Pointer to the ring buffer
 * @param index Index of the element to read (0 = oldest)
 * @param result Pointer to store the result
 */
static void etk_ringbuffer_read_generic(const etk_ringbuffer_t *rb, int index, void *result)
{
    if (rb == NULL || result == NULL || rb->buffer == NULL)
        return;
    if (index < 0 || index >= rb->count)
        return;

    // Calculate actual position: move index positions back from write_index
    int   actual_index = (rb->write_index - rb->count + index + rb->size) % rb->size;
    char *buffer_ptr   = (char *)rb->buffer;
    char *read_ptr     = buffer_ptr + (actual_index * rb->element_size);
    memcpy(result, read_ptr, rb->element_size);
}

/**
 * Get uint16_t data from the ring buffer
 * @param rb Pointer to the ring buffer
 * @param index Data index (0 = oldest data)
 * @return Data value, 0 if index is invalid
 */
uint16_t etk_ringbuffer_get_data_u16(const etk_ringbuffer_t *rb, int index)
{
    uint16_t result = 0;
    if (rb == NULL || rb->data_type != ETK_RB_UINT16)
        return 0;
    etk_ringbuffer_read_generic(rb, index, &result);
    return result;
}

/**
 * Get uint32_t data from the ring buffer
 * @param rb Pointer to the ring buffer
 * @param index Data index (0 = oldest data)
 * @return Data value, 0 if index is invalid
 */
uint32_t etk_ringbuffer_get_data_u32(const etk_ringbuffer_t *rb, int index)
{
    uint32_t result = 0;
    if (rb == NULL || rb->data_type != ETK_RB_UINT32)
        return 0;
    etk_ringbuffer_read_generic(rb, index, &result);
    return result;
}

/**
 * Get float data from the ring buffer
 * @param rb Pointer to the ring buffer
 * @param index Data index (0 = oldest data)
 * @return Data value, 0.0f if index is invalid
 */
float etk_ringbuffer_get_data_f32(const etk_ringbuffer_t *rb, int index)
{
    float result = 0.0f;
    if (rb == NULL || rb->data_type != ETK_RB_FLOAT)
        return 0.0f;
    etk_ringbuffer_read_generic(rb, index, &result);
    return result;
}

/**
 * Get double data from the ring buffer
 * @param rb Pointer to the ring buffer
 * @param index Data index (0 = oldest data)
 * @return Data value, 0.0 if index is invalid
 */
double etk_ringbuffer_get_data_f64(const etk_ringbuffer_t *rb, int index)
{
    double result = 0.0;
    if (rb == NULL || rb->data_type != ETK_RB_DOUBLE)
        return 0.0;
    etk_ringbuffer_read_generic(rb, index, &result);
    return result;
}

/**
 * Check if the ring buffer is full
 * @param rb Pointer to the ring buffer
 * @return true if full, false if not full
 */
bool etk_ringbuffer_is_full(const etk_ringbuffer_t *rb)
{
    return (rb == NULL) ? false : (rb->count == rb->size);
}

/**
 * Check if the ring buffer is empty
 * @param rb Pointer to the ring buffer
 * @return true if empty, false if not empty
 */
bool etk_ringbuffer_is_empty(const etk_ringbuffer_t *rb)
{
    return (rb == NULL) ? true : (rb->count == 0);
}

/**
 * Clear the ring buffer
 * @param rb Pointer to the ring buffer
 */
void etk_ringbuffer_clear(etk_ringbuffer_t *rb)
{
    if (rb == NULL)
        return;
    rb->write_index = 0;
    rb->count       = 0;
}

/**
 * Get the capacity of the ring buffer
 * @param rb Pointer to the ring buffer
 * @return Buffer capacity
 */
int etk_ringbuffer_get_capacity(const etk_ringbuffer_t *rb)
{
    return (rb == NULL) ? 0 : rb->size;
}

/**
 * Peek uint16_t data from the ring buffer (without modifying buffer state)
 * @param rb Pointer to the ring buffer
 * @param index Data index (0 = oldest data)
 * @return Data value, 0 if index is invalid
 */
uint16_t etk_ringbuffer_peek_u16(const etk_ringbuffer_t *rb, int index)
{
    return etk_ringbuffer_get_data_u16(rb, index);
}

/**
 * Peek uint32_t data from the ring buffer (without modifying buffer state)
 * @param rb Pointer to the ring buffer
 * @param index Data index (0 = oldest data)
 * @return Data value, 0 if index is invalid
 */
uint32_t etk_ringbuffer_peek_u32(const etk_ringbuffer_t *rb, int index)
{
    return etk_ringbuffer_get_data_u32(rb, index);
}

/**
 * Peek float data from the ring buffer (without modifying buffer state)
 * @param rb Pointer to the ring buffer
 * @param index Data index (0 = oldest data)
 * @return Data value, 0.0f if index is invalid
 */
float etk_ringbuffer_peek_f32(const etk_ringbuffer_t *rb, int index)
{
    return etk_ringbuffer_get_data_f32(rb, index);
}

/**
 * Peek double data from the ring buffer (without modifying buffer state)
 * @param rb Pointer to the ring buffer
 * @param index Data index (0 = oldest data)
 * @return Data value, 0.0 if index is invalid
 */
double etk_ringbuffer_peek_f64(const etk_ringbuffer_t *rb, int index)
{
    return etk_ringbuffer_get_data_f64(rb, index);
}
