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

struct circle_buff {
    void *buffer;
    void *buffer_end;

    size_t capacity;
    size_t count;
    size_t item_size;

    void *head;
    void *tail;

    data_free_func data_free;
};

struct circle_buff *cb_create(size_t capacity, size_t item_size, data_free_func cb)
{
    int hdr_raw_size = sizeof(struct circle_buff);
    int hdr_align_size = (hdr_raw_size + sizeof(int) - 1) & (~(sizeof(int) - 1));
    int item_align_size = (item_size + sizeof(int) - 1) & (~(sizeof(int) - 1));

    struct circle_buff *c = malloc(hdr_align_size + capacity * item_align_size);
    if (!c) {
        return NULL;
    }

    c->buffer = (char *)c + hdr_align_size;
    c->buffer_end = (char *)c->buffer + capacity * item_align_size;

    c->capacity = capacity;
    c->count = 0;
    c->item_size = item_align_size;
    c->head = c->buffer;
    c->tail = c->buffer;
    c->data_free = cb;

    return c;
}

void cb_destroy(struct circle_buff *c)
{
    if (c->data_free) {
        void *iter = c->head;
	int count = c->count;

        for (int i = 0; i < count; i++) {
	    c->data_free(iter);

	    iter = (char *)iter + c->item_size;
	    if (iter >= c->buffer_end) {
	        iter = c->buffer;
	    }
        }
    }

    if (c) {
        free(c);
    }
}

bool cb_is_full(struct circle_buff *c) {
    return (c->count == c->capacity);
}

bool cb_is_empty(struct circle_buff *c) {
    return (c->count == 0);
}

void cb_push_back(struct circle_buff *c, void *item)
{
    memcpy(c->tail, item, c->item_size);
    c->tail = (char *)c->tail + c->item_size;

    if (c->tail >= c->buffer_end) {
        c->tail = c->buffer;
    }

    if (c->count != c->capacity) {
        c->count++;

    } else {
        /* over written, update the head */
        c->head = c->tail;
    }

}

int cb_pop_front(struct circle_buff *c, void *item)
{
    if (cb_is_empty(c)) {
        return -1;
    }

    memcpy(item, c->head, c->item_size);
    
    c->head = (char *)c->head + c->item_size;
    
    if (c->head >= c->buffer_end) {
        c->head = c->buffer;
    }

    c->count--;
    return 0;
}


