#pragma warning(disable:4996)
#define HEAPHOOK


//#define USE_CHARCONV

#include <stdio.h>

//#include <crtdefs.h>
//#include <malloc.h>
#include <stdlib.h>
#include <string.h>
//#include <intrin.h>

#ifdef USE_CHARCONV
#include <charconv>
#endif

//#define OBJECT_GUARD_END_SIZE 8
#define OBJECT_GUARD_END_SIZE 0

//#define BF_USE_STOMP_ALLOC 1

//extern "C"
//{
//#include "gperftools/stacktrace.h"
//}

#ifdef _MSC_VER
#include <intrin.h>
#pragma intrinsic(_ReturnAddress)
#define BF_RETURN_ADDRESS _ReturnAddress()
#else
#define BF_RETURN_ADDRESS __builtin_return_address(0)
#endif

#include "BeefySysLib/Common.h"
#include "BfObjects.h"
//#include "gc.h"
#include "StompAlloc.h"
#include "BeefySysLib/platform/PlatformHelper.h"
#ifndef BF_DISABLE_FFI
#include "ffi.h"
#endif
#include "Thread.h"

#ifdef BF_PLATFORM_WINDOWS
#include <fcntl.h>
#include <io.h>
#endif

USING_NS_BF;

static Beefy::StringT<0> gCmdLineString;
bool gCmdLineStringHandled;
bf::System::Runtime::BfRtCallbacks gBfRtCallbacks;
BfRtFlags gBfRtFlags = (BfRtFlags)0;

#ifdef BF_PLATFORM_WINDOWS
DWORD gBfTLSKey = 0;
#else
pthread_key_t gBfTLSKey = 0;
#endif

static int gTestMethodIdx = -1;
static uint32 gTestStartTick = 0;
static bool gTestBreakOnFailure = false;

typedef void(*ClientPipeErrorFunc)(const Beefy::StringView& error, int stackOffset);

static BfpFile* gClientPipe = NULL;
static ClientPipeErrorFunc gClientPipeErrorFunc;
static Beefy::String gTestInBuffer;

namespace bf
{
	namespace System
	{
		class Object;
		class Exception;

		//System::Threading::Thread* gMainThread;

		class Internal
		{
		private:
			BFRT_EXPORT static void __BfStaticCtor();
			BFRT_EXPORT static void __BfStaticDtor();

			BFRT_EXPORT static void BfStaticCtor();
			BFRT_EXPORT static void BfStaticDtor();
			BFRT_EXPORT static void Shutdown_Internal();
		public:
			BFRT_EXPORT static Object* UnsafeCastToObject(void* inPtr);
			BFRT_EXPORT static void* UnsafeCastToPtr(Object* obj);
			BFRT_EXPORT static void ObjectDynCheck(Object* object, int typeId, bool allowNull);
			BFRT_EXPORT static void ObjectDynCheckFailed(Object* object, int typeId);
			BFRT_EXPORT static void ThrowIndexOutOfRange(intptr stackOffset);
			BFRT_EXPORT static void ThrowObjectNotInitialized(intptr stackOffset);
			BFRT_EXPORT static void FatalError(String* error, intptr stackOffset = 0);
			BFRT_EXPORT static void MemCpy(void* dest, void* src, intptr length);
			BFRT_EXPORT static void MemMove(void* dest, void* src, intptr length);
			BFRT_EXPORT static void MemSet(void* addr, uint8 val, intptr length);
			BFRT_EXPORT static int CStrLen(char* charPtr);
			BFRT_EXPORT static void* Malloc(intptr length);
			BFRT_EXPORT static void Free(void* ptr);
			BFRT_EXPORT static void* VirtualAlloc(intptr size, bool canExecute, bool canWrite);
			BFRT_EXPORT static int64 GetTickCountMicro();
			BFRT_EXPORT static void BfDelegateTargetCheck(void* target);
			BFRT_EXPORT static void* LoadSharedLibrary(char* filePath);
			BFRT_EXPORT static void LoadSharedLibraryInto(char* filePath, void** libDest);
			BFRT_EXPORT static void* GetSharedProcAddress(void* libHandle, char* procName);
			BFRT_EXPORT static void GetSharedProcAddressInto(void* libHandle, char* procName, void** procDest);
			BFRT_EXPORT static char* GetCommandLineArgs();
			BFRT_EXPORT static void BfLog(char* str);
			BFRT_EXPORT static void ProfilerCmd(char* str);
			BFRT_EXPORT static void ReportMemory();

