// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT

#include <TestFramework.h>

#include <Utils/CustomMemoryHook.h>

#ifdef JPH_CUSTOM_MEMORY_HOOK_ENABLED

// Global to turn checking on/off
static bool sEnableCustomMemoryHook = false;

// Local to temporarily disable checking
static thread_local int sDisableCustomMemoryHook = 1;

// Local to keep track if we're coming through the custom allocator
static thread_local bool sInCustomAllocator = false;

// Struct to put on the stack to flag that we're in the custom memory allocator
struct InCustomAllocator
{
	InCustomAllocator()
	{
		JPH_ASSERT(!sInCustomAllocator);
		sInCustomAllocator = true;
	}

	~InCustomAllocator()
	{
		JPH_ASSERT(sInCustomAllocator);
		sInCustomAllocator = false;
	}
};

// Add a tag to an allocation to track if it is aligned / unaligned
static void *TagAllocation(void *inPointer, size_t inAlignment, char inMode)
{
	if (inPointer == nullptr)
		return nullptr;

	uint8 *p = reinterpret_cast<uint8 *>(inPointer);
	*p = inMode;
	return p + inAlignment;
}

// Remove tag from allocation
static void *UntagAllocation(void *inPointer, size_t inAlignment, char inMode)
{
	if (inPointer == nullptr)
		return nullptr;

	uint8 *p = reinterpret_cast<uint8 *>(inPointer) - inAlignment;
	JPH_ASSERT(*p == inMode);
	*p = 0;
	return p;
}

static void *AllocateHook(size_t inSize)
{
	JPH_ASSERT(inSize > 0);
	InCustomAllocator ica;
	return TagAllocation(malloc(inSize + 16), 16, 'U');
}

static void *ReallocateHook(void *inBlock, size_t inOldSize, size_t inNewSize)
{
	JPH_ASSERT(inNewSize > 0);
	InCustomAllocator ica;
	return TagAllocation(realloc(UntagAllocation(inBlock, 16, 'U'), inNewSize + 16), 16, 'U');
}

static void FreeHook(void *inBlock)
{
	InCustomAllocator ica;
	free(UntagAllocation(inBlock, 16, 'U'));
}

static void *AlignedAllocateHook(size_t inSize, size_t inAlignment)
{
	JPH_ASSERT(inSize > 0 && inAlignment > 0 && inAlignment <= 64);
	InCustomAllocator ica;
	return TagAllocation(_aligned_malloc(inSize + 64, inAlignment), 64, 'A');
}

static void AlignedFreeHook(void *inBlock)
{
	InCustomAllocator ica;
	_aligned_free(UntagAllocation(inBlock, 64, 'A'));
}

static int MyAllocHook(int nAllocType, void *pvData, size_t nSize, int nBlockUse, long lRequest, const unsigned char * szFileName, int nLine) noexcept
{
	JPH_ASSERT(!sEnableCustomMemoryHook || sDisableCustomMemoryHook <= 0 || sInCustomAllocator);
	return true;
}

void RegisterCustomMemoryHook()
{
	Allocate = AllocateHook;
	Reallocate = ReallocateHook;
	Free = FreeHook;
	AlignedAllocate = AlignedAllocateHook;
	AlignedFree = AlignedFreeHook;

	_CrtSetAllocHook(MyAllocHook);
}

void EnableCustomMemoryHook(bool inEnable)
{
	sEnableCustomMemoryHook = inEnable;
}

bool IsCustomMemoryHookEnabled()
{
	return sEnableCustomMemoryHook;
}

DisableCustomMemoryHook::DisableCustomMemoryHook()
{
	sDisableCustomMemoryHook--;
}

DisableCustomMemoryHook::~DisableCustomMemoryHook()
{
	sDisableCustomMemoryHook++;
}

#else

DisableCustomMemoryHook::DisableCustomMemoryHook()
{
}

DisableCustomMemoryHook::~DisableCustomMemoryHook()
{
}

#endif // JPH_CUSTOM_MEMORY_HOOK_ENABLED
