#if BF_ENABLE_OBJECT_DEBUG_FLAGS || BF_DEBUG_ALLOC
#define BF_DBG_RUNTIME
#endif

namespace System
{
	[StaticInitPriority(100)]
	class Runtime
	{
		const int32 cVersion = 8;

		[CRepr, AlwaysInclude]
		struct BfDebugMessageData
		{
			enum MessageType : int32
			{
				None = 0,
				Error = 1,
				ProfilerCmd = 2
			};

			MessageType mMessageType;
			int32 mStackWindbackCount;
			int32 mBufferParamLen;
			char8* mBufferParam;
			void* mPCOverride;

			char8* mBufferPtr = null;
			int mStrSize = 0;

			[CLink]
			public static BfDebugMessageData gBfDebugMessageData;

			public static ~this()
			{
				if (gBfDebugMessageData.mBufferPtr != null)
				{
                    Internal.Free(gBfDebugMessageData.mBufferPtr);
					gBfDebugMessageData.mBufferPtr = null;
					gBfDebugMessageData.mStrSize = 0;
				}
			}

			public void SetupError(char8* str, int32 stackWindbackCount = 0) mut
			{
				mMessageType = .Error;
				mStackWindbackCount = stackWindbackCount;
				int size = Internal.CStrLen(str) + 1;
				if (mStrSize < size)
				{
					if (mBufferPtr != null)
						Internal.Free(mBufferPtr);
					mStrSize = size;
                    mBufferPtr = (char8*)Internal.Malloc(mStrSize);
				}
				Internal.MemCpy(mBufferPtr, str, size);
				mBufferParam = mBufferPtr;
				mBufferParamLen = (int32)size - 1;
				mPCOverride = null;
			}

			public void SetupProfilerCmd(char8* str) mut
			{
				mMessageType = .ProfilerCmd;
				mStackWindbackCount = 0;

				int size = Internal.CStrLen(str) + 1;
				if (mStrSize < size)
				{
					if (mBufferPtr != null)
						Internal.Free(mBufferPtr);
					mStrSize = size;
				    mBufferPtr = (char8*)Internal.Malloc(mStrSize);
				}
				Internal.MemCpy(mBufferPtr, str, size);
				mBufferParam = mBufferPtr;
				mBufferParamLen = (int32)size - 1;
				mPCOverride = null;
			}

			public void Fatal() mut
			{
				var str = scope String();
				str.Reference(mBufferPtr, mBufferParamLen, 0);
				Internal.FatalError(str, -1);
			}

			public void Clear() mut
			{
				mMessageType = .None;
				if (mBufferPtr != null)
					mBufferPtr[0] = 0;
				mBufferParamLen = 0;
			}
		}

		struct BfRtCallbacks
		{
			public static BfRtCallbacks sCallbacks = .();

			function void* (int size) mAlloc;
			function void (void* ptr) mFree;
			function void (Object obj) mObject_Delete;
			function void (Object obj, String str) mObject_ToString;
			function Type (Object obj) mObject_GetType;
			function void (Object obj) mObject_GCMarkMembers;
			function Object (Object obj, int32 typeId) mObject_DynamicCastToTypeId;
			function void (Type type, String str) mType_GetFullName;
			function String () mString_Alloc;
			function char8* (String str) mString_ToCStr;
			function Object () mThread_Alloc;
			function Object () mThread_GetMainThread;
			function void (Object thread) mThread_ThreadProc;
			function void* (Object thread) mThread_GetInternalThread;
			function void (Object thread, void* internalThread) mThread_SetInternalThread;
			function bool (Object thread) mThread_IsAutoDelete;
			function void (Object thread) mThread_AutoDelete;
			function int32 (Object thread) mThread_GetMaxStackSize;
			function void () mGC_MarkAllStaticMembers;
			function bool () mGC_CallRootCallbacks;
			function void () mGC_Shutdown;
			function void (char8* str) mSetErrorString;
			function void (char8* str, int32 stackWindbackCount) mDebugMessageData_SetupError;
			function void (char8* str) mDebugMessageData_SetupProfilerCmd;
			function void () mDebugMessageData_Fatal;
			function void () mDebugMessageData_Clear;

			static void* Alloc(int size)
			{
				return Internal.Malloc(size);
			}

			static void Free(void* ptr)
			{
				Internal.Free(ptr);
			}

			static void Object_Delete(Object obj)
			{
				delete obj;
			}

			static void Object_ToString(Object obj, String str)
			{
#if BF_DBG_RUNTIME
				obj.ToString(str);
#endif
			}

			static Type Object_GetType(Object obj)
			{
#if BF_DBG_RUNTIME
				return obj.[Friend]RawGetType();
#else
				return null;
#endif
			}

			static void Object_GCMarkMembers(Object obj)
			{
#if BF_ENABLE_REALTIME_LEAK_CHECK
				obj.[Friend, DisableObjectAccessChecks]GCMarkMembers();
#endif
			}