		private:
			BFRT_EXPORT static void Test_Init(char* testData);
			BFRT_EXPORT static void Test_Error(char* error);
			BFRT_EXPORT static void Test_Write(char* str);
			BFRT_EXPORT static int32 Test_Query();
			BFRT_EXPORT static void Test_Finish();
		};

		namespace IO
		{
			class File
			{
			private:
				BFRT_EXPORT static bool Exists(char* fileName);
			};

			class Directory
			{
			private:
				BFRT_EXPORT static bool Exists(char* fileName);
			};
		}

		namespace Diagnostics
		{
			namespace Contracts
			{
				class Contract
				{
				public:
					enum ContractFailureKind : uint8
					{
						ContractFailureKind_Precondition,
						//[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Postcondition")]
						ContractFailureKind_Postcondition,
						//[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Postcondition")]
						ContractFailureKind_PostconditionOnException,
						ContractFailureKind_Invariant,
						ContractFailureKind_Assert,
						ContractFailureKind_Assume,
					};

				private:
					BFRT_EXPORT static void ReportFailure(ContractFailureKind failureKind, char* userMessage, int userMessageLen, char* conditionText, int conditionTextLen);
				};
			}

			class Debug
			{
			private:
				BFRT_EXPORT static void Write(char* str, intptr strLen);
			};
		}

		namespace FFI
		{
			enum FFIABI : int32;
			enum FFIResult : int32;

			struct FFIType;

			struct FFILIB
			{
				struct FFICIF;

				BFRT_EXPORT static void* ClosureAlloc(intptr size, void** outFunc);
				BFRT_EXPORT static FFIResult PrepCif(FFICIF* cif, FFIABI abi, int32 nargs, FFIType* rtype, FFIType** argTypes);
				BFRT_EXPORT static void Call(FFICIF* cif, void* funcPtr, void* rvalue, void** args);
			};
		}

		struct Float
		{
		private:
			BFRT_EXPORT static int ToString(float f, char* outStr, bool roundTrip);
		};

		struct Double
		{
		private:
			BFRT_EXPORT static int ToString(double f, char* outStr, bool roundTrip);
		};
	}
}

//#define BF_TRACK_SIZES 1

#if BF_TRACK_SIZES
static int sAllocSizes[1024*1024];
static int sHighestId = 0;
#endif

using namespace bf::System;

#ifndef BF_PLATFORM_WINDOWS

bool IsDebuggerPresent()
{
    return false;
}


#endif

static void TestString(const StringImpl& str);
static void TestReadCmd(Beefy::String& str);

static void Internal_FatalError(const char* error)
{
	if (gBfRtCallbacks.CheckErrorHandler != NULL)
		gBfRtCallbacks.CheckErrorHandler("FatalError", error, NULL, 0);

	if (gClientPipeErrorFunc != NULL)
		gClientPipeErrorFunc(error, 0);
	else
		BfpSystem_FatalError(error, "BEEF FATAL ERROR");
}

extern "C" BFRT_EXPORT int BF_CALLTYPE ftoa(float val, char* str)
{
	return sprintf(str, "%1.9f", val);
}

/*static void* MallocHook(size_t size, const void *caller)
{
	printf("MallocHook\n");
	return NULL;
}*/


/*static int __cdecl HeapHook(int a, size_t b, void* c, void** d)
{
	printf("Heap Hook\n");
	return 0;
}*/

//////////////////////////////////////////////////////////////////////////

//static Beefy::StringT<0> gErrorString;

static const char* volatile gErrorString = NULL;

void SetErrorString(const char* str)
{
	char* newStr = strdup(str);
	while (true)
	{
		const char* prevStr = gErrorString;
		auto result = (void*)BfpSystem_InterlockedCompareExchangePtr((uintptr*)&gErrorString, (uintptr)prevStr, (uintptr)newStr);
		if (result != prevStr)
			continue;
		if (prevStr != NULL)
			free((void*)prevStr);
		break;
	}
}

#define SETUP_ERROR(str, skip) SetErrorString(str); gBfRtCallbacks.DebugMessageData_SetupError(str, skip)

static void GetCrashInfo()
{
	auto errorString = gErrorString;
	if (errorString != NULL)
	{
		Beefy::String debugStr;
		debugStr += "Beef Error: ";
		debugStr += (const char*)errorString;
		BfpSystem_AddCrashInfo(debugStr.c_str());
	}
}

static void NTAPI TlsFreeFunc(void* ptr)
{
	gBfRtCallbacks.Thread_Exiting();
}

