//
//datablck.c
//
//Functions for the management of data blocks.
//
//
//
//
//-UserX 2001/11/06

/**
Functions for handling \Ref{DataBlock}s. <P>

@author UserX
@name dblock
*/
//@{

#include <string.h>
#include "base/dblock.h"
#include "base/mem.h"
#include "base/str.h"
#include "base/logger.h"

#define DBLOCK_STEP	1024
#define DBLOCK_STEP_1 (DBLOCK_STEP - 1)

/**	
Creates a datablock that can hold at least <B>length</B> bytes 
and the number of used bytes are preset to <B>initlength</B>. <P>
If <B>length} is less than <B>initlength</B> length will be 
increased to <B>initlength</B>.<P>
data is NOT cleared upon allocation
@param	length	The minimum amount of data the dblock should be able to hold.
@param	initlength	How much data should be marked as being already used.
@param	subgroup	The subgroup descriptor handed to the memory manager.
@return	Returns a pointer to the created DataBlock
*/
DataBlock *dblockMake(int length, int initlength, char *subgroup) {
	DataBlock *db;
	LOGDEBUGSPAM(stringJoinMany(
			"dblockMake: maxlength", 
			intToHexString(length), 
			", startlength", 
			intToHexString(initlength),
		NULL));
	if(length < initlength) {
		length = initlength;
	}

	//round up space used to DBLOCK_STEP size
	length = (length + sizeof(DataBlock) + DBLOCK_STEP_1); 
	length -= length % DBLOCK_STEP + sizeof(DataBlock);

	db = memAlloc(length + sizeof(DataBlock), "DBlock", subgroup);
	db->maxSize = length;
	db->size = initlength;

	LOGDEBUGSPAM(stringJoinMany(
			"- db:", 
			ptrToString(db), 
			", maxSize:", 
			intToHexString(db->maxSize),
		NULL));

	return db;
}

/**	Resize a data block so that it can hold at least <B>newmaxlength</B> bytes. 
 *	If NULL is passed as a data block it will call dblockMake. 
 *	If newmaxlength is shorter than the block's used length the used
 *	length will be shortened to newmaxlength.
@param	db	The DataBlock to resize the data buffer for.
@param	newmaxlength	The new maxsize for the DataBlock.
@return The new pointer to db.
 */
DataBlock *dblockResize(DataBlock *db, int newmaxlength) {
	if(db == NULL) {
		return dblockMake(newmaxlength, 0, NULL);
	}

	LOGDEBUGSPAM(stringJoinMany(
			"dblockResize: db:", 
			ptrToString(db), 
			", newmaxlength:", 
			intToHexString(db->maxSize),
		NULL));

	if(newmaxlength < 0) {
		newmaxlength = 0;
	}

	if(db->size > newmaxlength) {
		db->size = newmaxlength;
	}

	//round up space used to DBLOCK_STEP size
	newmaxlength = (newmaxlength + sizeof(DataBlock) + DBLOCK_STEP_1);
	newmaxlength -= newmaxlength % DBLOCK_STEP + sizeof(DataBlock);

	if(newmaxlength == db->maxSize) {
		return db;
	}
	//todo: tune this so it less aggressive about shrinking dataBlocks
	

	db = memResize(db, newmaxlength + sizeof(DataBlock));
	
	db->maxSize = newmaxlength;

	LOGDEBUGSPAM(stringJoinMany(
			"- db:", 
			ptrToString(db), 
			", maxSize:", 
			intToHexString(db->maxSize),
		NULL));

	return db;
}

/**
Release a DataBlock.
@param	db	The DataBlock to be released.
*/
void dblockFree(DataBlock *db) {
	LOGDEBUGSPAM(stringJoinMany(
			"dblockFree: db:", 
			ptrToString(db), 
		NULL));

	if(db != NULL) {
		memFree(db);
	}
}

/**	
Creates a new empty DataBlock.
@param	subgroup	The subgroup descriptor handed to the memory manager.
@return	Returns a pointer to the created DataBlock
 */
DataBlock *dblockEmpty(char *subgroup) {
	return dblockMake(0, 0, subgroup);
}

/**
Check to see if a DataBlock can hold at least <B>newlength</B> bytes. If not expand it.
@param	db	The DataBlock to expand.
@param	newlength	The minimum size the DataBlock must be able to hold.
@return	The new (if resized) or old (if unchanged) pointer to the DataBlock
*/
DataBlock *dblockExpand(DataBlock *db, int newlength) {
	if(newlength < db->maxSize) {
		return db;
	} else {
		return dblockResize(db, newlength);
	}
}

