/**
*
* \file Asura_Platform_MemoryManagement.cpp
*
* Created by Kevin Lea - 08/12/2001 16:05:53
*
* Copyright (C) 2001 Rebellion, All Rights Reserved.
*
* \brief
*
**/

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// Stdlib platform-specific.
#if defined(ASURA_MEMORYMANAGEMENT_STACK_WALK_ALL) || defined(ASURA_MEMORYMANAGEMENT_STACK_WALK_ANONYMOUS)
#include <dbghelp.h>
#endif
#if defined(ASURA_ASSERT) && defined(ASURA_MEMORYMANAGEMENT_ENABLED) && defined(ASURA_MAIN)
#include <shellapi.h>
#endif

// Engine independent.
#include "Debugging/Asura_Assert.h"

// Engine platform-specific.
#include "Core/Asura_PlatformBasics.h"

// G L O B A L S /////////////////////////////////////////////////////////////

#include "Core/Asura_MemoryManagement_DebugOff.h"
/// Kevin Lea - 08/12/2001 16:13:18 - we want this CRITICAL_SECTION to be apart from 
/// the Memory Manager, since it's used by the Asura allocate fns.
static CRITICAL_SECTION MemoryManager;
#include "Core/Asura_MemoryManagement_DebugOn.h"

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

void Asura_MemoryManagement_System::Platform_Initialise(void)
{
	InitializeCriticalSection(&MemoryManager);

#if defined(ASURA_MEMORYMANAGEMENT_STACK_WALK_ALL) || defined(ASURA_MEMORYMANAGEMENT_STACK_WALK_ANONYMOUS)
	SymInitialize( GetCurrentProcess(), NULL, TRUE );
#endif
}

void Asura_MemoryManagement_System::Platform_Lock(void)
{
	EnterCriticalSection(&MemoryManager);
}

void Asura_MemoryManagement_System::Platform_Unlock(void)
{
	LeaveCriticalSection(&MemoryManager);
}

#ifdef ASURA_MEMORYMANAGEMENT_ENABLED
u_int Asura_MemoryManagement_System::Platform_GetAddressSpaceStart(void) { return 0x0; } //base address of our process
u_int Asura_MemoryManagement_System::Platform_GetAddressSpaceEnd(void) { return 0x40000000; } //1gb (first half of virtual memory available to our process)
#endif


typedef bool ((CALLBACK* DLLFunc)(void**));		// Pointer to the functions in the DLL needed to disable/revert 64-bit program lookup

void Asura_MemoryManagement_System::Platform_Shutdown(void)
{
#if defined(ASURA_MEMORYMANAGEMENT_STACK_WALK_ALL) || defined(ASURA_MEMORYMANAGEMENT_STACK_WALK_ANONYMOUS)
    SymCleanup( GetCurrentProcess() );
#endif

    DeleteCriticalSection(&MemoryManager);

	#if defined(ASURA_ASSERT) && defined(ASURA_MEMORYMANAGEMENT_ENABLED) && defined(ASURA_MAIN)
	//The assert dialog boxes (or any dialog boxes for that matter) don't work at the
	//point that the memory leaks are detected , so some alternative feedback is done here.
	//RAR 16/05/2007
	if(s_bMemoryLeak && ! AsuraAssert_GetIgnoreAll())
	{
		STARTUPINFO			xSI;
		PROCESS_INFORMATION xPI;
		char				szCmdLine [1024];

		// For 64-bit Vista machines, we need to tell it to disable trying to run the 32-bit versions of the files.
		void*	pOldRedirValue;

		// Can we disable it?
		HINSTANCE	hDLL;				// DLL handle
		hDLL = LoadLibrary ("Kernel32.dll");	// Where Wow64DisableWow64FsRedirection resides
		if (hDLL)
		{
			DLLFunc pfnDllDisableFunction = (DLLFunc)(GetProcAddress (hDLL, "Wow64DisableWow64FsRedirection"));
			if (pfnDllDisableFunction)
			{
				pfnDllDisableFunction (&pOldRedirValue);
			}
		}	// End If DLL Loaded OK.
		

		#if defined(_DEBUG) && defined(ASURA_LOGFILES_ENABLED)
			// Open the memory leak logfile in a default text editor
			ZeroMemory (&xSI, sizeof (STARTUPINFO));
			xSI.cb = sizeof (STARTUPINFO);
			ZeroMemory( &xPI, sizeof (PROCESS_INFORMATION));

			char acFilePath[MAX_PATH];
			Asura_PrintfToBfr(acFilePath, "%s%s", MAX_PATH, true, GetDefaultMallocDumpPath(), GetMemoryLeakFilename());
			Asura_PrintfToBfr (szCmdLine, "\"notepad.exe\" \"%s\"", sizeof(szCmdLine), true, acFilePath);
			if( CreateProcess(NULL, szCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &xSI, &xPI) )
			{
				CloseHandle( xPI.hProcess );
				CloseHandle( xPI.hThread );
			}
		#endif // DEBUG && ASURA LOGFILES ENABLED

		// Show a dialog box by using msg.exe
        ZeroMemory( &xSI, sizeof(STARTUPINFO) );
        xSI.cb = sizeof(STARTUPINFO);
        ZeroMemory( &xPI, sizeof(PROCESS_INFORMATION) );

		Asura_PrintfToBfr (szCmdLine, "\"msg.exe\" * \"ASURA MEMORY MANAGER DETECTED MEMORY LEAKS - PLEASE SEE LOGFILE\"", sizeof(szCmdLine), true);
        if( CreateProcess (NULL, szCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &xSI, &xPI) )
        {
            CloseHandle( xPI.hProcess );
            CloseHandle( xPI.hThread );
        }

		// Restore the previous redirection value.
		if (hDLL)
		{
			DLLFunc pfnDllRevertFunction = (DLLFunc)(GetProcAddress (hDLL, "Wow64RevertWow64FsRedirection"));
			if (pfnDllRevertFunction)
			{
				pfnDllRevertFunction (&pOldRedirValue);
			}
			FreeLibrary (hDLL);
		}	// End If DLL loaded OK.
	}	// End If Memory Leak and not ignoring asserts
	#endif
}

