#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sbuf.h"

// static const char *version = "1.0.0";

#define voidnext(ptr) (*(void **)(ptr))
// #define voiddata(ptr) (((char *)ptr) + sizeof(*ptr))
#define voiddata(ptr) (char *)(((void **)(ptr)) + 1)
#define voiddatum(ptr) (void *)(((void **)(ptr)) + 1)

static void *sbuf_cache;

static void *sbuf_alloc(void)
{
	void *res;
	if(sbuf_cache == NULL)
		return malloc( SBUFBLOCK );
	res = sbuf_cache;
	sbuf_cache = voidnext(sbuf_cache);
	return res;
}
static void sbuf_free(void *ptr)
{
	voidnext(ptr) = sbuf_cache;
	sbuf_cache = ptr;
}

int sbuf_claim(struct sbuf *record)
{
	bzero(record, sizeof(*record));
	return 0;
}


int sbuf_put(struct sbuf *record, const void *message, size_t length)
{
	register void *bucket;
	register void *data;
	register size_t chunk;
	register size_t offset;

	/* If there is free space in the last bucket, skip allocation */
	offset = (record->length + record->offset) % SBUFMTU;
	if(offset)
	{
		bucket = record->tail;
		data = voiddata(bucket) + offset;
		chunk = SBUFMTU - offset;
		if(chunk > length)
			chunk = length;
		/* Copy the block into the buffer */
		memcpy( data, message, chunk);
		message = (void *)(((unsigned char *)message) + chunk);
		record->length += chunk;
		length -= chunk;
	}
	
	while(length)
	{
		/* Grow the sbuf */
		bucket = sbuf_alloc();
		if(bucket == NULL)
			return -1;
		voidnext(bucket) = NULL;
		if(record->tail)
			voidnext(record->tail) = bucket;
		else
			record->head = bucket;

		record->tail = bucket;

		chunk = length > SBUFMTU ? SBUFMTU : length;
		data = voiddata(bucket);
		/* Copy the block into the buffer */
		memcpy( data, message, chunk);
		message = (void *)(((unsigned char *)message) + chunk);
		record->length += chunk;
		length -= chunk;
	}
	return 0;
}

int sbuf_delete(struct sbuf *record, size_t length)
{
	register void *bucket;
	register size_t chunk;
	
	if(length > record->length)
		length = record->length;
	
	while(length)
	{
		chunk = SBUFMTU - record->offset;
		if(chunk > length)
			chunk = length;
		length -= chunk;
		record->offset += chunk;
		record->length -= chunk;
		
		if( record->offset == SBUFMTU)
		{
			bucket = record->head;
			record->offset = 0;
			record->head = voidnext(bucket);
			sbuf_free(bucket);
			if( record->length == 0 )
				record->tail = NULL;
		}
	}
	if(record->length == 0 && record->offset > 0)
	{
		bucket = record->head;
		record->offset = 0;
		record->head = NULL;
		record->tail = NULL;
		sbuf_free(bucket);
	}
	return 0;
}

int sbuf_clear(struct sbuf *record)
{
	register void *bucket;
	
	while(record->head)
	{
		bucket = record->head;
		record->head = voidnext(bucket);
		sbuf_free(bucket);
	}
	record->tail = NULL;
	record->length = 0;
	record->offset = 0;
	return 0;
}

void *sbuf_pagemap(struct sbuf *record, size_t *length)
{
	if(record->head == NULL)
		return NULL;
	if(record->length == 0)
		return NULL;
	
	*length = SBUFMTU - record->offset;
	if(*length > record->length)
		*length = record->length;

	return (void *)(voiddata(record->head) + record->offset);
}

void *sbuf_statemap(struct sbufstate *state, size_t *length)
{
	if(state->chunk == 0)
		return NULL;
	*length = state->chunk;
	return state->data;
}


int sbuf_nextchunk(struct sbufstate *state)
{
	if(state->length == 0)
		return -1;
	state->bucket = voidnext(state->bucket);
	state->chunk = SBUFMTU;
	if(state->chunk > state->length)
		state->chunk = state->length;
	state->length -= state->chunk;
	state->data = (void *)voiddata(state->bucket);

	return 0;
}

int sbuf_firstchunk(struct sbuf *record, struct sbufstate *state)
{
	if(record->length == 0)
		return -1;

	state->length = record->length;
	state->bucket = record->head;
	state->chunk = SBUFMTU - record->offset;
	if(state->chunk > record->length)
		state->chunk = record->length;
	state->length -= state->chunk;
	state->data = (void *)(voiddata(state->bucket) + record->offset);
	return 0;
}

int sbuf_getmsg(struct sbuf *record, char *buf, size_t length)
{
	struct sbufstate state;
	int res;
	char *d;
	char *s;
	char *eos;
	char *eod;
	int nlcount;
	size_t rlength;
	size_t tlength;
	
	nlcount = 0;
	tlength = 0;
	d = buf;
	eod = buf + length;
	bzero(&state, sizeof(state));
	res = sbuf_firstchunk(record, &state);
	while(res == 0)
	{
		s = sbuf_statemap(&state, &rlength);
		eos = s + rlength;
		for(;s < eos; s++)
		{
			if((*s == '\r') || (*s == '\n'))
			{
				nlcount++;
				continue;
			}
			if(*s == '\0')
			{
				/* no nul byte allowed in input */
				continue;
			}
			
			if(nlcount)
				break;
			
			tlength++;
			if(d < eod)
				*d++ = *s;
		}
	
		res = sbuf_nextchunk(&state);
	}

	if(nlcount == 0)
		return 0;

	sbuf_delete(record, nlcount + tlength);
	if(d >= eod)
		d = eod-1;
	*d++ = '\0';
	return d - buf;
}


