// Copyright: SPDX-License-Identifier:  GPL-3.0-only

#include "cringbuffer.h"
#include "cringbuffer_internal.h"

#include <stdlib.h>
#include <assert.h>
#include <string.h>

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

// this allows user to use their own
// custom memory allocation functions
#ifdef RBUF_CUSTOM_MALLOC
#include "../../helpers/ringbuffer_pal.h"
#else
#define crbuf_malloc(...) malloc(__VA_ARGS__)
#define crbuf_free(...) free(__VA_ARGS__)
#endif

rbuf_t rbuf_create(size_t size)
{
    rbuf_t buffer = crbuf_malloc(sizeof(struct rbuf_t) + size);
    if (!buffer)
        return NULL;

    memset(buffer, 0, sizeof(struct rbuf_t));

    buffer->data = ((char*)buffer) + sizeof(struct rbuf_t);

    buffer->head = buffer->data;
    buffer->tail = buffer->data;
    buffer->size = size;
    buffer->end = buffer->data + size;

    return buffer;
}

void rbuf_free(rbuf_t buffer)
{
    crbuf_free(buffer);
}

void rbuf_flush(rbuf_t buffer)
{
    buffer->head = buffer->data;
    buffer->tail = buffer->data;
    buffer->size_data = 0;
}

char *rbuf_get_linear_insert_range(rbuf_t buffer, size_t *bytes)
{
    *bytes = 0;
    if (buffer->head == buffer->tail && buffer->size_data)
        return NULL;

    *bytes = ((buffer->head >= buffer->tail) ? buffer->end : buffer->tail) - buffer->head;
    return buffer->head;
}

char *rbuf_get_linear_read_range(rbuf_t buffer, size_t *bytes)
{
    *bytes = 0;
    if(buffer->head == buffer->tail && !buffer->size_data)
        return NULL;

    *bytes = ((buffer->tail >= buffer->head) ? buffer->end : buffer->head) - buffer->tail;

    return buffer->tail;
}

int rbuf_bump_head(rbuf_t buffer, size_t bytes)
{
    size_t free_bytes = rbuf_bytes_free(buffer);
    if (bytes > free_bytes)
        return 0;
    int i = buffer->head - buffer->data;
    buffer->head = &buffer->data[(i + bytes) % buffer->size];
    buffer->size_data += bytes;
    return 1;
}

int rbuf_bump_tail(rbuf_t buffer, size_t bytes)
{
    if(!rbuf_bump_tail_noopt(buffer, bytes))
        return 0;

    // if tail catched up with head
    // start writing buffer from beggining
    // this is not necessary (rbuf must work well without it)
    // but helps to optimize big writes as rbuf_get_linear_insert_range
    // will return bigger continuous region
    if(buffer->tail == buffer->head) {
        assert(buffer->size_data == 0);
        rbuf_flush(buffer);
    }

    return 1;
}

size_t rbuf_get_capacity(rbuf_t buffer)
{
    return buffer->size;
}

size_t rbuf_bytes_available(rbuf_t buffer)
{
    return buffer->size_data;
}

size_t rbuf_bytes_free(rbuf_t buffer)
{
    return buffer->size - buffer->size_data;
}

size_t rbuf_push(rbuf_t buffer, const char *data, size_t len)
{
    size_t to_cpy;
    char *w_ptr = rbuf_get_linear_insert_range(buffer, &to_cpy);
    if(!to_cpy)
        return to_cpy;

    to_cpy = MIN(to_cpy, len);
    memcpy(w_ptr, data, to_cpy);
    rbuf_bump_head(buffer, to_cpy);
    if(to_cpy < len)
        to_cpy += rbuf_push(buffer, &data[to_cpy], len - to_cpy);
    return to_cpy;
}

size_t rbuf_pop(rbuf_t buffer, char *data, size_t len)
{
    size_t to_cpy;
    const char *r_ptr = rbuf_get_linear_read_range(buffer, &to_cpy);
    if(!to_cpy)
        return to_cpy;

    to_cpy = MIN(to_cpy, len);
    memcpy(data, r_ptr, to_cpy);
    rbuf_bump_tail(buffer, to_cpy);
    if(to_cpy < len)
        to_cpy += rbuf_pop(buffer, &data[to_cpy], len - to_cpy);
    return to_cpy;
}

static inline void rbuf_ptr_inc(rbuf_t buffer, const char **ptr)
{
    (*ptr)++;
    if(*ptr >= buffer->end)
        *ptr = buffer->data;
}

int rbuf_memcmp(rbuf_t buffer, const char *haystack, const char *needle, size_t needle_bytes)
{
    const char *end = needle + needle_bytes;

    // as head==tail can mean 2 things here
    if (haystack == buffer->head && buffer->size_data) {
        if (*haystack != *needle)
            return (*haystack - *needle);
        rbuf_ptr_inc(buffer, &haystack);
        needle++;
    }

    while (haystack != buffer->head && needle != end) {
        if (*haystack != *needle)
            return (*haystack - *needle);
        rbuf_ptr_inc(buffer, &haystack);
        needle++;
    }
    return 0;
}

int rbuf_memcmp_n(rbuf_t buffer, const char *to_cmp, size_t to_cmp_bytes)
{
    return rbuf_memcmp(buffer, buffer->tail, to_cmp, to_cmp_bytes);
}

char *rbuf_find_bytes(rbuf_t buffer, const char *needle, size_t needle_bytes, int *found_idx)
{
    const char *ptr = buffer->tail;
    *found_idx = 0;

    if (!rbuf_bytes_available(buffer))
        return NULL;

    if (buffer->head == buffer->tail && buffer->size_data) {
        if(!rbuf_memcmp(buffer, ptr, needle, needle_bytes))
            return (char *)ptr;
        rbuf_ptr_inc(buffer, &ptr);
        (*found_idx)++;
    }

    while (ptr != buffer->head)
    {
        if(!rbuf_memcmp(buffer, ptr, needle, needle_bytes))
            return (char *)ptr;
        rbuf_ptr_inc(buffer, &ptr);
        (*found_idx)++;
    }
    return NULL;
}
