#include "ConsecutivePoolAllocator.h"

#include "CustomAssert.h"

#include <stdint.h>

ConsecutivePoolAllocator createConsecutivePoolAllocator(char* b, unsigned bs, unsigned s)
{
	assert(b); //only allocated memory
	assert(bs >= sizeof(void*)); //we need to be able to store
	assert(s%bs==0); //we want a size that is the exact multiple of block size
	assert(s >= bs); //at least 1 element

	ConsecutivePoolAllocator pa =
	{
		.buf = b,
		.nextFreeBlock = (uint32_t*)b,
		.blockSize = bs,
		.size = s
	};

	//initialize linked list of free pointers
	uint32_t* ptr = pa.nextFreeBlock;
	unsigned last = s/bs - 1;
	for(unsigned c = 0; c < last; ++c)
	{
		*ptr = (char*)ptr + bs;
		ptr = (char*)ptr + bs;
	}

	*ptr = 0; //last element

	return pa;
}

void destroyConsecutivePoolAllocator(ConsecutivePoolAllocator* pa)
{
	//actual memory freeing is done by caller
	pa->buf = 0;
	pa->nextFreeBlock = 0;
	pa->blockSize = 0;
	pa->size = 0;
}

//allocate numBlocks consecutive memory
void* consecutivePoolAllocate(ConsecutivePoolAllocator* pa, uint32_t numBlocks)
{
	assert(pa->buf);

	if(!pa->nextFreeBlock)
	{
		return 0; //no free blocks
	}

	void* ret = 0;
	for(uint32_t* candidate = pa->nextFreeBlock; candidate; candidate = (uint32_t*)*candidate)
	{
		uint32_t found = 1;
		uint32_t* prevBlock = candidate;
		uint32_t* blockAfterCandidate = (uint32_t*)*candidate;
		//check if there are enough consecutive free blocks
		for(uint32_t c = 0; c < numBlocks - 1; ++c)
		{
			if(blockAfterCandidate - prevBlock != pa->blockSize)
			{
				//signal if not consecutive (ie. diff is greater than blocksize)
				found = 0;
				break;
			}
			prevBlock = blockAfterCandidate;
			blockAfterCandidate = (uint32_t*)*blockAfterCandidate;
		}

		//numblocks consecutive blocks found
		if(found)
		{
			ret = candidate;
			if(pa->nextFreeBlock == candidate)
			{
				//candidate found immediately
				pa->nextFreeBlock = blockAfterCandidate;
			}
			else
			{
				//somewhere the linked list would point to candidate, we need to correct this
				for(uint32_t* nextFreeBlockCandidate = pa->nextFreeBlock; nextFreeBlockCandidate; nextFreeBlockCandidate = (uint32_t*)*nextFreeBlockCandidate)
				{
					if((uint32_t*)*nextFreeBlockCandidate == candidate)
					{
						*nextFreeBlockCandidate = (uint32_t)blockAfterCandidate;
						break;
					}
				}
			}
			break;
		}
	}

	return ret;
}

//free numBlocks consecutive memory
void consecutivePoolFree(ConsecutivePoolAllocator* pa, void* p, uint32_t numBlocks)
{
	assert(pa->buf);
	assert(p);

	if((void*)pa->nextFreeBlock > p)
	{
		for(uint32_t c = 0; c < numBlocks - 1; ++c)
		{
			//set each allocated block to form a linked list
			*(uint32_t*)((char*)p + c * pa->blockSize) = (uint32_t)((char*)p + (c + 1) * pa->blockSize);
		}
		//set last block to point to the next free
		*(uint32_t*)((char*)p + (numBlocks - 1) * pa->blockSize) = (uint32_t)pa->nextFreeBlock;
		//set next free to the newly freed block
		pa->nextFreeBlock = p;
		return;
	}

	int counter = 0;

	//somewhere the linked list may point after the free block (or null), we need to correct this
	for(uint32_t* nextFreeBlockCandidate = pa->nextFreeBlock; nextFreeBlockCandidate; nextFreeBlockCandidate = (uint32_t*)*nextFreeBlockCandidate)
	{
		//TODO infinite loop
		counter++;

		if(counter > 100)
		{
			fprintf(stderr, "--------------detected infinite loop nextFreeCandidate: %p, *nextfreecandidate: %p, p: %p\n", nextFreeBlockCandidate, *nextFreeBlockCandidate, p);
			break;
		}

		if((void*)*nextFreeBlockCandidate > p || !*nextFreeBlockCandidate)
		{
			for(uint32_t c = 0; c < numBlocks - 1; ++c)
			{
				//set each allocated block to form a linked list
				*(uint32_t*)((char*)p + c * pa->blockSize) = (uint32_t)((char*)p + (c + 1) * pa->blockSize);
			}
			//set last block to point to the next free
			*(uint32_t*)((char*)p + (numBlocks - 1) * pa->blockSize) = *nextFreeBlockCandidate;

			*nextFreeBlockCandidate = (uint32_t)p;
			break;
		}
	}
}

//if there's a block free after the current block, it just allocates one more block
//else it frees current block and allocates a new one
void* consecutivePoolReAllocate(ConsecutivePoolAllocator* pa, void* currentMem, uint32_t currNumBlocks)
{
	if(pa->nextFreeBlock == (uint32_t*)((char*)currentMem + currNumBlocks * pa->blockSize))
	{
		//we have one more block after current one, so just expand current
		pa->nextFreeBlock = (uint32_t*)*pa->nextFreeBlock;
		return currentMem;
	}
	else
	{
		void* ret = consecutivePoolAllocate(pa, currNumBlocks + 1);
		consecutivePoolFree(pa, currentMem, currNumBlocks);
		return ret;
	}
}
