/*-------------------------------------------------------------------------
 * drawElements Memory Pool Library
 * --------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Memory pool array class.
 *
 * Features of the pooled arrays:
 * - single indirection layer (grows exponentially)
 * - constant # elements per page
 * - recycles old indirection tables as element pages
 * - about 10% overhead on large arrays
 *//*--------------------------------------------------------------------*/

#include "dePoolArray.h"
#include "deInt32.h"

#include <stdlib.h>
#include <string.h>

/*--------------------------------------------------------------------*//*!
 * \internal
 * \brief Create a new pool array.
 * \param pool			Pool to allocate memory from.
 * \param elementSize	Size of the element to be put in array.
 * \param Pointer to the created array, or null on failure.
 *//*--------------------------------------------------------------------*/
dePoolArray* dePoolArray_create (deMemPool* pool, int elementSize)
{
	/* Alloc struct. */
	dePoolArray* arr = DE_POOL_NEW(pool, dePoolArray);
	if (!arr)
		return DE_NULL;

	/* Init array. */
	memset(arr, 0, sizeof(dePoolArray));
	arr->pool			= pool;
	arr->elementSize	= elementSize;

	return arr;
}

/*--------------------------------------------------------------------*//*!
 * \internal
 * \brief Ensure that the array can hold at least N elements.
 * \param arr	Array pointer.
 * \param size	Number of elements for which to reserve memory.
 * \param True on success, false on failure.
 *//*--------------------------------------------------------------------*/
deBool			dePoolArray_reserve			(dePoolArray* arr, int size)
{
	if (size >= arr->capacity)
	{
		void*	oldPageTable			= DE_NULL;
		int		oldPageTableSize		= 0;

		int		newCapacity				= deAlign32(size, 1 << DE_ARRAY_ELEMENTS_PER_PAGE_LOG2);
		int		reqPageTableCapacity	= newCapacity >> DE_ARRAY_ELEMENTS_PER_PAGE_LOG2;

		if (arr->pageTableCapacity < reqPageTableCapacity)
		{
			int		newPageTableCapacity	= deMax32(2*arr->pageTableCapacity, reqPageTableCapacity);
			void**	newPageTable			= (void**)deMemPool_alloc(arr->pool, newPageTableCapacity * sizeof(void*));
			int		i;

			if (!newPageTable)
				return DE_FALSE;

			for (i = 0; i < arr->pageTableCapacity; i++)
				newPageTable[i] = arr->pageTable[i];

			for (; i < newPageTableCapacity; i++)
				newPageTable[i] = DE_NULL;

			/* Grab information about old page table for recycling purposes. */
			oldPageTable		= arr->pageTable;
			oldPageTableSize	= arr->pageTableCapacity * sizeof(void*);

			arr->pageTable			= newPageTable;
			arr->pageTableCapacity	= newPageTableCapacity;
		}

		/* Allocate new pages. */
		{
			int pageAllocSize = arr->elementSize << DE_ARRAY_ELEMENTS_PER_PAGE_LOG2;
			int pageTableNdx = arr->capacity >> DE_ARRAY_ELEMENTS_PER_PAGE_LOG2;

			/* Allocate new pages from recycled old page table. */
			while (oldPageTableSize >= pageAllocSize)
			{
				void* newPage = oldPageTable;
				DE_ASSERT(arr->pageTableCapacity > pageTableNdx); /* \todo [petri] is this always true? */
				DE_ASSERT(!arr->pageTable[pageTableNdx]);
				arr->pageTable[pageTableNdx++] = newPage;

				oldPageTable = (void*)((deUint8*)oldPageTable + pageAllocSize);
				oldPageTableSize -= pageAllocSize;
			}

			/* Allocate the rest of the needed pages from the pool. */
			for (; pageTableNdx < reqPageTableCapacity; pageTableNdx++)
			{
				void* newPage = deMemPool_alloc(arr->pool, pageAllocSize);
				if (!newPage)
					return DE_FALSE;

				DE_ASSERT(!arr->pageTable[pageTableNdx]);
				arr->pageTable[pageTableNdx] = newPage;
			}

			arr->capacity = pageTableNdx << DE_ARRAY_ELEMENTS_PER_PAGE_LOG2;
			DE_ASSERT(arr->capacity >= newCapacity);
		}
	}

	return DE_TRUE;
}