void bf::System::Console::PutChars(char* ptr, int len)
{
#ifdef BF_PLATFORM_WINDOWS
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	if (handle != INVALID_HANDLE_VALUE)
	{
		DWORD numBytesWritten = 0;
		::WriteFile(handle, ptr, (DWORD)len, &numBytesWritten, NULL);
		return;
	}
#endif

	for (int i = 0; i < len; i++)
		putchar(ptr[i]);
	fflush(stdout);
}

void bf::System::Console::ReopenHandles()
{
#ifdef _MSC_VER	
 	FILE* fDummy;
 	freopen_s(&fDummy, "CONOUT$", "w", stdout);
	freopen_s(&fDummy, "CONOUT$", "w", stderr);
	freopen_s(&fDummy, "CONIN$", "r", stdin);	
// 
// 	// std::wcout, std::wclog, std::wcerr, std::wcin
 	HANDLE hConOut = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 	HANDLE hConIn = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 	SetStdHandle(STD_OUTPUT_HANDLE, hConOut);
 	SetStdHandle(STD_ERROR_HANDLE, hConOut);
 	SetStdHandle(STD_INPUT_HANDLE, hConIn);	
#endif
}

void bf::System::Runtime::Init(int version, int flags, BfRtCallbacks* callbacks)
{
	BfpSystemInitFlags sysInitFlags = BfpSystemInitFlag_InstallCrashCatcher;
	if ((flags & 4) != 0)
		sysInitFlags = (BfpSystemInitFlags)(sysInitFlags | BfpSystemInitFlag_SilentCrash);
	BfpSystem_Init(BFP_VERSION, sysInitFlags);

	if (gBfRtCallbacks.Alloc != NULL)
	{
		Internal_FatalError("BeefRT already initialized. Multiple executable modules in the same process cannot dynamically link to the Beef runtime.");
	}

	if (version != BFRT_VERSION)
	{
		char error[256];
		sprintf(error, "BeefRT build version '%d' does not match requested version '%d'", BFRT_VERSION, version);
        BfpSystem_FatalError(error, "BEEF FATAL ERROR");
	}

	gBfRtCallbacks = *callbacks;
	gBfRtFlags = (BfRtFlags)flags;

#ifdef BF_PLATFORM_WINDOWS
	gBfTLSKey = FlsAlloc(TlsFreeFunc);
#else
	pthread_key_create(&gBfTLSKey, TlsFreeFunc);
#endif
}

void bf::System::Runtime::InitCrashCatcher(int flags)
{
	BfpSystemInitFlags sysInitFlags = BfpSystemInitFlag_InstallCrashCatcher;
	if ((flags & 4) != 0)
		sysInitFlags = (BfpSystemInitFlags)(sysInitFlags | BfpSystemInitFlag_SilentCrash);
	BfpSystem_InitCrashCatcher(sysInitFlags);
	BfpSystem_AddCrashInfoFunc(GetCrashInfo);
}

void bf::System::Runtime::ShutdownCrashCatcher()
{
	BfpSystem_ShutdownCrashCatcher();
}

void bf::System::Runtime::SetErrorString(char* errorStr)
{
	::SetErrorString(errorStr);
}

void bf::System::Runtime::AddCrashInfoFunc(void* func)
{
	BfpSystem_AddCrashInfoFunc(*(BfpCrashInfoFunc*)&func);
}

void bf::System::Runtime::SetCrashReportKind(bf::System::Runtime::RtCrashReportKind crashReportKind)
{
	BfpSystem_SetCrashReportKind((BfpCrashReportKind)crashReportKind);
}

//////////////////////////////////////////////////////////////////////////

void Internal::Shutdown_Internal()
{
	BfInternalThread::WaitForAllDone();
	if (gBfRtCallbacks.GC_Shutdown != NULL)
		gBfRtCallbacks.GC_Shutdown();
	BfpSystem_Shutdown();
}

void Internal::BfStaticCtor()
{
	__BfStaticCtor();
}

void Internal::__BfStaticCtor()
{

}

void Internal::BfStaticDtor()
{

}

void Internal::__BfStaticDtor()
{

}

Object* Internal::UnsafeCastToObject(void* inPtr)
{
	return (Object*)inPtr;
}

void* Internal::UnsafeCastToPtr(Object* obj)
{
	return (void*)obj;
}

void Internal::ThrowIndexOutOfRange(intptr stackOffset)
{
	if (gClientPipeErrorFunc != NULL)
		gClientPipeErrorFunc("Index out of range", 0);
	else if ((stackOffset != -1) && (::IsDebuggerPresent()))
	{
		SETUP_ERROR("Index out of range", (int)(2 + stackOffset));
		BF_DEBUG_BREAK();
	}

	Internal_FatalError("Index out of range");
}

