/****************************************************************************
 * Copyright (C) 2013 FIX94
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************/
#include <string.h>
#include <algorithm>
#include "mem_manager.hpp"
#include "gecko/gecko.hpp"
#include "loader/utils.h"

static const u32 MEM_BLOCK_SIZE = 0x80;// 128

MemManager::MemManager()
{
	startAddr = NULL;
	memList = NULL;
	memListEnd = NULL;
	memSize = 0;
	LWP_MutexInit(&memMutex, 0);
}

MemManager::~MemManager()
{
	LWP_MutexDestroy(memMutex);
}

void MemManager::Init(u8 *start, u8 *list, u32 size)
{
	LWP_MutexLock(memMutex);
	//gprintf("Init %x %i\n", memList, memSize);

	startAddr = start;
	memList = list;
	memSize = ALIGN(MEM_BLOCK_SIZE, size) / MEM_BLOCK_SIZE;
	memListEnd = list + memSize;
	memset(memList, MEM_FREE , memSize);
	memset(memListEnd, MEM_END, 1); //thats the +1

	LWP_MutexUnlock(memMutex);
}

void MemManager::ClearMem()
{
	LWP_MutexLock(memMutex);
	//gprintf("ClearMem %x %i\n", startAddr, memSize * MEM_BLOCK_SIZE);

	u32 MemFull = memSize * MEM_BLOCK_SIZE;
	memset(startAddr, 0, MemFull);

	LWP_MutexUnlock(memMutex);
}

void *MemManager::Alloc(u32 size)
{
	size = ALIGN(MEM_BLOCK_SIZE, size) / MEM_BLOCK_SIZE;
	if(size > memSize)
		return NULL;

	LWP_MutexLock(memMutex);

	vu8 *tmp_block;
	u32 blocksFree;
	for(vu8 *block = memList; block < memListEnd; block++)
	{
		for( ; *block & (ALLOC_USED | ALLOC_END); block++) ;
		blocksFree = 0;
		for(tmp_block = block; *tmp_block == MEM_FREE; tmp_block++)
		{
			blocksFree++;
			if(blocksFree == size)
			{
				u8 *addr = (u8*)block;
				memset(addr, ALLOC_USED, blocksFree - 1); //start blocks
				memset(addr + blocksFree - 1, ALLOC_END, 1); //end block
				void *ptr = (void*)(startAddr + ((addr - memList)*MEM_BLOCK_SIZE));
				//gprintf("Alloc %x mem, %i blocks\n", ptr, blocksFree);
				LWP_MutexUnlock(memMutex);
				return ptr;
			}
		}
		block = tmp_block;
	}
	LWP_MutexUnlock(memMutex);
	return NULL;
}

void MemManager::Free(void *mem)
{
	vu8 *blockUsed = memList+(((u8*)mem-startAddr) / MEM_BLOCK_SIZE);
	if(blockUsed < memList || blockUsed >= memListEnd || *blockUsed == 0)
		return;

	LWP_MutexLock(memMutex);

	//gprintf("Free %x mem, %x block\n", mem, blockUsed);

	u32 size = 0;
	vu8 *tmp_block = blockUsed;
	for( ; *tmp_block == ALLOC_USED; tmp_block++)
		size++;
	if(*tmp_block == ALLOC_END)
		size++;

	u8 *addr = (u8*)blockUsed;
	memset(addr, MEM_FREE, size);

	LWP_MutexUnlock(memMutex);
}

u32 MemManager::MemBlockSize(void *mem)
{
	vu8 *blockUsed = memList+(((u8*)mem-startAddr) / MEM_BLOCK_SIZE);
	if(blockUsed < memList || blockUsed >= memListEnd || *blockUsed == 0)
		return 0;

	LWP_MutexLock(memMutex);

	u32 size = 0;
	for( ; *blockUsed == ALLOC_USED; blockUsed++)
		size++;
	if(*blockUsed == ALLOC_END)
		size++;
	size *= MEM_BLOCK_SIZE;

	LWP_MutexUnlock(memMutex);

	return size;
}

u32 MemManager::FreeSize()
{
	LWP_MutexLock(memMutex);

	u32 free = 0;
	for(vu8 *block = memList; block < memListEnd; block++)
	{
		for( ; *block == MEM_FREE; block++)
			free++;
	}
	free *= MEM_BLOCK_SIZE;

	LWP_MutexUnlock(memMutex);

	return free;
}

void *MemManager::ReAlloc(void *mem, u32 size)
{
	if(mem == NULL)
		return Alloc(size);

	//gprintf("Realloc %x, %i\n", mem, size);
	void *new_m = Alloc(size);
	if(new_m == NULL)
	{
		Free(mem);
		return NULL;
	}
	const u32 copysize = std::min(MemBlockSize(mem), size);

	LWP_MutexLock(memMutex);
	memcpy(new_m, mem, copysize);
	LWP_MutexUnlock(memMutex);

	Free(mem);

	return new_m;
}
