#ifndef TIER0_MEMALLOC_H
#define TIER0_MEMALLOC_H

#ifdef _WIN32
#pragma once
#endif

#ifndef POSIX
#endif

#if defined( _MEMTEST )
#ifdef _WIN32
#define USE_MEM_DEBUG 1
#endif
#endif

#define MEM_DEBUG_CLASSNAME 1

#include <stddef.h>
#if defined( OSX )
#include <malloc/malloc.h>
#endif

#include "mem.h"

#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE)

struct _CrtMemState;

#define MEMALLOC_VERSION 1

typedef size_t(*MemAllocFailHandler_t)(size_t);

class IMemAlloc
{
public:
	virtual void* Alloc(size_t nSize) = 0;
	virtual void* Realloc(void* pMem, size_t nSize) = 0;
	virtual void Free(void* pMem) = 0;
	virtual void* Expand_NoLongerSupported(void* pMem, size_t nSize) = 0;

	virtual void* Alloc(size_t nSize, const char* pFileName, int nLine) = 0;
	virtual void* Realloc(void* pMem, size_t nSize, const char* pFileName, int nLine) = 0;
	virtual void  Free(void* pMem, const char* pFileName, int nLine) = 0;
	virtual void* Expand_NoLongerSupported(void* pMem, size_t nSize, const char* pFileName, int nLine) = 0;

	virtual size_t GetSize(void* pMem) = 0;

	virtual void PushAllocDbgInfo(const char* pFileName, int nLine) = 0;
	virtual void PopAllocDbgInfo() = 0;

	virtual long CrtSetBreakAlloc(long lNewBreakAlloc) = 0;
	virtual	int CrtSetReportMode(int nReportType, int nReportMode) = 0;
	virtual int CrtIsValidHeapPointer(const void* pMem) = 0;
	virtual int CrtIsValidPointer(const void* pMem, unsigned int size, int access) = 0;
	virtual int CrtCheckMemory(void) = 0;
	virtual int CrtSetDbgFlag(int nNewFlag) = 0;
	virtual void CrtMemCheckpoint(_CrtMemState* pState) = 0;

	virtual void DumpStats() = 0;
	virtual void DumpStatsFileBase(char const* pchFileBase) = 0;

	virtual void* CrtSetReportFile(int nRptType, void* hFile) = 0;
	virtual void* CrtSetReportHook(void* pfnNewHook) = 0;
	virtual int CrtDbgReport(int nRptType, const char* szFile,
			int nLine, const char* szModule, const char* pMsg) = 0;

	virtual int heapchk() = 0;

	virtual bool IsDebugHeap() = 0;

	virtual void GetActualDbgInfo(const char*& pFileName, int& nLine) = 0;
	virtual void RegisterAllocation(const char* pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime) = 0;
	virtual void RegisterDeallocation(const char* pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime) = 0;

	virtual int GetVersion() = 0;

	virtual void CompactHeap() = 0;

	virtual MemAllocFailHandler_t SetAllocFailHandler(MemAllocFailHandler_t pfnMemAllocFailHandler) = 0;

	virtual void DumpBlockStats(void*) = 0;

#if defined( _MEMTEST )	
	virtual void SetStatsExtraInfo(const char* pMapName, const char* pComment) = 0;
#endif

	virtual size_t MemoryAllocFailed() = 0;

	virtual uint32 GetDebugInfoSize() = 0;
	virtual void SaveDebugInfo(void* pvDebugInfo) = 0;
	virtual void RestoreDebugInfo(const void* pvDebugInfo) = 0;
	virtual void InitDebugInfo(void* pvDebugInfo, const char* pchRootFileName, int nLine) = 0;

	virtual void GlobalMemoryStatus(size_t* pUsedMemory, size_t* pFreeMemory) = 0;
};

MEM_INTERFACE IMemAlloc* g_pMemAlloc;