void Internal::ThrowObjectNotInitialized(intptr stackOffset)
{
	if (gClientPipeErrorFunc != NULL)
		gClientPipeErrorFunc("Object not initialized", 0);
	else if ((stackOffset != -1) && (::IsDebuggerPresent()))
	{
		SETUP_ERROR("Object not initialized", (int)(2 + stackOffset));
		BF_DEBUG_BREAK();
	}

	Internal_FatalError("Object not initialized");
}

void Internal::FatalError(bf::System::String* error, intptr stackOffset)
{
	Beefy::StringView errorStringView = error->ToStringView();
	Beefy::StringSimple errorString = errorStringView;
	if (gClientPipeErrorFunc != NULL)
		gClientPipeErrorFunc(errorStringView, 0);
	if ((stackOffset != -1) && (::IsDebuggerPresent()))
	{
		SETUP_ERROR(errorString.c_str(), (int)(2 + stackOffset));
		BF_DEBUG_BREAK();
	}

    Internal_FatalError(errorString.c_str());
}

void Internal::MemCpy(void* dest, void* src, intptr length)
{
	memcpy(dest, src, length);
}

void Internal::MemMove(void* dest, void* src, intptr length)
{
	memmove(dest, src, length);
}

void Internal::MemSet(void* addr, uint8 val, intptr length)
{
	memset(addr, val, length);
}

int Internal::CStrLen(char* charPtr)
{
	return (int)strlen(charPtr);
}

void* Internal::Malloc(intptr length)
{
#if BF_USE_STOMP_ALLOC
	return StompAlloc(length);
#elif BF_TRACK_SIZES
	uint8* allocPtr = (uint8*)malloc(length + 16);
	*((int*)allocPtr) = length;
	sAllocSizes[0] += length;
	return allocPtr + 16;
#else
	return malloc(length);
#endif
}

void* Internal::VirtualAlloc(intptr size, bool canExecute, bool canWrite)
{
#ifdef BF_PLATFORM_WINDOWS
    OutputDebugStrF("Performing VirtualAlloc: %d %d %d\n", size, canExecute, canWrite);
    int prot = PAGE_READWRITE;
    if (canExecute && canWrite)
        prot = PAGE_EXECUTE_READWRITE;
    else if (canExecute)
        prot = PAGE_EXECUTE_READ;
    void* ptr = ::VirtualAlloc(NULL, size, MEM_RESERVE, prot);
    return ptr;
#else
    BF_FATAL("Not supported");
    return NULL;
#endif
}

void Internal::Free(void* ptr)
{
#if BF_USE_STOMP_ALLOC
	StompFree(ptr);
#elif BF_TRACK_SIZES
	uint8* allocPtr = ((uint8*)ptr) - 16;
	sAllocSizes[0] -= *((int*)allocPtr);
	free(allocPtr);
#else
	free(ptr);
#endif
}

BFRT_EXPORT int64 Internal::GetTickCountMicro()
{
	return BFGetTickCountMicro();
}

void Internal::BfDelegateTargetCheck(void* target)
{
	if (target != NULL)
	{
		SETUP_ERROR("Attempting pass non-static method reference to extern method", 2);
		BF_DEBUG_BREAK();
		gBfRtCallbacks.DebugMessageData_Fatal();
	}
}

void* Internal::LoadSharedLibrary(char* libName)
{
	//::MessageBox(NULL, "Hey", "Dude", 0);
    void* libHandle = BfpDynLib_Load(libName);
	if (libHandle == NULL)
	{
		if (gBfRtCallbacks.CheckErrorHandler != NULL)
		{
			if (gBfRtCallbacks.CheckErrorHandler("LoadSharedLibrary", libName, NULL, 0) == 1)
				return NULL;
		}

		Beefy::String errorStr = StrFormat("Failed to load shared library: %s", libName);
		SETUP_ERROR(errorStr.c_str(), 1);
		BF_DEBUG_BREAK();
		gBfRtCallbacks.DebugMessageData_Fatal();
	}
	return libHandle;
}

void Internal::LoadSharedLibraryInto(char* libName, void** libDest)
{
	if (*libDest == NULL)
		*libDest = LoadSharedLibrary(libName);
}

