#include "WimpLib:PtrArray.h"

#include <string.h>

#include "WimpLib:Exception.h"
#include "WimpLib:mem.h"

static const char Err_InvalidIndex[] = "Array index out of bounds";

struct PtrArray
{
	int    m_elem_count;
	int    m_elem_size;
	int    m_grow_count;
	int    m_alloc_count;
	void** m_pData;
};


/**
 * Creates a new array.
 *
 * @param  grow_count  Number of elements to extending the array by
 *                     when the array is full.
 *
 * @returns            Pointer to a new array.
 */
PtrArray* New_PtrArray(int grow_count) throws(mem)
{
	PtrArray* This = throw_mem_calloc(1, sizeof(*This));

	This->m_elem_size  = sizeof(void*);
	This->m_grow_count = grow_count;

	return This;
}

/**
 * Deletes an array.
 */
void Delete_PtrArray(PtrArray* This)
{
	if (This)
	{
		PtrArray_Clear(This);
		mem_free(This);
	}
}

/**
 * Returns the number of nodes in the array.
 */
int PtrArray_Count(const PtrArray* This)
{
	return This->m_elem_count;
}

/**
 * Inserts the element in the array.
 *
 * @param  index  Index at which the element is to be inserted.
 *                Use -1 to insert after all existing elements.
 * @param  pData  Pointer to the data to insert in the array.
 */
void PtrArray_Insert(PtrArray* This, int index, void* pobj) throws(mem index)
{
	void** ptoobj;

	if ((index < -1) || (index > This->m_elem_count))
		throw_runtime(Err_InvalidIndex);

	if (index == -1) index = This->m_elem_count;

	if (This->m_elem_count >= This->m_alloc_count)
	{
		int count = This->m_alloc_count + This->m_grow_count;
		void* pData = throw_mem_alloc(count * This->m_elem_size);

		if (This->m_pData)
		{
			memcpy(pData, This->m_pData, This->m_alloc_count * This->m_elem_size);
			mem_free(This->m_pData);
		}
		This->m_pData = pData;
		This->m_alloc_count = count;
	}

	ptoobj = This->m_pData + index;
	memmove(ptoobj + 1, ptoobj, (This->m_elem_count - index) * This->m_elem_size);
	*ptoobj = pobj;
	This->m_elem_count++;
}

void PtrArray_Set(PtrArray* This, int index, void* pobj) throws(index)
{
	if ((index < 0) || (index >= This->m_elem_count))
		throw_runtime(Err_InvalidIndex);

	This->m_pData[index] = pobj;
}

/**
 * Removes an element from the array.
 *
 * @param  index  Index of the element to remove.
 */
void PtrArray_Remove(PtrArray* This, int index) throws(index)
{
	void** ptoobj;

	if (index == -1)
		return;

	if ((index < 0) || (index >= This->m_elem_count))
		throw_runtime(Err_InvalidIndex);

	index++;

	if (index < This->m_elem_count)
	{
		ptoobj = This->m_pData + index;
		memmove(ptoobj - 1, ptoobj, (This->m_elem_count - index) * This->m_elem_size);
	}

	This->m_elem_count--;

	if (!This->m_elem_count)
	{
		mem_free(This->m_pData);
		This->m_alloc_count = 0;
		This->m_pData = NULL;
	}
}

/**
 * Removes all elements from the array.
 */
void PtrArray_Clear(PtrArray* This)
{
	mem_free(This->m_pData);

	This->m_elem_count = 0;
	This->m_alloc_count = 0;
	This->m_pData = NULL;
}

/**
 * Gets an element from the array.
 *
 * @param  index  Index of the element to retrieve.
 *
 * @returns       Pointer to the element.
 */
void* PtrArray_Get(const PtrArray* This, int index) throws(index)
{
	if ((index < 0) || (index >= This->m_elem_count))
		throw_runtime(Err_InvalidIndex);

	return This->m_pData[index];
}

/**
 * Locates an element in the array.
 *
 * @param  index  Index of first element to check.
 * @param  pobj   Pointer to the element to locate.
 *
 * @returns       Index of the first matching element
 *                or -1 if not found.
 */
int PtrArray_Find(const PtrArray* This, int index, const void* pobj) throws(index)
{
	if (index < 0)
		throw_runtime(Err_InvalidIndex);

	for (; index < This->m_elem_count; index++)
	{
		if (This->m_pData[index] == pobj)
			return index;
	}

	if (index > This->m_elem_count)
		throw_runtime(Err_InvalidIndex);

	return -1;
}
