/**
  ******************************************************************************
  * @file	ringbuf.c
  * @brief  driver for a ring buffer used for UART RX
 ******************************************************************************
  * @author: Thomas Kuschel KW4NZ
  * created 2022-07-12
  *
  * A description can be found in the header file ringbuf.h
 ******************************************************************************/
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <stdlib.h>	/* malloc */
#include <stdio.h>	/* printf */
#include <string.h>	/* memcpy */
#include "ringbuf.h"

/* macros */
#define MEM_USED(b) ((b->size + b->head - b->tail) % b->size)
#define MEM_FREE(b) ((b->size + b->tail - b->head - 1) % b->size + 1)

#if RING_STATISTICS_ENABLED
typedef struct stat {
	uint32_t overflows;
	uint32_t reads;
	uint32_t writes;
} stat_t;
#endif

typedef struct ringbuf {
#if RING_STATISTICS_ENABLED
	stat_t		statistics;
#endif
	ringbuf_rcv_cb_t	rcv_callback;
	void		*rcv_cb_data;
	uint8_t		*buf;
	uint16_t	size;
	uint16_t	head;
	uint16_t	tail;
	uint16_t	delimiterfound;
	uint16_t	max_read_len;
	unsigned	full :1;	// not implemented yet
	unsigned	halffull:1;	// not implemented yet
	unsigned	overflow:1;
	unsigned	allowoverwrite:1;
} ringbuf_t;

struct ringbuf * ringbuf_create(size_t size, ringbuf_param_t param) {

	struct ringbuf * rb;
	if (size == 0 || size > USHRT_MAX)
		return NULL;

	// rb = malloc(sizeof(ringbuf_t) + size * sizeof(uint8_t));
	rb = calloc(1, sizeof(ringbuf_t) + size * sizeof(uint8_t));
	if (rb == NULL) {
		puts("Memory not allocated.");
		exit(0);
	} else {
		//puts("Memory successfully allocated.");
		// the data area is connected to the structure
		rb->buf = (uint8_t *)rb + sizeof(ringbuf_t);
		rb->size = (uint16_t)size;
		// rb->head = rb->tail = rb->halffull = rb->overflow 0;
		if (param & RINGBUF_ALLOWOVERWRITE)
			rb->allowoverwrite = 1;
		rb->max_read_len = RINGBUF_MAX_READ_LEN;
	}
	return rb;
}

void ringbuf_destroy(struct ringbuf *ring) {
	if (ring != 0)
		free(ring);
}

int ringbuf_dump(struct ringbuf *ring) {

	if (ring == NULL)
			return -EINVAL;
	printf("Buffer: 0x%08x\n", (unsigned int) ring);
	printf("Start:  0x%08x\n", (unsigned int) ring->buf);
	printf("Size:   total: %d  used: %d  free: %d\n", ring->size, MEM_USED(ring), MEM_FREE(ring));
	printf("Head:   %d\n", ring->head);
	printf("Tail:   %d\n", ring->tail);
	printf("Empty:  %s\n", (ring->head == ring->tail) ? "yes" : "no");
	printf("Max read length: %d\n", ring->max_read_len);
	for (size_t i = 0; i < ring->size; i += 16 ) {
		for (size_t j = 0; j < 16 && (j + i) < ring->size; j++) {
		//printf("%02x%s", *(ring->buf +i), ((i%4)==3)?(((i%16)==15)?"\n":" "):"");
			printf("%02x%s", *(ring->buf + i + j), ((j%4)==3)?" ":"");
		}
		putchar(' ');
		for (size_t j = 0; j < 16 && (j + i) < ring->size; j++) {
			uint8_t b = *(ring->buf + i + j);
			printf("%c", ((b >= ' ') && (b < 127))? b: '.');
		}
		puts("");
	}
	return 0;
}

int ringbuf_push(struct ringbuf *ring, const uint8_t *data, size_t size) {

	size_t delimiterpos;
	size_t delimiterfound = 0;
	uint16_t head;
	uint8_t *ptr;

	if (ring == NULL || size == 0 || data == NULL || size > USHRT_MAX)
		return -EINVAL;

	if (size >= ring->size)
		return -ENOMEM;

	if (size >= (size_t)MEM_FREE(ring)) { // no free space available, but overwrite ?
#if RING_STATISTICS_ENABLED
		ring->statistics.overflows++;
#endif
		if (ring->allowoverwrite)
			ring->overflow = 1;
		else
			return -ENOMEM;
	}

	head = ring->head;
	if (head + size > ring->size) {
		uint16_t remaining = ring->size - head;
		memcpy(ring->buf + head, data, remaining);
		ring->head = (uint16_t)(size - remaining);
		memcpy(ring->buf, data + remaining, ring->head);
	} else {
		memcpy(ring->buf + head, data, size);
		ring->head += (uint16_t)size;
	}
	for (delimiterpos = 0; delimiterpos < size; delimiterpos++) {
		if (data[delimiterpos] == '\n' || data[delimiterpos] == 0 ) {
			delimiterfound++;
			ptr = ring->buf + ((head + delimiterpos) % ring->size);
			*ptr = 0;
		}
	}
	ring->head %= ring->size;

#if RING_STATISTICS_ENABLED
	ring->statistics.writes +=size;
#endif
	ring->delimiterfound = (uint16_t)delimiterfound;
	//call registered callback function
	if (ring->delimiterfound && ring->rcv_callback != NULL)
		ring->rcv_callback(ring->delimiterfound, ring->rcv_cb_data);
	return (int)size;
}

