//
//array.c
//
//Memory management functions for handling arbitry arrays of data
//
//
//-UserX 2001/11/04

/**
Functions for handling \Ref{ArrayHandle} based arrays. 
@author UserX
@name array
*/
//@{

#include <string.h>
#include <memory.h>
#include <assert.h>
#include "base/array.h"
#include "base/mem.h"

/**
Creates a new array.
@param eSize The size of each element in the array.
@param eStep How many elements to add to the array each time it needs expanding. A value of zero will use the default step size. This parameter can be used to tune memory usage. 
@param blank A pointer to a blank element that is copied into newly created spaces. A copy of the blank element will be created. If this is NULL it will create a blank element filled with zeroes.
@param createfunction A pointer to a function that initializes an array element.
@param deletefunction A pointer to a function that deletes an array element.
@return Pointer to the new ArrayHandle.
*/
ArrayHandle *arrayMake(int eSize, int eStep, void *blank, void (*createfunction)(void *), void (*deletefunction)(void *)) {
	ArrayHandle *array = NULL;
	assert(eSize != 0);
	if(eStep <= 0) {
		eStep = ARRAY_DEFAULTSTEP;
	}
	array = memAlloc(sizeof(ArrayHandle), "Array", NULL);
	array->blankElement = memAlloc(eSize, "ArrayElement", NULL);
	array->data = memAlloc(eStep * eSize, "ArrayData", NULL);
	array->maxElements = eStep;
	array->elementStep = eStep;
	array->elementSize = eSize;
	array->size = 0;
	//array->data = NULL;
	array->createFunction = createfunction;
	array->deleteFunction = deletefunction;

	if(array->blankElement == NULL) {
		memset(array->blankElement, 
				0, array->elementSize);
	} else {
		memcpy(array->blankElement, 
				blank, array->elementSize);
	}
	return array;
}

/**
Internal - Releases the memory allocated to the array but does not call the delete function on any of the used elements.
@param array Pointer to the array to be released.
*/
void arrayNuke(ArrayHandle *array) {
	memFree(array->blankElement);//xifree(array->blankElement);
	memFree(array->data);//xifree(array->data);
	memFree(array);//xifree(array);
}

/**
Deletes all elements on an array and releases the memory allocated it.
@param array Pointer to the array to be released.
*/
void arrayFree(ArrayHandle *array) {
	int i;
	if(array == NULL) {
		return;
	}
	if(array->deleteFunction != NULL) {
		for(i = 0; i < array->size; i++) {
			(array->deleteFunction)((void *) ((char *)array->data + array->elementSize * i));
		}
	}
	arrayNuke(array);
}

/**
Internal - Resizes the amount of available space on an array. Does not sanity check the new size against the used size.
@param array Pointer to the array to be resized.
@param newmax The new number of elements the array can hold.
*/
void arrayResize(ArrayHandle *array, int newmax) {
	if(newmax != 0) {
		array->data = memResize(array->data, newmax * array->elementSize); //xirealloc(array->data, newmax * array->elementSize);
	} else {
		memFree(array->data);
		array->data = NULL;
	}
	array->maxElements = newmax;
}

//returns array->data
/**
Internal - Adds elements to the end of an array. Does not call createFunction or deleteFunction. Deletes elements if a negative quantity is specified.
@param array The pointer to the array.
@param num The number of elements to add.
*/
void *arrayAddElements(ArrayHandle *array, int num) {
	int ns = array->size + num;
	int nm = array->maxElements;
	if(num == 0) {
		return array->data;
	}
	if (num > 0) {
		while (ns > nm) {
			nm += array->elementStep;
		}
		if(nm != array->maxElements) {
			arrayResize(array, nm);
		}
		for(;array->size < ns;array->size++){ //blank new elements
			memcpy((char *)array->data + array->elementSize * array->size, 
					array->blankElement, array->elementSize);
		}
	} else {
		while(ns < nm - array->elementStep * 2){
			nm -= array->elementStep;
		}
		if(nm != array->maxElements) {
			arrayResize(array, nm);
		}
		array->size = ns;
	}
	return array->data;
}

/**
Internal - Deletes elements from the end of an array. Does not call createFunction or deleteFunction. 
@param array The pointer to the array.
@param num The number of elements to remove.
*/
void *arrayDeleteElements(ArrayHandle *array, int num) {
	return arrayAddElements(array, -num);
}

/**
Insert blank elements into an array. Calls createFunction on each of the new elements. 
@param array The pointer to the array. 
@param first The element at which to start the insert.  
@param count The number of elements to insert. 
*/
void *arrayInsert(ArrayHandle *array, int first, int count) {
	int i;
	if(array == NULL) {
		return NULL;
	}
	if(first < 0) {
		count += first;
		first = 0;
	}
	if(first > array->size) { 
		count += first - array->size;
		first = array->size;
	}
	if(count <= 0) {
		return array->data;
	}

	arrayAddElements(array, count);
	memmove((char *)array->data + array->elementSize * (first + count),
			(char *)array->data + array->elementSize * (first),
			(array->size - first - count) * array->elementSize);

	for(i = first; i < first + count; i++){ //blank new elements
		memcpy((char *)array->data + array->elementSize * i, 
				array->blankElement, array->elementSize);
		if (array->createFunction != NULL) {
			(array->createFunction)((void *) ((char *)array->data + array->elementSize * i));
		}
	}
	return array->data;
}

/**
Deletes elements from an array. 
@param array The pointer to the array. 
@param first The first element to delete.  
@param count The number of elements to delete. 
*/
void *arrayDelete(ArrayHandle *array, int first, int count) {
	int i;
	if(array == NULL) {
		return NULL;
	}
	if(first < 0) {
		count += first;
		first = 0;
	}
	if(first >= array->size) { 
		return array->data;
	}
	if(first + count > array->size) {
		count = array->size - first;
	}
	if(count <= 0) {
		return array->data;
	}
	if (array->deleteFunction != NULL) {
		for(i = first; i < first + count; i++) {
			(array->deleteFunction)((void *) ((char *)array->data + array->elementSize * i));
		}
	}
	memmove((char *)array->data + array->elementSize * (first),
			(char *)array->data + array->elementSize * (first + count),
			(array->size - first - count) * array->elementSize);

	arrayDeleteElements(array, count);

	return array->data;
}

//@}