			static Object Object_DynamicCastToTypeId(Object obj, int32 typeId)
			{
#if BF_DYNAMIC_CAST_CHECK
				return obj.DynamicCastToTypeId(typeId);
#else
				return null;
#endif
			}

			static void Type_GetFullName(Type type, String str)
			{
#if BF_DBG_RUNTIME
				type.GetFullName(str);
#else
				//
#endif
			}

			static String String_Alloc()
			{
				return new String();
			}

			static char8* String_ToCStr(String str)
			{
				return str.CStr();
			}

			static void GC_MarkAllStaticMembers()
			{
#if BF_ENABLE_REALTIME_LEAK_CHECK
				GC.[Friend]MarkAllStaticMembers();
#endif
			}

			static bool GC_CallRootCallbacks()
			{
#if BF_ENABLE_REALTIME_LEAK_CHECK
				return GC.[Friend]CallRootCallbacks();
#else
				return true;
#endif
			}

			static void GC_Shutdown()
			{
#if BF_DBG_RUNTIME
				GC.Shutdown();
#endif
			}

			static void DebugMessageData_SetupError(char8* str, int32 stackWindbackCount)
			{
				BfDebugMessageData.gBfDebugMessageData.SetupError(str, stackWindbackCount);
			}

			static void DebugMessageData_SetupProfilerCmd(char8* str)
			{
				BfDebugMessageData.gBfDebugMessageData.SetupProfilerCmd(str);
			}

			static void DebugMessageData_Fatal()
			{
				BfDebugMessageData.gBfDebugMessageData.Fatal();
			}

			static void DebugMessageData_Clear()
			{
				BfDebugMessageData.gBfDebugMessageData.Clear();
			}
		

			public void Init() mut
			{
				mAlloc = => Alloc;
				mFree = => Free;
				mObject_Delete = => Object_Delete;
				mObject_ToString = => Object_ToString;
				mObject_GetType = => Object_GetType;
				mObject_GCMarkMembers = => Object_GCMarkMembers;
			    mObject_DynamicCastToTypeId = => Object_DynamicCastToTypeId;
				mType_GetFullName = => Type_GetFullName;
				mString_Alloc = => String_Alloc;
				mString_ToCStr = => String_ToCStr;
				mGC_MarkAllStaticMembers = => GC_MarkAllStaticMembers;
				mGC_CallRootCallbacks = => GC_CallRootCallbacks;
				mGC_Shutdown = => GC_Shutdown;
				mSetErrorString = => SetErrorString;
				mDebugMessageData_SetupError = => DebugMessageData_SetupError;
				mDebugMessageData_SetupProfilerCmd = => DebugMessageData_SetupProfilerCmd;
				mDebugMessageData_Fatal = => DebugMessageData_Fatal;
				mDebugMessageData_Clear = => DebugMessageData_Clear;			
			}
		};

		private static extern void Init(int32 version, int32 flags, BfRtCallbacks* callbacks);
		private static extern void AddCrashInfoFunc(void* func);
		private static extern void Dbg_Init(int32 version, int32 flags, BfRtCallbacks* callbacks);
		private static extern void SetErrorString(char8* error);
		private static extern void* Dbg_GetCrashInfoFunc();
		public static extern void SetCrashReportKind(RtCrashReportKind crashReportKind);

		public enum RtCrashReportKind : int32
		{
			Default,
			GUI,
			Console,
			PrintOnly,
			None
		}

		enum RtFlags : int32
		{
			ObjectHasDebugFlags = 1,
			LeakCheck = 2,
			SilentCrash = 4,
			DebugAlloc = 8
		}

		static RtFlags sExtraFlags;

		public static this()
		{
			BfRtCallbacks.sCallbacks.Init();

			RtFlags flags = sExtraFlags;
#if BF_ENABLE_OBJECT_DEBUG_FLAGS
			flags |= .ObjectHasDebugFlags;
#endif
#if BF_ENABLE_REALTIME_LEAK_CHECK
			flags |= .LeakCheck;
#endif
#if BF_DEBUG_ALLOC
			flags |= .DebugAlloc;
#endif
			Init(cVersion, (int32)flags, &BfRtCallbacks.sCallbacks);
#if BF_DBG_RUNTIME
			Dbg_Init(cVersion, (int32)flags, &BfRtCallbacks.sCallbacks);
#endif
		}

		[NoReturn]
		public static void FatalError(String msg = "Fatal error encountered")
		{
			Internal.FatalError(msg, 1);
		}

		[NoReturn]
		public static void NotImplemented()
		{
			Internal.FatalError("Not Implemented", 1);
		}

		public static void Assert(bool condition) 
		{
			if (!condition)
				Internal.FatalError("Assert failed", 1);
		}

		public static void Assert(bool condition, String error) 
		{
			if (!condition)
				Internal.FatalError("Assert failed", 1);
		}
	}
}
