/********************************************
libmawk (C) 2009-2014, Tibor 'Igor2' Palinkas;

This is a source file for libmawk, an implementation of
the AWK programming language, fork of mawk.

Libmawk is distributed without warranty under the terms of
the GNU General Public License, version 2, 1991.
********************************************/

#include <string.h>
#include "mawk.h"
#include "zfifo.h"
#include "zmalloc.h"

mawk_zfifo_t *mawk_zfifo_alloc(mawk_state_t *MAWK, mawk_zfifo_t *sfifo, int max_size)
{
	if (sfifo == NULL) {
		sfifo = mawk_zmalloc(MAWK, sizeof(mawk_zfifo_t));
		sfifo->zalloced = 1;
	}
	else
		sfifo->zalloced = 0;

	sfifo->size = 0;
	sfifo->max_size = max_size;
	sfifo->stage_used = 0;
	sfifo->head = NULL;
	sfifo->tail = NULL;
	return sfifo;
}

void mawk_zfifo_free(mawk_state_t *MAWK, mawk_zfifo_t *fifo)
{
	mawk_zfifo_block_t *b, *next;
	for(b = fifo->head; b != NULL; b = next) {
		next = b->next;
		mawk_zfree(MAWK, b, sizeof(mawk_zfifo_block_t));
	}
	if (fifo->zalloced)
		mawk_zfree(MAWK, fifo, sizeof(mawk_zfifo_t));
}

int mawk_zfifo_write(mawk_state_t *MAWK, mawk_zfifo_t *fifo, const char *data, int size)
{
	mawk_zfifo_block_t *b;
	
	/* don't write more than we should */
	if ((fifo->max_size > 0) && (fifo->size + size > fifo->max_size))
		return 0;

	/* small write, append to stage buffer */
	if (fifo->stage_used + size < sizeof(fifo->stage_buf)) {
		memcpy(fifo->stage_buf + fifo->stage_used, data, size);
		fifo->stage_used += size;
		fifo->size += size;
		return size;
	}

	/* can't stage, alloc a new block at the end of the list and append
	   the stage _and_ new data there */
	b = mawk_zmalloc(MAWK, sizeof(mawk_zfifo_block_t) + size + fifo->stage_used);
	b->size = size + fifo->stage_used;
	memcpy(b->buf, fifo->stage_buf, fifo->stage_used);
	memcpy(b->buf + fifo->stage_used, data, size);
	fifo->stage_used = 0;
	fifo->size += size;
	b->next = NULL;
	b->readp = 0;

	/* append the block to the list */
	if (fifo->tail == NULL) {
		fifo->head = b;
		fifo->tail = b;
	}
	else {
		fifo->tail->next = b;
		fifo->tail = b;
	}

	return size;
}

int mawk_zfifo_read(mawk_state_t *MAWK, mawk_zfifo_t *fifo, char *data, int size)
{
	mawk_zfifo_block_t *b, *next;
	char *end = data;
	int left = size;

#define append(from, asize) \
	do { \
		int append_asize = asize; \
		memcpy(end, from, append_asize); \
		end += append_asize; \
		left -= append_asize; \
		fifo->size -= append_asize; \
	} while(0)

	/* start appending blocks from head */
	for(b = fifo->head; b != NULL; b = next) {
		next = b->next;
		if ((b->size - b->readp) < left) {
			/* whole block append */
			append(b->buf + b->readp, b->size - b->readp);
			mawk_zfree(MAWK, b, sizeof(mawk_zfifo_block_t));
			fifo->head = next;
		}
		else {
			int old_left;
			/* partial append */
			old_left = left;
			append(b->buf+b->readp, left);
			b->readp += old_left;
			break; /* don't even look at further blocks, we are full! */
		}
	}

	/* removed all blocks */
	if (fifo->head == NULL)
		fifo->tail = NULL;

	/* if there's nothing else left, read from the staging buff */
	if (fifo->stage_used > 0) {
		int amount, stage_left;

		/* can we copy the whole buff? */
		if (fifo->stage_used < left) {
			amount = fifo->stage_used;
			stage_left = 0;
		}
		else {
			/* the rare case when the end of a read cuts the staging buff in half */
			amount = left;
			stage_left = fifo->stage_used - amount;
		}
		append(fifo->stage_buf, amount);
		if (stage_left > 0)
			memmove(fifo->stage_buf, fifo->stage_buf + amount, stage_left);
		fifo->stage_used = stage_left;
	}

	return end - data;
}