#ifdef ASURA_MEMORYMANAGEMENT_ENABLED

#ifdef ASURA_MEMORYMANAGEMENT_FILL

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Asura_MemoryManagement_System::Platform_FillMem().
// PARAMETERS   pAddr          - Address, or actual address if aligned.
//              uSize          - Size, or actual size if aligned.
//              eEventID       - Type of memory management event.
//              uAlignment     - Alignment type, or 0 if no alignment.
//              pReportedAddr  - Reported address if memory is aligned.
//              uReportedSize  - Reported size if memory is aligned.
// RETURNS      None.
// DESCRIPTION  Fills specified region of memory. If the caller requested
//              memory aligned on a 64-byte boundary and a multiple of 64
//              bytes in size then we ensure the returned memory is not
//              present in the cache.
// AUTHOR       Morgan Parry - 29.07.2002.
//
//////////////////////////////////////////////////////////////////////////////
void Asura_MemoryManagement_System::Platform_FillMem( void* pAddr,
                                                      u_int uSize,
                                                      ASURA_MM_EVENT_ID eEventID,
                                                      u_int uAlignment,
                                                      void* pReportedAddr /*= NULL*/,
                                                      u_int 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.
    u_char* pucOut = reinterpret_cast<u_char*>( pAddr );
   
    {
        while( uSize-- ) { *pucOut++ = ucFill; }
    }
}
// End function Asura_MemoryManagement_System::Platform_FillMem().

#endif // #ifdef ASURA_MEMORYMANAGEMENT_FILL

void Asura_MemoryManagement_System::Platform_PostDeallocate( void )
{
}

#else // #ifdef ASURA_MEMORYMANAGEMENT_ENABLED

void* Asura_PC_AllocateAlign(unsigned int bytes, enum ASURA_ALIGN_ID alignment)
{
    int iTotalBytes = bytes + alignment + 4;    // 4 bytes before reported address is ptr to start of block

    char* const pcMem = (char*)Asura_Allocate(iTotalBytes);
    
    if (!pcMem) 
    {
        return NULL;
    }

    unsigned int offset = (alignment - ((unsigned int)(pcMem + 4)) & (alignment-1));
	char* const pcReportedMem = (char*)((unsigned char*)(pcMem + 4) + offset);
    u_int* const pcActualAddressStore = (u_int*)(pcReportedMem - 4);
    *pcActualAddressStore = (u_int)pcMem;
    //printf("Asura_PS2_AllocateAlign: %d bytes, %d align, addr = 0x%8p\n", bytes, alignment, pcReportedMem);
    return pcReportedMem;
}

void Asura_PC_DeallocateAlign(void* address)
{
    if (!address) return;

    u_int* puActualAddress = ((u_int*)address) - 1;
    u_int uActualAddress = *puActualAddress;
    //printf("Asura_PS2_DeallocateAlign: addr = 0x%8p, actual = 0x%8x\n", address, uActualAddress);
    Asura_Deallocate((void*)uActualAddress);
}

#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

#if !ASURA_MEMORYMANAGEMENT_PLATFORM_INIT_WHEN_DISABLED || !ASURA_MEMORYMANAGEMENT_INIT_OBJ_IN_PLATFORM_CODE
#error This code assumes these defines are turned on in this file's header.
#endif
#pragma warning(disable: 4073)
#pragma init_seg(lib) // Ensure this is constructed before any of the other globals in our application.
static Asura_MemoryManagement_System g_xMemManagementInitialiserObj;
