/*
 * This file is part of LibParserUtils.
 * Licensed under the MIT License,
 *                http://www.opensource.org/licenses/mit-license.php
 * Copyright 2008 John-Mark Bell <jmb@netsurf-browser.org>
 */

#include <string.h>

#include <parserutils/utils/buffer.h>

#define DEFAULT_SIZE (4096)

/**
 * Create a memory buffer
 *
 * \param alloc   Memory (de)allocation function
 * \param pw      Pointer to client-specific private data
 * \param buffer  Pointer to location to receive memory buffer
 * \return PARSERUTILS_OK on success,
 *         PARSERUTILS_BADPARM on bad parameters,
 *         PARSERUTILS_NOMEM on memory exhausion
 */
parserutils_error parserutils_buffer_create(parserutils_alloc alloc, void *pw,
		parserutils_buffer **buffer)
{
	parserutils_buffer *b;

	if (alloc == NULL || buffer == NULL)
		return PARSERUTILS_BADPARM;

	b = alloc(NULL, sizeof(parserutils_buffer), pw);
	if (b == NULL)
		return PARSERUTILS_NOMEM;

	b->data = alloc(NULL, DEFAULT_SIZE, pw);
	if (b->data == NULL) {
		alloc(b, 0, pw);
		return PARSERUTILS_NOMEM;
	}

	b->length = 0;
	b->allocated = DEFAULT_SIZE;

	b->alloc = alloc;
	b->pw = pw;

	*buffer = b;

	return PARSERUTILS_OK;
}

/**
 * Destroy a memory buffer
 *
 * \param buffer  The buffer to destroy
 * \return PARSERUTILS_OK on success, appropriate error otherwise
 */
parserutils_error parserutils_buffer_destroy(parserutils_buffer *buffer)
{
	if (buffer == NULL)
		return PARSERUTILS_BADPARM;

	buffer->alloc(buffer->data, 0, buffer->pw);
	buffer->alloc(buffer, 0, buffer->pw);

	return PARSERUTILS_OK;
}

/**
 * Append data to a memory buffer
 *
 * \param buffer  The buffer to append to
 * \param data    The data to append
 * \param len     The length, in bytes, of the data to append
 * \return PARSERUTILS_OK on success, appropriate error otherwise.
 */
parserutils_error parserutils_buffer_append(parserutils_buffer *buffer, 
		const uint8_t *data, size_t len)
{
	while (len >= buffer->allocated - buffer->length) {
		parserutils_error error = parserutils_buffer_grow(buffer);
		if (error != PARSERUTILS_OK)
			return error;
	}

	memcpy(buffer->data + buffer->length, data, len);

	buffer->length += len;

	return PARSERUTILS_OK;
}

/**
 * Insert data into a memory buffer
 *
 * \param buffer  The buffer to insert into
 * \param offset  The offset into the buffer to insert at
 * \param data    The data to insert
 * \param len     The length, in bytes, of the data to insert
 * \return PARSERUTILS_OK on success, appropriate error otherwise
 */
parserutils_error parserutils_buffer_insert(parserutils_buffer *buffer, 
		size_t offset, const uint8_t *data, size_t len)
{
	if (offset > buffer->length)
		return PARSERUTILS_BADPARM;

	if (offset == buffer->length)
		return parserutils_buffer_append(buffer, data, len);

	while (len >= buffer->allocated - buffer->length) {
		parserutils_error error = parserutils_buffer_grow(buffer);
		if (error != PARSERUTILS_OK)
			return error;
	}

	memmove(buffer->data + offset + len,
			buffer->data + offset, buffer->length - offset);

	memcpy(buffer->data + offset, data, len);

	buffer->length += len;

	return PARSERUTILS_OK;
}

/**
 * Discard a section of a memory buffer
 *
 * \param buffer  The buffer to discard data from
 * \param offset  The offset into the buffer of the start of the section
 * \param len     The number of bytes to discard
 * \return PARSERUTILS_OK on success, appropriate error otherwise.
 */
parserutils_error parserutils_buffer_discard(parserutils_buffer *buffer, 
		size_t offset, size_t len)
{
	if (offset >= buffer->length || offset + len > buffer->length)
		return PARSERUTILS_BADPARM;

	memmove(buffer->data + offset, buffer->data + offset + len, 
			buffer->length - len);

	buffer->length -= len;

	return PARSERUTILS_OK;
}

/**
 * Extend the amount of space allocated for a memory buffer
 *
 * \param buffer  The buffer to extend
 * \return PARSERUTILS_OK on success, appropriate error otherwise.
 */
parserutils_error parserutils_buffer_grow(parserutils_buffer *buffer)
{
	uint8_t *temp = buffer->alloc(buffer->data, 
			buffer->allocated * 2, buffer->pw);
	if (temp == NULL)
		return PARSERUTILS_NOMEM;

	buffer->data = temp;
	buffer->allocated *= 2;

	return PARSERUTILS_OK;
}

parserutils_error parserutils_buffer_randomise(parserutils_buffer *buffer)
{
#ifndef NDEBUG
	uint8_t *temp;
#endif

	if (buffer == NULL)
		return PARSERUTILS_BADPARM;

#ifndef NDEBUG
	temp = buffer->alloc(NULL, buffer->allocated, buffer->pw);
	if (temp == NULL)
		return PARSERUTILS_NOMEM;

	memcpy(temp, buffer->data, buffer->length);

	memset(buffer->data, 0xff, buffer->length);

	/* Leak the buffer's current data, so we don't reuse it */
	/* buffer->alloc(buffer->data, 0, buffer->pw); */

	buffer->data = temp;
#endif


	return PARSERUTILS_OK;
}

