// Copyright (C) 2004-2009 Christoph Kubisch & Eike Decker
// This file is part of the "Luxinia Engine".
// For conditions of distribution and use, see copyright notice in luxiniapublicsdk.txt

#include <luxcore/memorypool.h>
#include <luxplatform/debug.h>

//////////////////////////////////////////////////////////////////////////
// MemoryPool
/**
* Allows to allocate a chunk of data which is parted
* into clusters. You can malloc only one cluster per time. Usefull for data
* structures with constant size like listnodes. There is no guarantee that
* two malloced variables will be adjacent in the memory.
*/

#include "memory_defs.h"

lxMemoryPool_t* lxMemoryPool_new (uint valueSize, uint pageValues, uint alignSize, booln multiPages)
{
	lxMemoryPool_t* memg;

	memg = (lxMemoryPool_t*) MEMORY_LOCAL_MALLOC(sizeof(lxMemoryPool_t));
	MemoryPool_init(memg,valueSize,pageValues,alignSize,multiPages);

	return memg;
}

void MemoryPool_addPage (lxMemoryPool_t* memg)
{
	uint valueSize = memg->valueSize;
	uint pageValues = memg->pageValues;
	uint alignSize = memg->alignSize;
	uint pageSize = pageValues*valueSize;
	uint usedValues = (pageSize-sizeof(lxMemoryPage_t))/valueSize;

	byte* cur;
	byte* data;
	lxMemoryNode_t*	node;
	lxMemoryPage_t*	page;
	uint32 i;

	LUX_DEBUGASSERT(usedValues);

	if (alignSize){
		data = MEMORY_LOCAL_MALLOCALIGNED(pageSize, alignSize );
	}
	else{
		data = MEMORY_LOCAL_MALLOC(pageSize);
	}
	// add page
	page = (lxMemoryPage_t*)data;
	page->next = memg->pagelist;
	memg->pagelist = page;

	// add freeitems
	data += valueSize*(pageValues-usedValues);
	cur = data;
	for (i = 0; i < usedValues-1; ++i, cur+=valueSize){
		node = (lxMemoryNode_t*)cur;
		node->next = (lxMemoryNode_t*)(cur+valueSize);
	}
	// last item
	node = (lxMemoryNode_t*)cur;
	node->next = memg->freelist;
	memg->freelist = (lxMemoryNode_t*)data;
	
}

void MemoryPool_init (lxMemoryPool_t* memg, uint valueSize, uint pageValues, uint alignSize, booln multiPages)
{
	memg->valueSize = valueSize;
	memg->pageValues = pageValues;
	memg->alignSize = alignSize;
	memg->multiPages = multiPages;
	memg->used = 0;
	memg->pagelist = NULL;
	memg->freelist = NULL;

	if (alignSize){
		LUX_ASSERT(valueSize%alignSize == 0);
	}

	MemoryPool_addPage(memg);
}


void * lxMemoryPool_allocItem (lxMemoryPool_t * mem) 
{
	lxMemoryNode_t* node = mem->freelist;
	if (!node && mem->multiPages){
		MemoryPool_addPage(mem);
		node = mem->freelist;
	}
	LUX_ASSERT(node);
	
	mem->used++;
	mem->freelist = node->next;
	
	return (void*)node;
}

void lxMemoryPool_freeItem (lxMemoryPool_t * mem, void *ptrv)
{
	lxMemoryNode_t* node = (lxMemoryNode_t*)ptrv;
	node->next = mem->freelist;
	mem->freelist = node;

	mem->used--;
}

uint lxMemoryPool_shrink(lxMemoryPool_t* mem)
{
	lxMemoryPage_t*	page	= mem->pagelist;
	lxMemoryNode_t*	node	= mem->freelist;
	lxMemoryPage_t**	lastp	= &mem->pagelist;
	booln aligned = mem->alignSize;
	uint pageSize = mem->pageValues * mem->valueSize;
	uint maxValues = (pageSize-sizeof(lxMemoryPage_t))/mem->valueSize;
	uint shrinked = 0;

	// init counters
	while(page){
		lxMemoryPage_t*	next = page->next;
		page->size = 0;

		page = next;
	}

	// find out how many freeitems per page
	while (node){
		byte*	nb = (byte*)node;

		page = mem->pagelist;
		while(page){
			lxMemoryPage_t*	next = page->next;
			byte*	pb = (byte*)page;

			page->size += (nb > pb && nb < pb+pageSize);

			page = next;
		}
		node = node->next;
	}

	while(page){
		lxMemoryPage_t*	next = page->next;
		// remove from list
		if (page->size == maxValues){
			*lastp = next;
			if (aligned){
				MEMORY_LOCAL_FREEALIGNED(page,pageSize);
			}
			else{
				MEMORY_LOCAL_FREE(page,pageSize);
			}

			shrinked  += pageSize;
		}
		else{
			lastp = &page->next;
		}
		page = next;
	}

	return shrinked;
}

void MemoryPool_deinit (lxMemoryPool_t *mem) {
	lxMemoryPage_t*	page  = mem->pagelist;
	uint pagesize = mem->pageValues*mem->valueSize;
	booln aligned = mem->alignSize;
	
	while(page){
		lxMemoryPage_t*	next = page->next;
		if (aligned){
			MEMORY_LOCAL_FREEALIGNED(page,pagesize);
		}
		else{
			MEMORY_LOCAL_FREE(page,pagesize);
		}
		
		page = next;
	}
	
	mem->pagelist = NULL;
	mem->freelist = NULL;
	mem->used = 0;
}

LUX_INLINE uint lxMemoryPool_memUsed(lxMemoryPoolPTR mem)
{
	return mem->used*mem->valueSize;
}
LUX_INLINE uint lxMemoryPool_memAllocated(lxMemoryPoolPTR mem)
{
	byte*	page  = (byte*)mem->pagelist;
	uint pagesize = mem->pageValues*mem->valueSize;
	uint ret = 0;

	while(page){
		lxMemoryNode_t*	next = (lxMemoryNode_t*)(page+pagesize);
		ret += pagesize;
		page = (byte*)next->next;
	}

	return ret;
}

float lxMemoryPool_memRatio(lxMemoryPool_t* mem)
{
	return (float)lxMemoryPool_memUsed(mem)/(float)lxMemoryPool_memAllocated(mem);
}

void lxMemoryPool_delete (lxMemoryPool_t *mem) {
	MemoryPool_deinit(mem);
	MEMORY_LOCAL_FREE(mem,sizeof(lxMemoryPool_t));
}