void* Internal::GetSharedProcAddress(void* libHandle, char* procName)
{
	if (libHandle == NULL)
		return NULL;

    void* procAddr = BfpDynLib_GetProcAddress((BfpDynLib*)libHandle, procName);
	if (procAddr == NULL)
	{
        char libFileName[4096];
        int libFileNameLen = 4096;
        BfpDynLib_GetFilePath((BfpDynLib*)libHandle, libFileName, &libFileNameLen, NULL);

		if (gBfRtCallbacks.CheckErrorHandler != NULL)
		{
			if (gBfRtCallbacks.CheckErrorHandler("GetSharedProcAddress", libFileName, procName, 0) == 1)
				return NULL;
		}

		Beefy::String errorStr = StrFormat("Failed to load shared procedure '%s' from '%s'", procName, libFileName);
		SETUP_ERROR(errorStr.c_str(), 1);
		BF_DEBUG_BREAK();
		gBfRtCallbacks.DebugMessageData_Fatal();
	}
	return procAddr;
}

void Internal::GetSharedProcAddressInto(void* libHandle, char* procName, void** procDest)
{
	*procDest = GetSharedProcAddress(libHandle, procName);
}

char* Internal::GetCommandLineArgs()
{
	if (!gCmdLineStringHandled)
	{
		Beefy::String cmdLine;

		BfpSystemResult result;
		BFP_GETSTR_HELPER(cmdLine, result, BfpSystem_GetCommandLine(__STR, __STRLEN, &result));

		char* cmdLineStr = (char*)cmdLine.c_str();

		//::MessageBoxA(NULL, cmdLineStr, "BFRT", 0);

		char* useCmdLineStr = cmdLineStr;

		if (cmdLineStr[0] != 0)
		{
			bool nameQuoted = cmdLineStr[0] == '\"';

			Beefy::String passedName;
			int i;
			for (i = (nameQuoted ? 1 : 0); cmdLineStr[i] != 0; i++)
			{
				wchar_t c = cmdLineStr[i];

				if (((nameQuoted) && (c == '"')) ||
					((!nameQuoted) && (c == ' ')))
				{
					i++;
					break;
				}
				passedName += cmdLineStr[i];
			}

			useCmdLineStr += i;
			while (*useCmdLineStr == L' ')
				useCmdLineStr++;
		}
		gCmdLineString = useCmdLineStr;
		gCmdLineStringHandled = true;
	}

	return (char*)gCmdLineString.c_str();
}

void Internal::BfLog(char* str)
{
// 	static int lineNum = 0;
// 	lineNum++;
//
// 	static FILE* fp = fopen("dbg_internal.txt", "wb");
//
// 	Beefy::String aResult = StrFormat("%d ", lineNum) + str;
// 	fwrite(aResult.c_str(), 1, aResult.length(), fp);
// 	fflush(fp);
}

void Internal::ProfilerCmd(char* str)
{
	if (!::IsDebuggerPresent())
		return;

	gBfRtCallbacks.DebugMessageData_SetupProfilerCmd(str);
	BF_DEBUG_BREAK();
}

void Internal::ReportMemory()
{
	int totalMem = 0;
#if BF_TRACK_SIZES
	for (int i = 0; i <= sHighestId; i++)
		totalMem += sAllocSizes[i];
	OutputDebugStrF("Beef Object Memory: %dk\n", totalMem / 1024);
#endif
}

static void TestString(const StringImpl& str)
{
	BfpFileResult fileResult;
	BfpFile_Write(gClientPipe, str.c_str(), str.length(), -1, &fileResult);
	BF_ASSERT_REL(fileResult == BfpFileResult_Ok);
}

static void TestReadCmd(Beefy::String& str)
{
	while (true)
	{
		int crPos = (int)gTestInBuffer.IndexOf('\n');
		if (crPos != -1)
		{
			str = gTestInBuffer.Substring(0, crPos);
			gTestInBuffer.Remove(0, crPos + 1);
			return;
		}

		char data[1024];
		BfpFileResult fileResult;
		int readSize = (int)BfpFile_Read(gClientPipe, data, 1024, -1, &fileResult);
		if ((fileResult == BfpFileResult_Ok) || (fileResult == BfpFileResult_PartialData))
		{
			gTestInBuffer.Append(data, readSize);
		}
		else
		{
			BF_FATAL("Failed to read pipe to test manager");
		}
	}
}

void TestFailed(const Beefy::StringView& error, int stackOffset)
{
	if (gClientPipe != NULL)
	{
		Beefy::StringSimple errorString = error;

		if (gTestBreakOnFailure)
		{
			SETUP_ERROR(errorString.c_str(), (int)(2 + stackOffset));
			BF_DEBUG_BREAK();
		}

		Beefy::String str = ":TestFail\t";
		str += errorString.c_str();
		str.Replace('\n', '\r');
		str += "\n";
		TestString(str);
		exit(1);
	}
}