#ifdef MEMALLOC_REGIONS
#ifndef MEMALLOC_REGION
#define MEMALLOC_REGION 0
#endif
inline void* MemAlloc_Alloc(size_t nSize)
{
	return g_pMemAlloc->RegionAlloc(MEMALLOC_REGION, nSize);
}

inline void* MemAlloc_Alloc(size_t nSize, const char* pFileName, int nLine)
{
	return g_pMemAlloc->RegionAlloc(MEMALLOC_REGION, nSize, pFileName, nLine);
}
#else
#undef MEMALLOC_REGION
inline void* MemAlloc_Alloc(size_t nSize)
{
	return g_pMemAlloc->Alloc(nSize);
}

inline void* MemAlloc_Alloc(size_t nSize, const char* pFileName, int nLine)
{
	return g_pMemAlloc->Alloc(nSize, pFileName, nLine);
}
#endif
inline void MemAlloc_Free(void* ptr)
{
	g_pMemAlloc->Free(ptr);
}
inline void MemAlloc_Free(void* ptr, const char* pFileName, int nLine)
{
	g_pMemAlloc->Free(ptr, pFileName, nLine);
}

inline bool ValueIsPowerOfTwo(size_t value)			     
{
	return (value & (value - 1)) == 0;
}

inline void* MemAlloc_AllocAligned(size_t size, size_t align)
{
	unsigned char* pAlloc, * pResult;

	if (!IsPowerOfTwo(align))
		return NULL;

	align = (align > sizeof(void*) ? align : sizeof(void*)) - 1;

	if ((pAlloc = (unsigned char*)g_pMemAlloc->Alloc(sizeof(void*) + align + size)) == (unsigned char*)NULL)
		return NULL;

	pResult = (unsigned char*)((size_t)(pAlloc + sizeof(void*) + align) & ~align);
	((unsigned char**)(pResult))[-1] = pAlloc;

	return (void*)pResult;
}

inline void* MemAlloc_AllocAligned(size_t size, size_t align, const char* pszFile, int nLine)
{
	unsigned char* pAlloc, * pResult;

	if (!IsPowerOfTwo(align))
		return NULL;

	align = (align > sizeof(void*) ? align : sizeof(void*)) - 1;

	if ((pAlloc = (unsigned char*)g_pMemAlloc->Alloc(sizeof(void*) + align + size, pszFile, nLine)) == (unsigned char*)NULL)
		return NULL;

	pResult = (unsigned char*)((size_t)(pAlloc + sizeof(void*) + align) & ~align);
	((unsigned char**)(pResult))[-1] = pAlloc;

	return (void*)pResult;
}

inline void* MemAlloc_AllocAlignedUnattributed(size_t size, size_t align)
{
	unsigned char* pAlloc, * pResult;

	if (!ValueIsPowerOfTwo(align))
		return NULL;

	align = (align > sizeof(void*) ? align : sizeof(void*)) - 1;

	if ((pAlloc = (unsigned char*)MemAlloc_Alloc(sizeof(void*) + align + size)) == (unsigned char*)NULL)
		return NULL;

	pResult = (unsigned char*)((size_t)(pAlloc + sizeof(void*) + align) & ~align);
	((unsigned char**)(pResult))[-1] = pAlloc;

	return (void*)pResult;
}

inline void* MemAlloc_AllocAlignedFileLine(size_t size, size_t align, const char* pszFile, int nLine)
{
	unsigned char* pAlloc, * pResult;

	if (!ValueIsPowerOfTwo(align))
		return NULL;

	align = (align > sizeof(void*) ? align : sizeof(void*)) - 1;

	if ((pAlloc = (unsigned char*)MemAlloc_Alloc(sizeof(void*) + align + size, pszFile, nLine)) == (unsigned char*)NULL)
		return NULL;

	pResult = (unsigned char*)((size_t)(pAlloc + sizeof(void*) + align) & ~align);
	((unsigned char**)(pResult))[-1] = pAlloc;

	return (void*)pResult;
}