/*--------------------------------------------------------------------*//*!
 * \internal
 * \brief Set the size of the array (also reserves capacity).
 * \param arr	Array pointer.
 * \param size	New size of the array (in elements).
 * \param True on success, false on failure.
 *//*--------------------------------------------------------------------*/
deBool			dePoolArray_setSize			(dePoolArray* arr, int size)
{
	if (!dePoolArray_reserve(arr, size))
		return DE_FALSE;

	arr->numElements = size;
	return DE_TRUE;
}

DE_DECLARE_POOL_ARRAY(dePoolIntArray, int);
DE_DECLARE_POOL_ARRAY(dePoolInt16Array, deInt16);

/*--------------------------------------------------------------------*//*!
 * \internal
 * \brief Test array functionality.
 *//*--------------------------------------------------------------------*/
void dePoolArray_selfTest (void)
{
	deMemPool*			pool	= deMemPool_createRoot(DE_NULL, 0);
	dePoolIntArray*		arr		= dePoolIntArray_create(pool);
	dePoolInt16Array*	arr16	= dePoolInt16Array_create(pool);
	int					i;

	/* Test pushBack(). */
	for (i = 0; i < 5000; i++)
	{
		/* Dummy alloc to try to break alignments. */
		deMemPool_alloc(pool, 1);

		dePoolIntArray_pushBack(arr, i);
		dePoolInt16Array_pushBack(arr16, (deInt16)i);
	}

	DE_TEST_ASSERT(dePoolIntArray_getNumElements(arr) == 5000);
	DE_TEST_ASSERT(dePoolInt16Array_getNumElements(arr16) == 5000);
	for (i = 0; i < 5000; i++)
	{
		DE_TEST_ASSERT(dePoolIntArray_get(arr, i) == i);
		DE_TEST_ASSERT(dePoolInt16Array_get(arr16, i) == i);
	}

	/* Test popBack(). */
	for (i = 0; i < 1000; i++)
	{
		DE_TEST_ASSERT(dePoolIntArray_popBack(arr) == (4999 - i));
		DE_TEST_ASSERT(dePoolInt16Array_popBack(arr16) == (4999 - i));
	}

	DE_TEST_ASSERT(dePoolIntArray_getNumElements(arr) == 4000);
	DE_TEST_ASSERT(dePoolInt16Array_getNumElements(arr16) == 4000);
	for (i = 0; i < 4000; i++)
	{
		DE_TEST_ASSERT(dePoolIntArray_get(arr, i) == i);
		DE_TEST_ASSERT(dePoolInt16Array_get(arr16, i) == i);
	}

	/* Test setSize(). */
	dePoolIntArray_setSize(arr, 1000);
	dePoolInt16Array_setSize(arr16, 1000);
	for (i = 1000; i < 5000; i++)
	{
		dePoolIntArray_pushBack(arr, i);
		dePoolInt16Array_pushBack(arr16, (deInt16)i);
	}

	DE_TEST_ASSERT(dePoolIntArray_getNumElements(arr) == 5000);
	DE_TEST_ASSERT(dePoolInt16Array_getNumElements(arr16) == 5000);
	for (i = 0; i < 5000; i++)
	{
		DE_TEST_ASSERT(dePoolIntArray_get(arr, i) == i);
		DE_TEST_ASSERT(dePoolInt16Array_get(arr16, i) == i);
	}

	/* Test set() and pushBack() with reserve(). */
	arr = dePoolIntArray_create(pool);
	dePoolIntArray_setSize(arr, 1500);
	dePoolIntArray_reserve(arr, 2000);
	for (i = 0; i < 1500; i++)
		dePoolIntArray_set(arr, i, i);
	for (; i < 5000; i++)
		dePoolIntArray_pushBack(arr, i);

	DE_TEST_ASSERT(dePoolIntArray_getNumElements(arr) == 5000);
	for (i = 0; i < 5000; i++)
	{
		int val = dePoolIntArray_get(arr, i);
		DE_TEST_ASSERT(val == i);
	}

	deMemPool_destroy(pool);
}