void Internal::Test_Init(char* testData)
{
	BfpSystem_SetCrashReportKind(BfpCrashReportKind_None);

	Beefy::String args = GetCommandLineArgs();

	BfpFileResult fileResult;
	gClientPipe = BfpFile_Create(args.c_str(), BfpFileCreateKind_OpenExisting, (BfpFileCreateFlags)(BfpFileCreateFlag_Read | BfpFileCreateFlag_Write | BfpFileCreateFlag_Pipe), BfpFileAttribute_None, &fileResult);
	if (fileResult != BfpFileResult_Ok)
		BF_FATAL("Test_Init failed to create pipe to test manager");

	gClientPipeErrorFunc = TestFailed;

	Beefy::String outStr;
	outStr += ":TestInit\n";
	outStr += testData;
	outStr += "\n";
	outStr += ":TestBegin\n";
	TestString(outStr);
}

void Internal::Test_Error(char* error)
{
	if (gTestBreakOnFailure)
	{
		SETUP_ERROR(error, 3);
		BF_DEBUG_BREAK();
	}

	if (gClientPipe != NULL)
	{
		Beefy::String str = ":TestFail\t";
		str += error;
		str.Replace('\n', '\r');
		str += "\n";
		TestString(str);
	}
	else
		Internal_FatalError(error);
}

void Internal::Test_Write(char* strPtr)
{
	if (gClientPipe != NULL)
	{
		Beefy::String str = ":TestWrite\t";
		str += strPtr;
		str.Replace('\n', '\r');
		str += "\n";
		TestString(str);
	}
}

int32 Internal::Test_Query()
{
	if (gTestMethodIdx != -1)
	{
		uint32 tickEnd = BfpSystem_TickCount();
		TestString(StrFormat(":TestResult\t%d\n", tickEnd - gTestStartTick));
	}

	TestString(":TestQuery\n");

	Beefy::String result;
	TestReadCmd(result);

	Beefy::String param;
	int tabPos = (int)result.IndexOf('\t');
	if (tabPos != -1)
	{
		param = result.Substring(tabPos + 1);
		result.RemoveToEnd(tabPos);
	}

	if (result == ":TestRun")
	{
		gTestStartTick = BfpSystem_TickCount();
		Beefy::String options;
		int tabPos = (int)param.IndexOf('\t');
		if (tabPos != -1)
		{
			options = param.Substring(tabPos + 1);
			param.RemoveToEnd(tabPos);
		}

		gTestMethodIdx = atoi(param.c_str());
		gTestBreakOnFailure = options.Contains("FailBreak");
		return gTestMethodIdx;
	}
	else if (result == ":TestFinish")
	{
		return -1;
	}
	else
	{
		printf("Command Str: %s\n", result.c_str());
		BF_FATAL("Invalid test command string from test manager");
	}

	return false;
}

void Internal::Test_Finish()
{
	TestString(":TestFinish\n");

	if (gClientPipe != NULL)
	{
		BfpFile_Release(gClientPipe);
		gClientPipe = NULL;
	}
}

///

int GetStackTrace(void **result, int max_depth, int skip_count);

void BfLog(const char* fmt ...);

static const int cMaxStackTraceCount = 1024;
struct PendingAllocState
{
	bool mHasData;
	void* mStackTrace[cMaxStackTraceCount];
	int mStackTraceCount;
	int mMetadataBytes;

	bool IsSmall(intptr curAllocBytes)
	{
		if ((mStackTraceCount > 255) || (mMetadataBytes > 255))
			return false;

		const intptr maxSmallObjectSize = ((intptr)1 << ((sizeof(intptr) - 2) * 8)) - 1;
		if (curAllocBytes <= maxSmallObjectSize)
			return true;

		intptr objBytes = curAllocBytes - mStackTraceCount*sizeof(intptr) - mMetadataBytes;
		return (objBytes < maxSmallObjectSize);
	}
};

void Internal::ObjectDynCheck(bf::System::Object* object, int typeId, bool allowNull)
{
	return;
	if (object == NULL)
	{
		if (allowNull)
			return;
		SETUP_ERROR("Attempting unboxing on null object", 1);
		BF_DEBUG_BREAK();
		gBfRtCallbacks.DebugMessageData_Fatal();
		return;
	}

	auto result = gBfRtCallbacks.Object_DynamicCastToTypeId(object, typeId);
	if (result == NULL)
	{
		Beefy::String errorStr = "Attempting invalid cast on object";
		errorStr += StrFormat("\x1LEAK\t0x%@\n   (%s)0x%@\n", object, "System.Object", object);
		SETUP_ERROR(errorStr.c_str(), 2);
		BF_DEBUG_BREAK();
		gBfRtCallbacks.DebugMessageData_Fatal();
	}
}

