#include "KePageAllocator.h"

using namespace Knight;

struct PageInfo
{
	PageInfo *next;
	void *base;
};

class Knight::KePageAllocator
{
public:
	size_t page_size;
	size_t page_granularity;
	PageInfo *free_pages;
	PageInfo *page_blocks;
};

static void *ke_LumpPageAlloc(KePageAllocator *alloc)
{
	void *base;
	char *page;
	PageInfo *lump;
	size_t pagesInBlock;

#if defined KE_PLATFORM_WINDOWS
	base = VirtualAlloc(
		NULL,
		alloc->page_granularity,
		MEM_COMMIT|MEM_RESERVE,
		PAGE_READWRITE);
#elif defined KE_PLATFORM_POSIX
	base = valloc(alloc->page_granularity);
#else 
#error "Unsupported platform"
#endif

	if (base == NULL)
	{
		return NULL;
	}

	lump = new PageInfo;
	lump->base = base;
	lump->next = alloc->page_blocks;
	alloc->page_blocks = lump->next;

	page = (char *)base + alloc->page_size;
	pagesInBlock = alloc->page_granularity / alloc->page_size;

	for (size_t i = 1; i < pagesInBlock; i++)
	{
		lump = new PageInfo;
		lump->base = page;
		lump->next = alloc->free_pages;
		alloc->free_pages = lump;
		page += alloc->page_size;
	}

	return base;
}

KePageAllocator *Knight::KE_CreatePageAllocator()
{
	KePageAllocator *alloc;

	alloc = new KePageAllocator;
	
#if defined KE_PLATFORM_WINDOWS
	SYSTEM_INFO info;
	
	GetSystemInfo(&info);
	alloc->page_size = info.dwPageSize;
	alloc->page_granularity = info.dwAllocationGranularity;
#elif defined KE_PLATFORM_POSIX
	alloc->page_size = sysconf(_SC_PAGESIZE);
	alloc->page_granularity = alloc->page_size * 16;
#else 
#error "Unsupported platform"
#endif

	alloc->free_pages = NULL;
	alloc->page_blocks = NULL;

	return alloc;
}

void Knight::KE_DestroyPageAllocator(KePageAllocator *alloc)
{
	PageInfo *info, *next;

	info = alloc->page_blocks;
	while (info != NULL)
	{
		next = info->next;
#if defined KE_PLATFORM_WINDOWS
		VirtualFree(info->base, 0, MEM_RELEASE);
#elif defined KE_PLATFORM_WINDOWS
		free(info->base);
#else 
#error "Unsupported platform"
#endif
		delete info;
		next = info;
	}

	info = alloc->free_pages;
	while (info != NULL)
	{
		next = info->next;
		delete info;
		info = next;
	}
}

void *Knight::KE_PageAlloc(KePageAllocator *alloc)
{
	if (alloc->free_pages != NULL)
	{
		void *base;
		PageInfo *info;

		info = alloc->free_pages;
		alloc->free_pages = info->next;
		base = info->base;
		delete info;

		return base;
	}

	return ke_LumpPageAlloc(alloc);
}

void Knight::KE_PageFree(KePageAllocator *alloc, void *page)
{
	PageInfo *info;

	info = new PageInfo;
	info->base = page;
	info->next = alloc->free_pages;
	alloc->free_pages = info->next;
}

size_t Knight::KE_PageSize(KePageAllocator *alloc)
{
	return alloc->page_size;
}