//
// Asura_Platform_MemoryManagement.cpp
//
// Created by KOR - 09 October 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// Asura headers
#include "Core/Asura_Multithread.h"
#include "Core/Asura_String.h"
#ifdef ASURA_MEMORYMANAGEMENT_FILL
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Assert.h"
#endif

// Library headers
#include <revolution/gx.h>

#ifdef ASURA_MEMHEAP_REPLACEMENT
#error ASURA_MEMHEAP_REPLACEMENT is deprecated.
#endif // #ifdef ASURA_MEMHEAP_REPLACEMENT

extern "C"
{
static unsigned int g_uHeapSize;
static unsigned int g_uEndOfMainMem = 0;

#define MORECORE_FAILURE    ((void*)(-1))
void* Asura_Wii_GetHeapMem(int iSize)
{
	static bool bFirstTime = true;
    if (bFirstTime && (iSize > 0))
	{
		bFirstTime = false;
		// align prog end to page size (4096)
		unsigned int uHeapStart = (unsigned int)OSAlloc(g_uHeapSize);
		g_uEndOfMainMem = uHeapStart + g_uHeapSize;
		if(uHeapStart)
		{
			uHeapStart = (uHeapStart + 4095) & ~4095;
			return (void*)uHeapStart;
		}
	}

    if (iSize == 0)
    {
        // return end of mem
        return (void*)g_uEndOfMainMem;
    }
    else
    {
        return MORECORE_FAILURE;
    }
}

// KOR - yuck - this should be in a header somewhere.
extern "C"
{
	struct mallinfo {
		int arena;    /* non-mmapped space allocated from system */
		int ordblks;  /* number of free chunks */
		int smblks;   /* number of fastbin blocks */
		int hblks;    /* number of mmapped regions */
		int hblkhd;   /* space in mmapped regions */
		int usmblks;  /* maximum total allocated space */
		int fsmblks;  /* space available in freed fastbin blocks */
		int uordblks; /* total allocated space */
		int fordblks; /* total free space */
		int keepcost; /* top-most, releasable (via malloc_trim) space */
	};

	struct mallinfo mallinfo();
};

DEBUGVAR bool dbg_bDumpMemory = false;

void Asura_Wii_OutOfMem()
{
	// KOR - unfortunately Asura_PrintfToBfr seems to break
	// in this situation (perhaps it attempts to allocate
	// from the heap). We could probably knock together
	// something to do a simple insertion of integers into
	// a string if we really want the information onscreen.
	// For now, we'll make do with a simple "Out of memory"
#if 0
	// Handy for debugging purposes to have mallinfo available.
	const struct mallinfo xMallInfo = mallinfo();

	char szDebuggerBuf[2048];
	int len = 0;

	Asura_PrintfToBfr(szDebuggerBuf + len,  
		"Out of memory:\n\
		number of free chunks: %i\n\
		total free space: %i\n\
		top-most, releasable space: %i\n",
		xMallInfo.ordblks,
		xMallInfo.fordblks,
		xMallInfo.keepcost);
#else
	const char szDebuggerBuf[] = "Out of MEM1 memory";
#endif

#ifdef ASURA_MEMORYMANAGEMENT_ENABLED
	if(dbg_bDumpMemory)
	{
		Asura_MemoryManagement_System::DumpMallocSummary();
		Asura_MemoryManagement_System::DumpMallocInfo();
	}
#endif

#ifndef ASURA_SUBMISSION_BUILD
	const GXColor xFatalBg = { 128, 0, 0, 0 };
	const GXColor xFatalFg = { 255, 255, 255, 0 };
	// Unfortunately we lose our stack when we do this.
	// Might want to investigate a more debuggable version.
	OSFatal( xFatalFg, xFatalBg, szDebuggerBuf);
#endif
}

} // extern "C"