int ringbuf_clear(struct ringbuf *ring) {
	if (ring == NULL)
		return -EINVAL;
	ring->tail = ring->head;
	ring->full = ring->halffull = ring->overflow = 0;
	return 0;
}

int ringbuf_is_empty(struct ringbuf *ring) {
	if (ring == NULL)
		return -EINVAL;
	return (ring->tail == ring->head);
}

int ringbuf_pull(struct ringbuf *ring, uint8_t *data, size_t maxsize) {

	size_t datasize;

	if (ring == NULL || maxsize == 0 || data == NULL || maxsize > USHRT_MAX)
			return -EINVAL;
	datasize = min(maxsize, (size_t)MEM_USED(ring));
	if (datasize > ring->size)
		return -ENOMEM;

	if ((ring->head > ring->tail) /*|| ring->full*/) {
		memcpy(data, ring->buf + ring->tail, datasize);
		ring->tail += (uint16_t)datasize;
	} else {
		if (ring->head < ring->tail) {
			size_t remaining = ring->size -ring->tail;
			if (datasize < remaining) {
				memcpy(data, ring->buf + ring->tail, datasize);
				ring->tail += (uint16_t)datasize;
			} else {
				memcpy(data, ring->buf + ring->tail, remaining);
				ring->tail = (uint16_t)(datasize - remaining);
				memcpy(data + remaining, ring->buf, ring->tail);
			}
		}
	}
#if RING_STATISTICS_ENABLED
	ring->statistics.reads += datasize;
#endif
	ring->overflow = 0;
	return (int)datasize;
}

int ringbuf_read(struct ringbuf *ring, char *str) {

	int len = 0;
	uint16_t tail = 0;

	if (ring == NULL || str == NULL)
		return -EINVAL;

	*str = '\0';
	if (ring->head == ring->tail){
		return 0;
	}
	tail = ring->tail;
	if (ring->head > ring->tail) {
		if (ring->max_read_len < 2)
			return -ENOMEM;
		strncpy(str, (char *)(ring->buf + ring->tail), ring->max_read_len);
		str[ring->max_read_len] = '\0';
		for (int i = ring->tail; i < ring->head; i++) {
			if (*(ring->buf + i) == 0) {
				ring->tail = (uint16_t)i + 1;
				break;
			}
		}
		if (ring->tail == tail) {
			// no \0 found
			return 0;
		}
	} else {
		int continu = 1;
		int tail = 0;
			strncpy(str, (char *)(ring->buf + ring->tail),(size_t)min(ring->size - ring->tail, ring->max_read_len));
		str[ring->max_read_len] = '\0';
		len = (int)strlen(str);
		for (int i = ring->tail; i < ring->size; i++) {
			if (*(ring->buf + i) == 0) {
				tail = i + 1;
				continu = 0;
				break;
			}
		}
		if (continu) {
			strncpy(str + ring->size - ring->tail, (char *)ring->buf, (size_t)(ring->max_read_len - ring->size + ring->tail));
			str[ring->max_read_len] = '\0';
			continu = 1;
			for (int i = 0; i < ring->head; i++) {
				if (*(ring->buf + i) == 0) {
					tail = i + 1;
					continu = 0;
					break;
				}
			}
			if (continu)
				return -EINVAL;
		}
		ring->tail = (uint16_t)tail;
	}
	len = (int)strlen(str);
	ring->tail = ring->tail % ring->size;
#if RING_STATISTICS_ENABLED
	ring->statistics.reads += (uint32_t)len + 1;
#endif
	return len;
}

int ringbuf_write(struct ringbuf *ring, char *str) {

	size_t len = 0;
	len = (size_t)strlen(str);
	if (len > 0)
		return ringbuf_push(ring, (uint8_t *)str, len + 1);
	else
		return -EINVAL;
}

#if RING_STATISTICS_ENABLED
int ringbuf_statistics(struct ringbuf *ring) {
	if (ring == NULL)
		return -EINVAL;
	puts("Ring Buffer Statistics:");
	printf(" Bytes written: %ld\n", ring->statistics.writes);
	printf("    Bytes read: %ld\n", ring->statistics.reads);
	printf("# of overflows: %ld\n", ring->statistics.overflows);
	return 0;
}
int ringbuf_stat_writes(struct ringbuf *ring) {
	if (ring == NULL)
		return -EINVAL;
	return (int)ring->statistics.writes;
}
int ringbuf_stat_reads(struct ringbuf *ring) {
	if (ring == NULL)
		return -EINVAL;
	return (int)ring->statistics.reads;
}
int ringbuf_stat_overflow(struct ringbuf *ring) {
	if (ring == NULL)
		return -EINVAL;
	return (int)ring->statistics.overflows;
}
#endif

int ringbuf_callback_register(struct ringbuf *ring, ringbuf_rcv_cb_t cb_func, void *cb_data) {
	if (ring == NULL)
		return -EINVAL;
	ring->rcv_callback = cb_func;
	ring->rcv_cb_data = cb_data;
	return 0;
}