inline void* MemAlloc_ReallocAligned(void* ptr, size_t size, size_t align)
{
	if (!IsPowerOfTwo(align))
		return NULL;

	if (((size_t)ptr & (align - 1)) != 0)
		return NULL;

	if (!ptr)
		return MemAlloc_AllocAligned(size, align);

	void* pAlloc, * pResult;

	pAlloc = ptr;
	pAlloc = (void*)(((size_t)pAlloc & ~(sizeof(void*) - 1)) - sizeof(void*));
	pAlloc = *((void**)pAlloc);

	size_t nOffset = (size_t)ptr - (size_t)pAlloc;
	size_t nOldSize = g_pMemAlloc->GetSize(pAlloc);
	if (nOldSize >= size + nOffset)
		return ptr;

	pResult = MemAlloc_AllocAligned(size, align);
	memcpy(pResult, ptr, nOldSize - nOffset);
	g_pMemAlloc->Free(pAlloc);
	return pResult;
}

inline void MemAlloc_FreeAligned(void* pMemBlock)
{
	void* pAlloc;

	if (pMemBlock == NULL)
		return;

	pAlloc = pMemBlock;

	pAlloc = (void*)(((size_t)pAlloc & ~(sizeof(void*) - 1)) - sizeof(void*));

	pAlloc = *((void**)pAlloc);
	g_pMemAlloc->Free(pAlloc);
}

inline void MemAlloc_FreeAligned(void* pMemBlock, const char* pFileName, int nLine)
{
	void* pAlloc;

	if (pMemBlock == NULL)
		return;

	pAlloc = pMemBlock;

	pAlloc = (void*)(((size_t)pAlloc & ~(sizeof(void*) - 1)) - sizeof(void*));

	pAlloc = *((void**)pAlloc);
	g_pMemAlloc->Free(pAlloc, pFileName, nLine);
}

inline size_t MemAlloc_GetSizeAligned(void* pMemBlock)
{
	void* pAlloc;

	if (pMemBlock == NULL)
		return 0;

	pAlloc = pMemBlock;

	pAlloc = (void*)(((size_t)pAlloc & ~(sizeof(void*) - 1)) - sizeof(void*));

	pAlloc = *((void**)pAlloc);
	return g_pMemAlloc->GetSize(pAlloc) - ((byte*)pMemBlock - (byte*)pAlloc);
}

#if (defined(_DEBUG) || defined(USE_MEM_DEBUG))
#define MEM_ALLOC_CREDIT_(tag)	CMemAllocAttributeAlloction memAllocAttributeAlloction( tag, __LINE__ )
#define MemAlloc_PushAllocDbgInfo( pszFile, line ) g_pMemAlloc->PushAllocDbgInfo( pszFile, line )
#define MemAlloc_PopAllocDbgInfo() g_pMemAlloc->PopAllocDbgInfo()
#define MemAlloc_RegisterAllocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) g_pMemAlloc->RegisterAllocation( pFileName, nLine, nLogicalSize, nActualSize, nTime )
#define MemAlloc_RegisterDeallocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) g_pMemAlloc->RegisterDeallocation( pFileName, nLine, nLogicalSize, nActualSize, nTime )
#else
#define MEM_ALLOC_CREDIT_(tag)	((void)0)
#define MemAlloc_PushAllocDbgInfo( pszFile, line ) ((void)0)
#define MemAlloc_PopAllocDbgInfo() ((void)0)
#define MemAlloc_RegisterAllocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) ((void)0)
#define MemAlloc_RegisterDeallocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) ((void)0)
#endif