void Asura_MemoryManagement_System::Platform_Initialise(void)
{
	OSHeapHandle xTheHeap;
	void* pvArenaLow;
	void* pvArenaHigh;

	//OSInit();

	pvArenaLow = OSGetArenaLo();
	pvArenaHigh = OSGetArenaHi();

	// The following chunk ripped straight out of Nintendo's OS guide...so it should be correct
	// OSInitAlloc should only ever be invoked once.
	pvArenaLow = OSInitAlloc(pvArenaLow, pvArenaHigh, 1); // 1 heap
	OSSetArenaLo(pvArenaLow);
	// The boundaries given to OSCreateHeap should be 32B aligned
	xTheHeap = OSCreateHeap((void*)OSRoundUp32B(pvArenaLow),(void*)OSRoundDown32B(pvArenaHigh));
	OSSetCurrentHeap(xTheHeap);
	// KOR - Grab everything we can...
	g_uHeapSize = (char *)OSRoundDown32B(pvArenaHigh) - (char *)OSRoundUp32B(pvArenaLow);
	unsigned int uReserved = 5000;
	if(g_uHeapSize > 24000000)
	{
		OSReport("Detected 48Mb. Executable takes %u bytes.\n", (48*1024*1024 - g_uHeapSize));
		// Cut down to 24 Mb for release build
#ifdef NDEBUG
		// KOR - 18/09/2006 - removed this limitation as we are now targeting Wii, which has much more memory
		//g_uHeapSize -= 24*1024*1024;
#else
		// Give ourselves a couple extra meg to deal with the larger elf
		//g_uHeapSize -= 22*1024*1024;
#endif
	}
	else
	{
		OSReport("Detected 24Mb. Executable takes %u bytes.\n", (24*1024*1024 - g_uHeapSize));
	}
	OSReport("Reserving %u bytes for OS.\n", uReserved);
	g_uHeapSize -= uReserved;
	g_uHeapSize = OSRoundDown32B(g_uHeapSize);
	OSReport( "Initial heap size: %i\n", g_uHeapSize);
	OSSetArenaLo(pvArenaLow = pvArenaHigh);
	// From here on out, OSAlloc and OSFree behave like malloc and free respectively
	// End of cut'n'paste job
}

static Asura_Mutex g_xAllocationMutex;

void Asura_MemoryManagement_System::Platform_Lock(void)
{
	g_xAllocationMutex.Lock();
}

void Asura_MemoryManagement_System::Platform_Unlock(void)
{
	g_xAllocationMutex.Unlock();
}

void Asura_MemoryManagement_System::Platform_Shutdown(void)
{
}

#if defined ASURA_MEMORYMANAGEMENT_ENABLED
void Asura_MemoryManagement_System::Platform_PostDeallocate(void)
{
}
#endif

#ifdef ASURA_MEMORYMANAGEMENT_FILL
// Alignment and further arguments are only relevant for allocations.
void Asura_MemoryManagement_System::Platform_FillMem( void* pAddr, unsigned uSize, ASURA_MM_EVENT_ID eEventID, unsigned uAlignment /* = 0 */, void* pReportedAddr /* = NULL */, unsigned uReportedSize /* = 0 */)
{
	if( uAlignment )
	{
		Asura_Assert( pReportedAddr != NULL, "" );
		Asura_Assert( uReportedSize > 0, "" );
	}

	u_char ucFill;
	switch( eEventID )
	{
	case ASURA_NEW:
	case ASURA_NEWALIGN:
	case ASURA_NEWARRAY:
	case ASURA_ALLOC:
		ucFill = ucFillValueAllocate;           
		break;

	case ASURA_DELETE:
	case ASURA_DELETEALIGN:
	case ASURA_DELETEARRAY:
	case ASURA_DEALLOC:
		ucFill = ucFillValueFree;           
		break;

	default:
		Asura_Assert( false, "Invalid memory manager event ID." );
		return;
	}

	// Fill.
	Asura_Mem::Set(pAddr, ucFill, uSize);
}
#endif

#if defined ASURA_MEMORYMANAGEMENT_ENABLED

void Asura_MemoryUtility::Platform_Initialise(void)
{
}

void Asura_MemoryUtility::Platform_Shutdown(void)
{
}

void* Asura_MemoryUtility::Platform_PreAllocate(/*const*/ size_t size)
{
	return 0;
}

void* Asura_MemoryUtility::Platform_PostAllocate(void* /*const*/ pAddr)
{
	return pAddr;
}

bool Asura_MemoryUtility::Platform_PreDeallocate(void* /*const*/ pAddr)
{
	return false;
}

void Asura_MemoryUtility::Platform_PostDeallocate(void* /*const*/ pAddr)
{
}

#endif