/**
Copies an existing DataBlock with the same maxsize, size and data.
@param	db	The DataBlock to copy
@return	The pointer to the created copy.
*/
DataBlock *dblockCopy(DataBlock *db) {
	DataBlock *dbc;
	if(db == NULL) {
		return dblockEmpty(NULL);
	}
	dbc = dblockMake(db->maxSize, db->size, memGetSubgroup(db));
	memcpy(dbc->data, db->data, db->size);
	return dbc;
}


/**
Makes a new DataBlock by copying bytes from a buffer.
If <B>srcbuffer</B> is NULL then the DataBlock will be initialized with <B>length</B> 0 bytes.
@param srcbuffer The buffer to copy from.
@param start The offset into the buffer to start copying from.
@param length The number of bytes to copy.
@param subgroup The subgroup descriptor handed to the memory manager.
@return The new DataBlock containing a copy of the buffer.
*/
DataBlock *dblockCopyBuffer(char *srcbuffer, int start, int length, char *subgroup) {
	DataBlock *dbc;
	
	dbc = dblockMake(length, length, subgroup);
	if(srcbuffer != NULL) {
		memcpy(dbc->data, srcbuffer + start, length);
	} else {
		memset(dbc->data, 0, length);
	}

	return dbc;
}

/**
Makes a new DataBlock by copying part of an existing one.
todo: range checking
@param db The DataBlock to copy from.
@param start The offset into the buffer to start copying from.
@param length The number of bytes to copy.
@return The new DataBlock containing a copy of the subsection of the source DataBlock.
*/
DataBlock *dblockCopyPart(DataBlock *db, int start, int length) {
	if(db == NULL) {
		//return dblockEmpty(NULL);
		return dblockCopyBuffer(NULL, start, length, NULL);
	}
	//todo: range checking
	return dblockCopyBuffer(db->data, start, length, memGetSubgroup(db));
}


/**
Sets the used length of a DataBlock to 0 and calls dblockResize.
@param db The DataBlock to clear.
@return The new pointer to db.
*/
DataBlock *dblockClear(DataBlock *db) {
	if(db == NULL) {
		return dblockEmpty(NULL);
	}
	db->size = 0;
	return dblockResize(db, 0);
}

/**
Trim a DataBlock so it only contains the <B>length</B> bytes of data starting from the offset <B>start</B>. 
Calls dblockResize.
@param db	The DataBlock to crop.
@param start The offset of the data to keep. 
@param length The number of bytes to keep. 
@return The new pointer to <B>db</B>. 
*/
DataBlock *dblockCrop(DataBlock *db, int start, int length) {
	if(db == NULL) {
		return dblockEmpty(NULL);
	}

	//todo: range checking
	memmove(db->data, db->data + start, length);

	db->size = length;

	return dblockResize(db, db->size);
}

/**
Insert a buffer of bytes into any location in a DataBlock.
@param db The DataBlock to insert the buffer into.
@param dststart The offset of where to start the insertion.
@param srcbuffer The buffer to copy from.
@param srcstart The offset into the buffer to start copying from.
@param length The number of bytes to copy/insert.
@return The new pointer to <B>db</B>. 
*/
DataBlock *dblockInsertBuffer(DataBlock *db, int dststart, char *srcbuffer, int srcstart, int length) {
	if (db == NULL) {
		db = dblockMake(length, 0, NULL);
	}
	if (srcbuffer == NULL) {
		return db;
	}
	//todo: range checking

	if(srcstart < 0) {
		length += srcstart;
		srcstart = 0;
		if(length <= 0) {
			return db;
		}
	}
	if(length == 0) {
		return db;
	}
	if(length < 0) { //todo: currently seperately for easy breakpointing
		return db;
	}


	db = dblockExpand(db, db->size + length);

	memmove(db->data + dststart + length, db->data + dststart, db->size - dststart);
	memcpy(db->data + dststart, srcbuffer + srcstart, length);

	db->size += length;

	return db;
}

