//
// Asura_DemonWare_MemoryManager.h
//
// Created by Andrew Catlender - 06 February 2006 12:59:10
//
// Copyright (C) 2006 Rebellion (Derby), All Rights Reserved.
//
//
// Wrapper class to allow integration of DemonWare's Matchmaking+ SDK
//

// Asura include
#include "Core/Asura.h"

#if defined(ASURA_SUPPORT_EXTERNAL_NETWORK) && defined (ASURA_SUPPORT_DEMONWARE)

// External includes
#if defined(SN_TARGET_PS2) && defined(ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT)
#include <libeenet.h>
#endif // SN_TARGET_PS2 && ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT

// Engine includes
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMHEAP)
#include "Core/Asura_Memheap.h"
#endif // ASURA_DEMONWARE_USE_ASURA_MEMHEAP
#include "Core/Asura_String.h"
#include "Debugging/Asura_Assert.h"
#include "Network/Asura_DemonWare_LogSubscriber.h"

// This file's include
#include "Network/Asura_DemonWare_MemoryManager.h"

// Static variables
bool Asura_DemonWare_MemoryManager::s_bInitialised = false;
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMHEAP)
Asura_MemHeap* Asura_DemonWare_MemoryManager::s_pxMemoryHeap = NULL;
void* Asura_DemonWare_MemoryManager::s_pHeapBuffer = NULL;
#endif // ASURA_DEMONWARE_USE_ASURA_MEMHEAP

#if defined(ASURA_LOGFILES_ENABLED)
int Asura_DemonWare_MemoryManager::s_iAllocateCount = 0;
int Asura_DemonWare_MemoryManager::s_iDeallocateCount = 0;
int Asura_DemonWare_MemoryManager::s_iReallocateCount = 0;
int Asura_DemonWare_MemoryManager::s_iAllocateAlignedCount = 0;
int Asura_DemonWare_MemoryManager::s_iDeallocateAlignedCount = 0;
int Asura_DemonWare_MemoryManager::s_iReallocateAlignedCount = 0;

#if defined(ASURA_DEMONWARE_LOG_VERBOSE)
	int Asura_DemonWare_MemoryManager::s_iAllocationIndex = 0;
#endif // ASURA_DEMONWARE_LOG_VERBOSE
#endif // ASURA_LOGFILES_ENABLED

// Belt and braces compilation option checking
#if defined(_WIN32) && defined(ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT)
#error Cannot use ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT on PC
#endif // _WIN32 && ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT) && defined(ASURA_DEMONWARE_USE_ASURA_MEMHEAP)
#error Cannot use ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT and ASURA_DEMONWARE_USE_ASURA_MEMHEAP
#endif // ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT && ASURA_DEMONWARE_USE_ASURA_MEMHEAP
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT) && defined(ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT)
#error Cannot use ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT and ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT
#endif // ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT && ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMHEAP) && defined(ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT)
#error Cannot use ASURA_DEMONWARE_USE_ASURA_MEMHEAP and ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT
#endif // ASURA_DEMONWARE_USE_ASURA_MEMHEAP && ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT
#if !defined(ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT) && !defined(ASURA_DEMONWARE_USE_ASURA_MEMHEAP) && !defined(ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT)
#error Must specify which memory manager to use for DemonWare in Asura_EngineConfiguration.h!
#endif // !ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT && !ASURA_DEMONWARE_USE_ASURA_MEMHEAP && !ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT

//
//	Initialise()
//
void Asura_DemonWare_MemoryManager::Initialise(void)
{
	Asura_Assert(s_bInitialised == false, "Asura_DemonWare_MemoryManager::Initialise() called twice!");
	if (s_bInitialised == true)
	{
		return;
	}

#if defined(ASURA_DEMONWARE_USE_ASURA_MEMHEAP)
	// Create memory heap for use by DemonWare
	s_pHeapBuffer = Asura_Allocate(ASURA_DEMONWARE_HEAP_SIZE);
	Asura_Assert(s_pHeapBuffer != NULL, "Asura_DemonWare_MemoryManager::Initialise() - unable to create heap");
	if (s_pHeapBuffer != NULL)
	{
		s_pxMemoryHeap = new Asura_MemHeap(s_pHeapBuffer, ASURA_DEMONWARE_HEAP_SIZE);		
	}
#endif // ASURA_DEMONWARE_USE_ASURA_MEMHEAP

	s_bInitialised = true;

#if defined(ASURA_LOGFILES_ENABLED)
	s_iAllocateCount = 0;
	s_iDeallocateCount = 0;
	s_iReallocateCount = 0;
	s_iAllocateAlignedCount = 0;
	s_iDeallocateAlignedCount = 0;
	s_iReallocateAlignedCount = 0;
#endif // ASURA_LOGFILES_ENABLED

	ASURA_DEMONWARE_LOG(("Asura_DemonWare_MemoryManager initialised"));
}

//
//	ShutDown()
//
void Asura_DemonWare_MemoryManager::ShutDown(void)
{
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMHEAP)
	// Free up the heap
	if (s_pxMemoryHeap != NULL)
	{
		delete s_pxMemoryHeap;
		s_pxMemoryHeap = NULL;
		Asura_Deallocate(s_pHeapBuffer);
		s_pHeapBuffer = NULL;
	}
#endif // ASURA_DEMONWARE_USE_ASURA_MEMHEAP

#if defined(ASURA_LOGFILES_ENABLED)
	ASURA_DEMONWARE_LOG(("Asura_DemonWare_MemoryManager: Allocate count        [%7i], Deallocate count        [%7i]", s_iAllocateCount, s_iDeallocateCount));
	ASURA_DEMONWARE_LOG(("Asura_DemonWare_MemoryManager: AllocateAligned count [%7i], DeallocateAligned count [%7i]", s_iAllocateAlignedCount, s_iDeallocateAlignedCount));
	ASURA_DEMONWARE_LOG(("Asura_DemonWare_MemoryManager: Reallocate count      [%7i], ReallocateAligned count [%7i]", s_iReallocateCount, s_iReallocateAlignedCount));
#endif // ASURA_LOGFILES_ENABLED

	ASURA_DEMONWARE_LOG(("Asura_DemonWare_MemoryManager shut down"));

	s_bInitialised = false;
}

//
//	Allocate()
//
void* Asura_DemonWare_MemoryManager::Allocate(ASURA_UINT uiBytes)
{
	Asura_Assert(s_bInitialised == true, "Asura_DemonWare_MemoryManager::Allocate() : memory manager not initialised!");

#if defined(ASURA_LOGFILES_ENABLED)
	s_iAllocateCount++;
#endif // ASURA_LOGFILES_ENABLED

#if defined(ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT)
	void* pAddress = Asura_Allocate(uiBytes);
#endif // ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMHEAP)
	void* pAddress = s_pxMemoryHeap->Alloc(uiBytes);
#endif // ASURA_DEMONWARE_USE_ASURA_MEMHEAP
#if defined(ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT)
	void* pAddress = sceEENetMalloc(uiBytes);
#endif // ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT

#if defined(ASURA_LOGFILES_ENABLED) && defined(ASURA_DEMONWARE_LOG_MEMORY)
	ASURA_DEMONWARE_LOG(("Allocate(%i) => 0x%08X", uiBytes, pAddress));
#endif // ASURA_LOGFILES_ENABLED && ASURA_DEMONWARE_LOG_MEMORY

	return pAddress;
};

//
//	Deallocate()
//
void Asura_DemonWare_MemoryManager::Deallocate(void* pAddress)
{
	Asura_Assert(s_bInitialised == true, "Asura_DemonWare_MemoryManager::Deallocate() : memory manager not initialised!");

#if defined(ASURA_LOGFILES_ENABLED)
	s_iDeallocateCount++;
#endif // ASURA_LOGFILES_ENABLED

#if defined(ASURA_LOGFILES_ENABLED) && defined(ASURA_DEMONWARE_LOG_MEMORY)
	ASURA_DEMONWARE_LOG(("Deallocate(0x%08X)", , pAddress));
#endif // ASURA_LOGFILES_ENABLED && ASURA_DEMONWARE_LOG_MEMORY

#if defined(ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT)
	Asura_Deallocate(pAddress);
#endif // ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMHEAP)
	s_pxMemoryHeap->Free(pAddress);
#endif // ASURA_DEMONWARE_USE_ASURA_MEMHEAP
#if defined(ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT)
	sceEENetFree(pAddress);
#endif // ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT
};