#define MemAlloc_DumpStats() g_pMemAlloc->DumpStats()
#define MemAlloc_CompactHeap() g_pMemAlloc->CompactHeap()
#define MemAlloc_OutOfMemory() g_pMemAlloc->OutOfMemory()
#define MemAlloc_CompactIncremental() g_pMemAlloc->CompactIncremental()
#define MemAlloc_DumpStatsFileBase( _filename ) g_pMemAlloc->DumpStatsFileBase( _filename )
#define MemAlloc_CrtCheckMemory() g_pMemAlloc->CrtCheckMemory()
#define MemAlloc_GlobalMemoryStatus( _usedMemory, _freeMemory ) g_pMemAlloc->GlobalMemoryStatus( _usedMemory, _freeMemory )
#define MemAlloc_MemoryAllocFailed() g_pMemAlloc->MemoryAllocFailed()

#define MemAlloc_GetDebugInfoSize() g_pMemAlloc->GetDebugInfoSize()
#define MemAlloc_SaveDebugInfo( pvDebugInfo ) g_pMemAlloc->SaveDebugInfo( pvDebugInfo )
#define MemAlloc_RestoreDebugInfo( pvDebugInfo ) g_pMemAlloc->RestoreDebugInfo( pvDebugInfo )
#define MemAlloc_InitDebugInfo( pvDebugInfo, pchRootFileName, nLine ) g_pMemAlloc->InitDebugInfo( pvDebugInfo, pchRootFileName, nLine )
#define MemAlloc_GetSize( x ) g_pMemAlloc->GetSize( x );
class CMemAllocAttributeAlloction
{
public:
	CMemAllocAttributeAlloction(const char* pszFile, int line)
	{
		MemAlloc_PushAllocDbgInfo(pszFile, line);
	}

	~CMemAllocAttributeAlloction()
	{
		MemAlloc_PopAllocDbgInfo();
	}
};

#define MEM_ALLOC_CREDIT()	MEM_ALLOC_CREDIT_(__FILE__)

#if defined(_WIN32) && ( defined(_DEBUG) || defined(USE_MEM_DEBUG) )

#pragma warning(disable:4290)
#pragma warning(push)
#include <typeinfo>

#if defined(_CPPRTTI) && defined(MEM_DEBUG_CLASSNAME)
#define MEM_ALLOC_CREDIT_CLASS()	MEM_ALLOC_CREDIT_( typeid(*this).name() )
#define MEM_ALLOC_CLASSNAME(type) (typeid((type*)(0)).name())
#else
#define MEM_ALLOC_CREDIT_CLASS()	MEM_ALLOC_CREDIT_( __FILE__ )
#define MEM_ALLOC_CLASSNAME(type) (__FILE__)
#endif

#ifdef _MSC_VER
#define MEM_ALLOC_CREDIT_FUNCTION()		MEM_ALLOC_CREDIT_( __FUNCTION__ )
#else
#define MEM_ALLOC_CREDIT_FUNCTION() (__FILE__)
#endif

#pragma warning(pop)
#else
#define MEM_ALLOC_CREDIT_CLASS()
#define MEM_ALLOC_CLASSNAME(type) NULL
#define MEM_ALLOC_CREDIT_FUNCTION() 
#endif

#if (defined(_DEBUG) || defined(USE_MEM_DEBUG))
struct MemAllocFileLine_t
{
	const char* pszFile;
	int line;
};

#define MEMALLOC_DEFINE_EXTERNAL_TRACKING( tag ) \
	static CUtlMap<void *, MemAllocFileLine_t, int> g_##tag##Allocs( DefLessFunc( void *) ); \
	static const char *g_psz##tag##Alloc = strcpy( (char *)g_pMemAlloc->Alloc( strlen( #tag "Alloc" ) + 1, "intentional leak", 0 ), #tag "Alloc" );