/**
Inserts part of DataBlock into any location of another DataBlock.
@param db The DataBlock to insert the buffer into.
@param dststart The offset of where to start the insertion.
@param dbs The DataBlock to copy from.
@param srcstart The offset into the <B>dbs</B> to start copying from.
@param length The number of bytes to copy/insert.
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockInsertPart(DataBlock *db, int dststart, DataBlock *dbs, int srcstart, int length) {
	if (dbs == NULL) {
		if (db == NULL) {
			return dblockMake(length, 0, NULL);
		} else {
			return db;
		}
	}
	if (db == NULL) {
		return dblockCopyPart(dbs, srcstart, length);
	}
	//todo: range checking
	if(srcstart < 0) {
		length += srcstart;
		srcstart = 0;
		if(length <= 0) {
			return db;
		}
	}
	if(srcstart >= dbs->size) {
		return db;
	}
	if(srcstart + length > dbs->size) {
		length = dbs->size - srcstart;
	}
	if(length == 0) {
		return db;
	}
	if(length < 0) { //todo: currently seperately for easy breakpointing
		return db;
	}

	return dblockInsertBuffer(db, dststart, dbs->data, srcstart, length);
}

/**
Inserts a complete DataBlock into any location of another DataBlock.
@param db The DataBlock to insert the buffer into.
@param dststart The offset of where to start the insertion.
@param dbs The DataBlock to copy from.
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockInsertBlock(DataBlock *db, int dststart, DataBlock *dbs) {
	return dblockInsertPart(db, dststart, dbs, 0, dbs->size);
}

/**
Appends a complete copy of a DataBlock onto the end of another DataBlock.
@param db The DataBlock to append to.
@param dbs The DataBlock to copy from.
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockAppendBlock(DataBlock *db, DataBlock *dbs) {
	return dblockInsertBlock(db, db->size, dbs);
}


/**
Prepends a complete copy of a DataBlock onto the front of another DataBlock.
@param dbs The DataBlock to copy from.
@param db The DataBlock to prepend to.
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockAppendBlock2(DataBlock *dbs, DataBlock *db) {
	return dblockInsertBlock(db, 0, dbs);
}

/**
Appends a buffer of bytes to an existing DataBlock.
@param db The DataBlock to append to.
@param buffer The buffer to be copied.
@param start The offset into the buffer to start
@param length The number of bytes to be copied.
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockAppendBuffer(DataBlock *db, char *buffer, int start, int length) {
	return dblockInsertBuffer(db, db->size, buffer, start, length);
}

/**
Appends a byte to an existing DataBlock. 
@param db The DataBlock to append to.
@param byt The byte to be copied.
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockAppendByte(DataBlock *db, char byt) {
	db = dblockExpand(db, db->size + 1);
	db->data[db->size] = byt;
	db->size++;
	return db;
}

/**
Deletes a range of bytes from inside a DataBlock.
Calls dblockResize.
@param db The DataBlock to delete from.
@param start The offset to start deleting from.
@param length The number of bytes to be copied.
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockDelete(DataBlock *db, int start, int length) {
	db = dblockDeleteNoShrink(db, start, length);
	return dblockResize(db, db->size);
}

/**
Deletes a range of bytes from inside a DataBlock.
Does NOT call dblockResize.
@param db The DataBlock to delete from.
@param start The offset to start deleting from.
@param length The number of bytes to be copied.
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockDeleteNoShrink(DataBlock *db, int start, int length) {
	if (db == NULL) {
		return dblockMake(0, 0, NULL);
	}

	//todo: range checking

	if(start > db->size) {
		return db;
	}
	if(start < 0) {
		length += start;
		start = 0;
	}
	if(length > db->size - start) {
		length = db->size - start;
	}
	if(length <= 0) {
		return db;
	}
	
	memmove(db->data + start, db->data + start + length, db->size - start - length);

	db->size -= length;

	if(db->size < 0) {
 		return db; //todo:
	}


	return db;
}

/**
Moves <B>length</B> bytes from the start of one DataBlock to the end of another DataBlock.
Does NOT call dblockResize on <B>dbs</B>.
@param db The DataBlock to append the data to.
@param dbs The DataBlock to remove the data from.
@param length The number of bytes to be moved.
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockMove(DataBlock *db, DataBlock *dbs, int length) {
	if(db == NULL) {
		db = dblockMake(0, 0, NULL);
	}
	if(dbs == NULL) {
		return db;
	}
	if(length <= 0) {
		return db;
	}
	if(length > dbs->size) {
		length = dbs->size;
	}
	db = dblockInsertBuffer(db, db->size, dbs->data, 0, length);
	memmove(dbs->data, dbs->data + length, dbs->size - length);
	dbs->size -= length;
	return db;
}

/**
Moves all data from one DataBlock to the end of another DataBlock. 
Does NOT call dblockResize on <B>dbs</B>.
@param db The DataBlock to append the data to.
@param dbs The DataBlock to remove the data from.
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockMoveAll(DataBlock *db, DataBlock *dbs) {
	if(dbs == NULL) {
		if(db == NULL) {
			return dblockMake(0, 0, NULL);
		} else {
			return db;
		}
	} else {
		return dblockMove(db, dbs, dbs->size);
	}
}


/**
Copies a string to a new DataBlock the null terminator is discarded from the string. 
Releases the string. 
<P>
Note: The DataBlock string will be zero terminated if <CODE>db->size++</CODE> is done 
immediately after calling this.
@param srcbuffer The string to make a DataBlock from (is released).
@param subgroup The subgroup descriptor handed to the memory manager.
@return The pointer to the new DataBlock
*/
DataBlock *stringToDBlock(char *srcbuffer, char *subgroup) {
	DataBlock *db;
	if (srcbuffer == NULL) {
		return dblockEmpty(subgroup);
	}
	db = dblockMake(strlen(srcbuffer) + 1, strlen(srcbuffer), subgroup);
	
	//memcpy(db->data, srcbuffer, strlen(srcbuffer));
	strcpy(db->data, srcbuffer);

	stringFree(srcbuffer);

	return db;
}