void Internal::ObjectDynCheckFailed(bf::System::Object* object, int typeId)
{
	if (object == NULL)
	{
		SETUP_ERROR("Attempting unboxing on null object", 1);
		BF_DEBUG_BREAK();
		gBfRtCallbacks.DebugMessageData_Fatal();
		return;
	}

	Beefy::String errorStr = "Attempting invalid cast on object";

	errorStr += StrFormat("\x1LEAK\t0x%@\n   (%s)0x%@\n", object, "System.Object", object);
	SETUP_ERROR(errorStr.c_str(), 2);
	BF_DEBUG_BREAK();
	gBfRtCallbacks.DebugMessageData_Fatal();
}

extern "C" BFRT_EXPORT int PrintF(const char* fmt, ...)
{
	int ret;
	/* Declare a va_list type variable */
	va_list myargs;
	/* Initialise the va_list variable with the ... after fmt */
	va_start(myargs, fmt);
	/* Forward the '...' to vprintf */
	ret = vprintf(fmt, myargs);
	/* Clean up the va_list */
	va_end(myargs);

	return ret;
}

///

using namespace bf::System::Diagnostics::Contracts;

void Contract::ReportFailure(Contract::ContractFailureKind failureKind, char* userMessage, int userMessageLen, char* conditionText, int conditionTextLen)
{
	Beefy::String userMessageStr;
	if (userMessageLen > 0)
		userMessageStr.Reference(userMessage, userMessageLen);
	Beefy::String conditionTextStr;
	if (conditionTextLen > 0)
		conditionTextStr.Reference(conditionText, conditionTextLen);

    Beefy::String errorMsg = "Contract";
    if (failureKind == Contract::ContractFailureKind_Assert)
    	errorMsg += ": Assert failed";
    if (userMessage != NULL)
        errorMsg += Beefy::String(": ") + userMessageStr;
    if (conditionText != NULL)
        errorMsg += Beefy::String(": ") + conditionTextStr;

	if (::IsDebuggerPresent())
	{
		SETUP_ERROR(errorMsg.c_str(), 3);
		BF_DEBUG_BREAK();
		gBfRtCallbacks.DebugMessageData_Fatal();
	}

	Internal_FatalError(errorMsg.c_str());

	return;
}

void bf::System::Diagnostics::Debug::Write(char* str, intptr strLen)
{
	Beefy::String strVal(str, strLen);
	OutputDebugStr(strVal);
}

//////////////////////////////////////////////////////////////////////////

bool IO::File::Exists(char* fileName)
{
    return BfpFile_Exists(fileName);
}

bool IO::Directory::Exists(char* fileName)
{
    return BfpDirectory_Exists(fileName);
}

//////////////////////////////////////////////////////////////////////////

void* bf::System::FFI::FFILIB::ClosureAlloc(intptr size, void** outFunc)
{
#ifndef BF_DISABLE_FFI
	return ffi_closure_alloc(size, outFunc);
#else
	return NULL;
#endif
}

bf::System::FFI::FFIResult bf::System::FFI::FFILIB::PrepCif(bf::System::FFI::FFILIB::FFICIF* cif, bf::System::FFI::FFIABI abi, int32 nargs, bf::System::FFI::FFIType* rtype, bf::System::FFI::FFIType** argTypes)
{
#ifndef BF_DISABLE_FFI
	return (bf::System::FFI::FFIResult)ffi_prep_cif((ffi_cif*)cif, (ffi_abi)abi, nargs, (ffi_type*)rtype, (ffi_type**)argTypes);
#else
	return (bf::System::FFI::FFIResult)0;
#endif
}

void bf::System::FFI::FFILIB::Call(bf::System::FFI::FFILIB::FFICIF* cif, void* funcPtr, void* rvalue, void** args)
{
#ifndef BF_DISABLE_FFI
	ffi_call((ffi_cif*)cif, (void(*)())funcPtr, rvalue, args);
#endif
}

//////////////////////////////////////////////////////////////////////////