//
//	Reallocate()
//
void* Asura_DemonWare_MemoryManager::Reallocate(void* pAddress, ASURA_UINT uiBytes)
{
	Asura_Assert(s_bInitialised == true, "Asura_DemonWare_MemoryManager::Reallocate() : memory manager not initialised!");

#if defined(ASURA_LOGFILES_ENABLED)
	s_iReallocateCount++;
#endif // ASURA_LOGFILES_ENABLED

	// Simulate realloc with Allocate/Deallocate
	if (pAddress == NULL)
	{
		// If the pointer is NULL, treat as Allocate()
		#if defined(ASURA_LOGFILES_ENABLED)
			s_iReallocateCount--;
		#endif // ASURA_LOGFILES_ENABLED
		return Allocate(uiBytes);
	}

	if (uiBytes == 0)
	{
		// If the size is 0, treat as Deallocate()
		#if defined(ASURA_LOGFILES_ENABLED)
			s_iReallocateCount--;
		#endif // ASURA_LOGFILES_ENABLED
		Deallocate(pAddress);
		return NULL;
	}

	void* pNewBlock = Allocate(uiBytes);
	if (pNewBlock != 0)
	{
		// Copy the original data to the new block
		memcpy(pNewBlock, pAddress, uiBytes);
		Deallocate(pAddress);
	}

#if defined(ASURA_LOGFILES_ENABLED) && defined(ASURA_DEMONWARE_LOG_MEMORY)
	ASURA_DEMONWARE_LOG(("Reallocate(0x%08X, %i) => 0x%08X", pAddress, uiBytes, pNewBlock));
#endif // ASURA_LOGFILES_ENABLED && ASURA_DEMONWARE_LOG_MEMORY
	return pNewBlock;
};

//
//	AllocateAligned()
//
void* Asura_DemonWare_MemoryManager::AllocateAligned(ASURA_UINT uiBytes, ASURA_UINT uiAlignment)
{
	Asura_Assert(s_bInitialised == true, "Asura_DemonWare_MemoryManager::AllocateAligned() : memory manager not initialised!");

#if defined(ASURA_LOGFILES_ENABLED)
	s_iAllocateAlignedCount++;
#endif // ASURA_LOGFILES_ENABLED

	// Aligned memory management is only valid in debug builds
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT)
	#if defined(ASURA_MEMORYMANAGEMENT_ENABLED)
		ASURA_ALIGN_ID eAlignment = ASURA_ALIGN_16_BYTE;
		switch(uiAlignment)
		{
			case 2:
				eAlignment = ASURA_ALIGN_2_BYTE;
				break;
			case 4:
				eAlignment = ASURA_ALIGN_4_BYTE;
				break;
			case 8:
				eAlignment = ASURA_ALIGN_8_BYTE;
				break;
			case 16:
				eAlignment = ASURA_ALIGN_16_BYTE;
				break;
			case 32:
				eAlignment = ASURA_ALIGN_32_BYTE;
				break;
			case 64:
				eAlignment = ASURA_ALIGN_64_BYTE;
				break;
			case 128:
				eAlignment = ASURA_ALIGN_128_BYTE;
				break;
		}

		void* pAddress = Asura_AllocateAlign(uiBytes, eAlignment);
	#else
		void* pAddress = Asura_Allocate(uiBytes);
	#endif // ASURA_MEMORYMANAGEMENT_ENABLED
#endif // ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMHEAP)
	void* pAddress = s_pxMemoryHeap->Alloc(uiBytes, uiAlignment);
#endif // ASURA_DEMONWARE_USE_ASURA_MEMHEAP
#if defined(ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT)
	void* pAddress = sceEENetMemalign(uiAlignment, uiBytes);
#endif // ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT

#if defined(ASURA_LOGFILES_ENABLED) && defined(ASURA_DEMONWARE_LOG_MEMORY)
	ASURA_DEMONWARE_LOG(("AllocateAligned(%i, %i) => 0x%08X", uiBytes, uiAlignment, pAddress));
#endif // ASURA_LOGFILES_ENABLED && ASURA_DEMONWARE_LOG_MEMORY

	return pAddress;
};

//
//	DeallocateAligned()
//
void Asura_DemonWare_MemoryManager::DeallocateAligned(void* pAddress)
{
	Asura_Assert(s_bInitialised == true, "Asura_DemonWare_MemoryManager::DeallocateAligned() : memory manager not initialised!");

#if defined(ASURA_LOGFILES_ENABLED)
	s_iDeallocateAlignedCount++;
#endif // ASURA_LOGFILES_ENABLED

#if defined(ASURA_LOGFILES_ENABLED) && defined(ASURA_DEMONWARE_LOG_MEMORY)
	ASURA_DEMONWARE_LOG(("DeallocateAligned(0x%8X)\n", pAddress));
#endif // ASURA_LOGFILES_ENABLED && ASURA_DEMONWARE_LOG_MEMORY

	// Aligned memory management is only valid in debug builds
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT)
	#if defined(ASURA_MEMORYMANAGEMENT_ENABLED)
		Asura_DeallocateAlign(pAddress);
	#else
		Asura_Deallocate(pAddress);
	#endif // ASURA_MEMORYMANAGEMENT_ENABLED
#endif // ASURA_DEMONWARE_USE_ASURA_MEMORYMANAGEMENT
#if defined(ASURA_DEMONWARE_USE_ASURA_MEMHEAP)
	s_pxMemoryHeap->Free(pAddress);
#endif // ASURA_DEMONWARE_USE_ASURA_MEMHEAP
#if defined(ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT)
	sceEENetFree(pAddress);
#endif // ASURA_DEMONWARE_USE_EENET_MEMORYMANAGEMENT
};

//
//	ReallocateAligned()
//
void* Asura_DemonWare_MemoryManager::ReallocateAligned(void* pAddress, ASURA_UINT uiBytes, ASURA_UINT uiAlignment)
{
	Asura_Assert(s_bInitialised == true, "Asura_DemonWare_MemoryManager::ReallocateAligned() : memory manager not initialised!");

#if defined(ASURA_LOGFILES_ENABLED)
	s_iReallocateAlignedCount++;
#endif // ASURA_LOGFILES_ENABLED

	// Simulate realloc with Allocate/Deallocate
	if (pAddress == NULL)
	{
		// If the pointer is NULL, treat as AllocateAligned()
		#if defined(ASURA_LOGFILES_ENABLED)
			s_iReallocateAlignedCount--;
		#endif // ASURA_LOGFILES_ENABLED
		return AllocateAligned(uiBytes, uiAlignment);
	}

	if (uiBytes == 0)
	{
		// If the size is 0, treat as DeallocateAligned()
		#if defined(ASURA_LOGFILES_ENABLED)
			s_iReallocateAlignedCount--;
		#endif // ASURA_LOGFILES_ENABLED
		DeallocateAligned(pAddress);
		return NULL;
	}

	void* pNewBlock = AllocateAligned(uiBytes, uiAlignment);
	if (pNewBlock != 0)
	{
		// Copy the original data to the new block
		memcpy(pNewBlock, pAddress, uiBytes);
		DeallocateAligned(pAddress);
	}

#if defined(ASURA_LOGFILES_ENABLED) && defined(ASURA_DEMONWARE_LOG_MEMORY)
	ASURA_DEMONWARE_LOG(("ReallocateAligned(0x%08X, %i, %i) => 0x%08X\n", pAddress, uiBytes, uiAlignment, pNewBlock));
#endif // ASURA_LOGFILES_ENABLED && ASURA_DEMONWARE_LOG_MEMORY
	return pNewBlock;
};

#endif // ASURA_SUPPORT_EXTERNAL_NETWORK && ASURA_SUPPORT_DEMONWARE