/**
Appends a string to an existing DataBlock the null terminator is discarded from the string. 
<P>
Note: The DataBlock string will be zero terminated if <CODE>db->size++</CODE> is done 
immediately after calling this.
@param db The DataBlock to append the string to.
@param srcbuffer The string to append to <B>db</B>.
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockAppendString(DataBlock *db, char *srcbuffer) {
	if(db == NULL) {
		db = dblockMake(strlen(srcbuffer), 0, NULL);
	}
	if(srcbuffer == NULL) {
		return db;
	}
	db = dblockExpand(db, db->size + strlen(srcbuffer) + 1);
	strcpy(db->data + db->size, srcbuffer);
	db->size += strlen(srcbuffer);
	return db;
}

/**
Gets a zero terminated string from a DataBlock. 
Deletes the used bytes. 
<P>
Returns NULL if no zero terminator is found. 
@param db The DataBlock to read the string from.
@return The pointer to the resulting string or NULL if no zero terminated string was found.
*/
char *dblockGetString(DataBlock *db) {
	char *buffer = NULL;
	if(db == NULL) {
		return NULL;
	}
	if(memchr(db->data, 0, db->size) == NULL) {
		return NULL;
	}
	buffer = stringCopy(db->data);
	db = dblockDeleteNoShrink(db, 0, strlen(buffer) + 1);
	return buffer;
}

/**
Returns the contents of a DataBlock as a string discarding any zero bytes found. 
<B>db</B> is left untouched. 
<P>
Returns a blank string if <B>db</B> is NULL. 
@param db The DataBlock to read the string from.
@return The pointer to the resulting string or NULL if no zero terminated string was found.
*/
char *dblockToStringKeep(DataBlock *db) {
	char *s;
	int i;
	int j;
	if(db == NULL) {
		return stringBlank();
	}
	s = stringMake(db->size);
	for(i = 0, j = 0; i < db->size; i++) {
		if(db->data[i] != 0) {
			s[j] = db->data[i];
			j++;
		}
	}
	s[j] = 0;
	return s;
}


/**
Returns the contents of a DataBlock as a string discarding any zero bytes found. 
<B>db</B> is cleared afterwards. 
<P>
Returns a blank string if <B>db</B> is NULL. 
@param db The DataBlock to read the string from.
@return The pointer to the resulting string or NULL if no zero terminated string was found.
*/
char *dblockToString(DataBlock *db) {
	char *s = dblockToStringKeep(db);
	if(db != NULL) {
		db->size = 0;
	}
	return s;
}

/**
Reads a signed little endian integer from the start of a DataBlock deleting the used bytes afterwards.
@param db The data block to read the value from
@param bytes the bytes length of the integer
@return The integer read.
*/
int dblockReadInt(DataBlock *db, int bytes) {
	int v = 0;
	uint8 c = 0;
	int i;
	if(db == NULL) {
		return 0;
	}
	if(db->size < bytes) {
		bytes = db->size;
	}
	if(bytes <= 0) {
		return 0;
	}

	for(i = 0; i < bytes; i++) {
		v += ((unsigned int) (c = db->data[i])) << (3 << i);
	}
	if(bytes < sizeof(int)) {
		//extend sign if a negative
		if((c & 0x80) != 0) {
			for(i = bytes; i < sizeof(int); i++) {
				v += ((unsigned int) 0xff) << (3 << i);
			}
		}
	}

//todo: assert: make sure db hasn't moved
	db = dblockDeleteNoShrink(db, 0, bytes);
	return v;
}

/**
Writes a signed little endian integer to the end of a DataBlock. 
@param db The DataBlock to write.
@param value The integer to write.
@param bytes The bytes length of the integer
@return The new pointer to <B>db</B>.
*/
DataBlock *dblockWriteInt(DataBlock *db, int value, int bytes) {
	if(db == NULL) {
		db = dblockMake(bytes, 0, NULL);
	}

	if(bytes <= 0) {
		return db;
	}

	db = dblockExpand(db, db->size + bytes);
	for(;bytes != 0; bytes--) {
		db->data[db->size] = (uint8) value;
		value >>= 8;
		db->size++;
	}

	return db;
}

//@}