static int ToString(float d, char* outStr, bool roundTrip)
{
	if (!roundTrip)
	{
		int digits;
		if (d > 100000)
			digits = 1;
		else if (d > 10000)
			digits = 2;
		else if (d > 1000)
			digits = 3;
		else if (d > 100)
			digits = 4;
		else if (d > 10)
			digits = 5;
		else
			digits = 6;

		sprintf(outStr, "%1.*f", digits, d);
	}
	else
		sprintf(outStr, "%1.9g", d);

	int len = (int)strlen(outStr);
	for (int i = 0; outStr[i] != 0; i++)
	{
		if (outStr[i] == '.')
		{
			int checkC = len - 1;
			while (true)
			{
				char c = outStr[checkC];
				if (c == '.')
				{
					return checkC;
				}
				else if (c == 'e')
				{
					return len;
				}
				else if (c != '0')
				{
					for (int j = i + 1; j <= checkC; j++)
						if (outStr[j] == 'e')
							return len;
					return checkC + 1;
				}
				checkC--;
			}
		}
	}
	if ((len == 3) && (outStr[0] == 'i'))
	{
		strcpy(outStr, "Infinity");
		return 8;
	}
	if ((len == 4) && (outStr[0] == '-') && (outStr[1] == 'i'))
	{
		strcpy(outStr, "-Infinity");
		return 9;
	}
	if ((len == 9) && (outStr[0] == '-') && (outStr[1] == 'n')) //-nan(xxx)
	{
		strcpy(outStr, "NaN");
		return 3;
	}
	return len;
}

static int ToString(double d, char* outStr, bool roundTrip)
{
	if (!roundTrip)
	{
		int digits;
		if (d < 0)
		{
			if (d < -10000000000)
			{
				sprintf(outStr, "%g", d);
			}
			else
			{
				if (d < -1000000000)
					digits = 1;
				else if (d < -100000000)
					digits = 2;
				else if (d < -10000000)
					digits = 3;
				else if (d < -1000000)
					digits = 4;
				else if (d < -100000)
					digits = 5;
				else if (d < -10000)
					digits = 6;
				else if (d < -1000)
					digits = 7;
				else if (d < -100)
					digits = 8;
				else if (d < -10)
					digits = 9;
				else
					digits = 10;

				sprintf(outStr, "%1.*f", digits, d);
			}
		}
		else
		{
			if (d > 10000000000)
			{
				sprintf(outStr, "%g", d);
			}
			else
			{
				if (d > 1000000000)
					digits = 1;
				else if (d > 100000000)
					digits = 2;
				else if (d > 10000000)
					digits = 3;
				else if (d > 1000000)
					digits = 4;
				else if (d > 100000)
					digits = 5;
				else if (d > 10000)
					digits = 6;
				else if (d > 1000)
					digits = 7;
				else if (d > 100)
					digits = 8;
				else if (d > 10)
					digits = 9;
				else
					digits = 10;

				sprintf(outStr, "%1.*f", digits, d);
			}
		}
	}
	else
		sprintf(outStr, "%1.17g", d);

	int len = (int)strlen(outStr);
	for (int i = 0; outStr[i] != 0; i++)
	{
		if (outStr[i] == '.')
		{
			int checkC = len - 1;
			while (true)
			{
				char c = outStr[checkC];
				if (c == '.')
				{
					return checkC;
				}
				else if (c == 'e')
				{
					return len;
				}
				else if (c != '0')
				{
					for (int j = i + 1; j <= checkC; j++)
						if (outStr[j] == 'e')
							return len;
					return checkC + 1;
				}
				checkC--;
			}
		}
	}
	if ((len == 3) && (outStr[0] == 'i'))
	{
		strcpy(outStr, "Infinity");
		return 8;
	}
	if ((len == 4) && (outStr[0] == '-') && (outStr[1] == 'i'))
	{
		strcpy(outStr, "-Infinity");
		return 9;
	}
	if ((len == 9) && (outStr[0] == '-') && (outStr[1] == 'n')) //-nan(xxx)
	{
		strcpy(outStr, "NaN");
		return 3;
	}
	return len;
}

int Float::ToString(float f, char* outStr, bool roundTrip)
{
#ifdef USE_CHARCONV
	auto result = std::to_chars(outStr, outStr + 256, f);
	return (int)(result.ptr - outStr);
#else
	return ::ToString(f, outStr, roundTrip);
#endif
}

int Double::ToString(double d, char* outStr, bool roundTrip)
{
#ifdef USE_CHARCONV
	auto result = std::to_chars(outStr, outStr + 256, d);
	return (int)(result.ptr - outStr);
#else
	return ::ToString(d, outStr, roundTrip);
#endif
}