#define MemAlloc_RegisterExternalAllocation( tag, p, size ) \
	if ( !p ) \
		; \
	else \
	{ \
		MemAllocFileLine_t fileLine = { g_psz##tag##Alloc, 0 }; \
		g_pMemAlloc->GetActualDbgInfo( fileLine.pszFile, fileLine.line ); \
		if ( fileLine.pszFile != g_psz##tag##Alloc ) \
		{ \
			g_##tag##Allocs.Insert( p, fileLine ); \
		} \
		\
		MemAlloc_RegisterAllocation( fileLine.pszFile, fileLine.line, size, size, 0); \
	}

#define MemAlloc_RegisterExternalDeallocation( tag, p, size ) \
	if ( !p ) \
		; \
	else \
	{ \
		MemAllocFileLine_t fileLine = { g_psz##tag##Alloc, 0 }; \
		CUtlMap<void *, MemAllocFileLine_t, int>::IndexType_t iRecordedFileLine = g_##tag##Allocs.Find( p ); \
		if ( iRecordedFileLine !=  g_##tag##Allocs.InvalidIndex() ) \
		{ \
			fileLine = g_##tag##Allocs[iRecordedFileLine]; \
			g_##tag##Allocs.RemoveAt( iRecordedFileLine ); \
		} \
		\
		MemAlloc_RegisterDeallocation( fileLine.pszFile, fileLine.line, size, size, 0); \
	}

#else

#define MEMALLOC_DEFINE_EXTERNAL_TRACKING( tag )
#define MemAlloc_RegisterExternalAllocation( tag, p, size ) ((void)0)
#define MemAlloc_RegisterExternalDeallocation( tag, p, size ) ((void)0)

#endif

#elif defined( POSIX )

#if defined( OSX )
inline void* memalign(size_t alignment, size_t size) { void* pTmp = NULL; pTmp = malloc(size); return pTmp; }
#endif

inline void* _aligned_malloc(size_t nSize, size_t align) { return memalign(align, nSize); }
inline void _aligned_free(void* ptr) { free(ptr); }

inline void* MemAlloc_Alloc(size_t nSize, const char* pFileName = NULL, int nLine = 0) { return malloc(nSize); }
inline void MemAlloc_Free(void* ptr, const char* pFileName = NULL, int nLine = 0) { free(ptr); }

inline void* MemAlloc_AllocAligned(size_t size, size_t align, const char* pszFile = NULL, int nLine = 0) { return memalign(align, size); }
inline void* MemAlloc_AllocAlignedFileLine(size_t size, size_t align, const char* pszFile = NULL, int nLine = 0) { return memalign(align, size); }
inline void MemAlloc_FreeAligned(void* pMemBlock, const char* pszFile = NULL, int nLine = 0) { free(pMemBlock); }

#if defined( OSX )
inline size_t _msize(void* ptr) { return malloc_size(ptr); }
#else
inline size_t _msize(void* ptr) { return malloc_usable_size(ptr); }
#endif

inline void* MemAlloc_ReallocAligned(void* ptr, size_t size, size_t align)
{
	void* ptr_new_aligned = memalign(align, size);

	if (ptr_new_aligned)
	{
		size_t old_size = _msize(ptr);
		size_t copy_size = (size < old_size) ? size : old_size;

		memcpy(ptr_new_aligned, ptr, copy_size);
		free(ptr);
	}

	return ptr_new_aligned;
}
#else
#define MemAlloc_GetDebugInfoSize() g_pMemAlloc->GetDebugInfoSize()
#define MemAlloc_SaveDebugInfo( pvDebugInfo ) g_pMemAlloc->SaveDebugInfo( pvDebugInfo )
#define MemAlloc_RestoreDebugInfo( pvDebugInfo ) g_pMemAlloc->RestoreDebugInfo( pvDebugInfo )
#define MemAlloc_InitDebugInfo( pvDebugInfo, pchRootFileName, nLine ) g_pMemAlloc->InitDebugInfo( pvDebugInfo, pchRootFileName, nLine )

#endif    

#if !defined(STEAM) && defined(NO_MALLOC_OVERRIDE)

#define MEM_ALLOC_CREDIT_(tag)	((void)0)
#define MEM_ALLOC_CREDIT()	MEM_ALLOC_CREDIT_(__FILE__)
#define MEM_ALLOC_CREDIT_FUNCTION()
#define MEM_ALLOC_CREDIT_CLASS()
#define MEM_ALLOC_CLASSNAME(type) NULL

#define MemAlloc_PushAllocDbgInfo( pszFile, line )
#define MemAlloc_PopAllocDbgInfo()
#define MemAlloc_RegisterAllocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) ((void)0)
#define MemAlloc_RegisterDeallocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) ((void)0)
#define MemAlloc_DumpStats() ((void)0)
#define MemAlloc_CompactHeap() ((void)0)
#define MemAlloc_OutOfMemory() ((void)0)
#define MemAlloc_CompactIncremental() ((void)0)
#define MemAlloc_DumpStatsFileBase( _filename ) ((void)0)
inline bool MemAlloc_CrtCheckMemory() { return true; }
inline void MemAlloc_GlobalMemoryStatus(size_t* pusedMemory, size_t* pfreeMemory)
{
	*pusedMemory = 0;
	*pfreeMemory = 0;
}
#define MemAlloc_MemoryAllocFailed() 0

#define MemAlloc_GetDebugInfoSize() 0
#define MemAlloc_SaveDebugInfo( pvDebugInfo ) ((void)0)
#define MemAlloc_RestoreDebugInfo( pvDebugInfo ) ((void)0)
#define MemAlloc_InitDebugInfo( pvDebugInfo, pchRootFileName, nLine ) ((void)0)


#define MEMALLOC_DEFINE_EXTERNAL_TRACKING( tag )
#define MemAlloc_RegisterExternalAllocation( tag, p, size ) ((void)0)
#define MemAlloc_RegisterExternalDeallocation( tag, p, size ) ((void)0)

#endif    



#if defined( POSIX ) && !defined( NO_HOOK_MALLOC )
PLATFORM_INTERFACE void MemoryLogMessage(char const* s);						       
PLATFORM_INTERFACE void EnableMemoryLogging(bool bOnOff);
PLATFORM_INTERFACE void DumpMemoryLog(int nThresh);
PLATFORM_INTERFACE void DumpMemorySummary(void);
PLATFORM_INTERFACE void SetMemoryMark(void);
PLATFORM_INTERFACE void DumpChangedMemory(int nThresh);

#else
FORCEINLINE void MemoryLogMessage(char const* s)
{
}

FORCEINLINE void EnableMemoryLogging(bool bOnOff)
{
}
FORCEINLINE void DumpMemoryLog(int nThresh)
{
}
FORCEINLINE void DumpMemorySummary(void)
{
}
FORCEINLINE void SetMemoryMark(void)
{
}
FORCEINLINE void DumpChangedMemory(int nThresh)
{
}

#endif

#ifdef POSIX
PLATFORM_INTERFACE size_t ApproximateProcessMemoryUsage(void);
#else
FORCEINLINE size_t ApproximateProcessMemoryUsage(void)
{
	return 0;
}

#endif

struct aligned_tmp_t
{
};

template< int bytesAlignment = 16, class T = aligned_tmp_t >
class CAlignedNewDelete : public T
{

public:
	void* operator new(size_t nSize)
	{
		return MemAlloc_AllocAligned(nSize, bytesAlignment);
	}

	void* operator new(size_t nSize, int nBlockUse, const char* pFileName, int nLine)
	{
		return MemAlloc_AllocAlignedFileLine(nSize, bytesAlignment, pFileName, nLine);
	}

	void operator delete(void* pData)
	{
		if (pData)
		{
			MemAlloc_FreeAligned(pData);
		}
	}

	void operator delete(void* pData, int nBlockUse, const char* pFileName, int nLine)
	{
		if (pData)
		{
			MemAlloc_FreeAligned(pData, pFileName, nLine);
		}
	}
};


#endif   