#ifndef RINGBUFFER_C
#define RINGBUFFER_C

#include "ringbuffer.h"
#include "../memory.c"

#define ERROR(msg) \
	do { \
		/* TODO Do something error-ish */ \
	} while(0)

#define ASSERT_NONNULL_ARG(argument) { \
	if (argument == NULL) { \
		ERROR(__func__ " got NULL for argument '" #argument "'"); \
	} \
}


struct ringbuffer* rbfr_create(const int capacity, void (*const destroy_element)(void*)) {
	ASSERT_NONNULL_ARG(destroy_element);

	struct ringbuffer* const this = alloc(sizeof (struct ringbuffer));

	this->buffer = alloc(capacity * sizeof (void*));
	this->buffer_n = capacity;
	this->size = 0;
	this->head = 0;

	this->destroy_element = destroy_element;

	return this;
}

void rbfr_destroy(struct ringbuffer* const this) {
	ASSERT_NONNULL_ARG(this);

	rbfr_clear(this);

	free(this->buffer);
	this->buffer = NULL;

	free(this);
}

int rbfr_size(const struct ringbuffer* const this) {
	ASSERT_NONNULL_ARG(this);

	return this->size;
}

int rbfr_capacity(const struct ringbuffer* this) {
	ASSERT_NONNULL_ARG(this);
	return this->buffer_n;
}

void rbfr_enqueue(struct ringbuffer* this, void* const element) {
	ASSERT_NONNULL_ARG(this);

	if (this->buffer_n == 0) {
		return;
	}

	int index = this->head + this->size;
	if (index >= this->buffer_n) {
		index -= this->buffer_n;
	}
	this->buffer[index] = element;
	if (this->size < this->buffer_n) {
		this->size++;
	} else {
		this->head++;
	}
	return;
}

bool rbfr_peek(const struct ringbuffer *const this, void** element) {
	ASSERT_NONNULL_ARG(this);
	ASSERT_NONNULL_ARG(element);

	if (this->size == 0) {
		return false;
	}
	*element = this->buffer[this->head];
	return true;
}

bool rbfr_dequeue(struct ringbuffer *const this, void** element) {
	ASSERT_NONNULL_ARG(this);
	ASSERT_NONNULL_ARG(element);

	if (!rbfr_peek(this, element)) {
		return false;
	}
	this->size--;
	this->head++;
	if (this->head >= this->buffer_n) {
		this->head = 0;
	}
	return true;
}

void rbfr_clear(struct ringbuffer* this) {
	ASSERT_NONNULL_ARG(this);

	void* element;
	while (rbfr_dequeue(this, &element)) {
		this->destroy_element(element);
	}
}

#endif // RINGBUFFER_C
