// Copyright (c) Wojciech Figat. All rights reserved.

#include "Engine/Scripting/Types.h"

#if USE_NETCORE

#pragma warning(default : 4297)

#include "Engine/Core/Log.h"
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Types/Stopwatch.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Platform/Platform.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Scripting/Internal/InternalCalls.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MDomain.h"
#include "Engine/Scripting/ManagedCLR/MEvent.h"
#include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/ManagedCLR/MProperty.h"
#include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Debug/Exceptions/CLRInnerException.h"
#if DOTNET_HOST_CORECLR
#include <nethost.h>
#include <coreclr_delegates.h>
#include <hostfxr.h>
#elif DOTNET_HOST_MONO
#include "Engine/Engine/CommandLine.h"
#include "Engine/Utilities/StringConverter.h"
#include <mono/jit/jit.h>
#include <mono/jit/mono-private-unstable.h>
#include <mono/utils/mono-logger.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/appdomain.h>
#include <mono/metadata/class.h>
#include <mono/metadata/metadata.h>
#include <mono/metadata/threads.h>
#include <mono/metadata/reflection.h>
#include <mono/metadata/mono-gc.h>
#include <mono/metadata/mono-private-unstable.h>
typedef char char_t;
#define DOTNET_HOST_MONO_DEBUG 0
#ifdef USE_MONO_AOT_MODULE
void* MonoAotModuleHandle = nullptr;
#endif
MonoDomain* MonoDomainHandle = nullptr;
#else
#error "Unknown .NET runtime host."
#endif
#if PLATFORM_WINDOWS
#include <combaseapi.h>
#undef SetEnvironmentVariable
#undef GetEnvironmentVariable
#undef LoadLibrary
#undef LoadImage
#endif

#if defined(_WIN32)
#define CORECLR_DELEGATE_CALLTYPE __stdcall
#define FLAX_CORECLR_STRING String
#define FLAX_CORECLR_TEXT(x) TEXT(x)
#else
#define CORECLR_DELEGATE_CALLTYPE
#define FLAX_CORECLR_STRING StringAnsi
#define FLAX_CORECLR_TEXT(x) x
#endif

// System.Reflection.TypeAttributes
enum class MTypeAttributes : uint32
{
    VisibilityMask = 0x00000007,
    NotPublic = 0x00000000,
    Public = 0x00000001,
    NestedPublic = 0x00000002,
    NestedPrivate = 0x00000003,
    NestedFamily = 0x00000004,
    NestedAssembly = 0x00000005,
    NestedFamANDAssem = 0x00000006,
    NestedFamORAssem = 0x00000007,
    LayoutMask = 0x00000018,
    AutoLayout = 0x00000000,
    SequentialLayout = 0x00000008,
    ExplicitLayout = 0x00000010,
    ClassSemanticsMask = 0x00000020,
    Class = 0x00000000,
    Interface = 0x00000020,
    Abstract = 0x00000080,
    Sealed = 0x00000100,
    SpecialName = 0x00000400,
    Import = 0x00001000,
    Serializable = 0x00002000,
    WindowsRuntime = 0x00004000,
    StringFormatMask = 0x00030000,
    AnsiClass = 0x00000000,
    UnicodeClass = 0x00010000,
    AutoClass = 0x00020000,
    CustomFormatClass = 0x00030000,
    CustomFormatMask = 0x00C00000,
    BeforeFieldInit = 0x00100000,
    RTSpecialName = 0x00000800,
    HasSecurity = 0x00040000,
    ReservedMask = 0x00040800,
};

// System.Reflection.MethodAttributes
enum class MMethodAttributes : uint32
{
    MemberAccessMask = 0x0007,
    PrivateScope = 0x0000,
    Private = 0x0001,
    FamANDAssem = 0x0002,
    Assembly = 0x0003,
    Family = 0x0004,
    FamORAssem = 0x0005,
    Public = 0x0006,
    Static = 0x0010,
    Final = 0x0020,
    Virtual = 0x0040,
    HideBySig = 0x0080,
    CheckAccessOnOverride = 0x0200,
    VtableLayoutMask = 0x0100,
    ReuseSlot = 0x0000,
    NewSlot = 0x0100,
    Abstract = 0x0400,
    SpecialName = 0x0800,
    PinvokeImpl = 0x2000,
    UnmanagedExport = 0x0008,
    RTSpecialName = 0x1000,
    HasSecurity = 0x4000,
    RequireSecObject = 0x8000,
    ReservedMask = 0xd000,
};

// System.Reflection.FieldAttributes
enum class MFieldAttributes : uint32
{
    FieldAccessMask = 0x0007,
    PrivateScope = 0x0000,
    Private = 0x0001,
    FamANDAssem = 0x0002,
    Assembly = 0x0003,
    Family = 0x0004,
    FamORAssem = 0x0005,
    Public = 0x0006,
    Static = 0x0010,
    InitOnly = 0x0020,
    Literal = 0x0040,
    NotSerialized = 0x0080,
    SpecialName = 0x0200,
    PinvokeImpl = 0x2000,
    RTSpecialName = 0x0400,
    HasFieldMarshal = 0x1000,
    HasDefault = 0x8000,
    HasFieldRVA = 0x0100,
    ReservedMask = 0x9500,
};

DECLARE_ENUM_OPERATORS(MTypeAttributes);
DECLARE_ENUM_OPERATORS(MMethodAttributes);
DECLARE_ENUM_OPERATORS(MFieldAttributes);

// Multiple AppDomains are superseded by AssemblyLoadContext in .NET
extern MDomain* MRootDomain;
extern MDomain* MActiveDomain;
extern Array<MDomain*, FixedAllocation<4>> MDomains;

Dictionary<String, void*> CachedFunctions;
Dictionary<void*, MClass*> CachedClassHandles;
Dictionary<void*, MAssembly*> CachedAssemblyHandles;

/// <summary>
/// Returns the function pointer to the managed static method in NativeInterop class.
/// </summary>
void* GetStaticMethodPointer(StringView methodName);

/// <summary>
/// Calls the managed static method with given parameters.
/// </summary>
template<typename RetType, typename... Args>
FORCE_INLINE RetType CallStaticMethod(void* methodPtr, Args... args)
{
#if DOTNET_HOST_MONO
    ASSERT_LOW_LAYER(mono_domain_get()); // Ensure that Mono runtime has been attached to this thread
#endif
    typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...);
    return ((fun)methodPtr)(args...);
}

void RegisterNativeLibrary(const char* moduleName, const Char* modulePath)
{
    static void* RegisterNativeLibraryPtr = GetStaticMethodPointer(TEXT("RegisterNativeLibrary"));
    CallStaticMethod<void, const char*, const Char*>(RegisterNativeLibraryPtr, moduleName, modulePath);
}

bool InitHostfxr();
void ShutdownHostfxr();

MAssembly* GetAssembly(void* assemblyHandle);
MClass* GetClass(MType* typeHandle);
MClass* GetOrCreateClass(MType* typeHandle);
MType* GetObjectType(MObject* obj);

void* GetCustomAttribute(const Array<MObject*, ArenaAllocation>& attributes, const MClass* attributeClass)
{
    for (MObject* attr : attributes)
    {
        MClass* attrClass = MCore::Object::GetClass(attr);
        if (attrClass == attributeClass)
            return attr;
    }
    return nullptr;
}

void GetCustomAttributes(Array<MObject*, ArenaAllocation>& result, void* handle, void* getAttributesFunc)
{
    MObject** attributes;
    int numAttributes;
    CallStaticMethod<void, void*, MObject***, int*>(getAttributesFunc, handle, &attributes, &numAttributes);
    result.Set(attributes, numAttributes);
    MCore::GC::FreeMemory(attributes);
}

// Structures used to pass information from runtime, must match with the structures in managed side
struct NativeClassDefinitions
{
    void* typeHandle;
    MClass* nativePointer;
    const char* name;
    const char* fullname;
    const char* namespace_;
    MTypeAttributes typeAttributes;
};

struct NativeMethodDefinitions
{
    const char* name;
    int numParameters;
    void* handle;
    MMethodAttributes methodAttributes;
};

struct NativeFieldDefinitions
{
    const char* name;
    void* fieldHandle;
    void* fieldType;
    int fieldOffset;
    MFieldAttributes fieldAttributes;
};

struct NativePropertyDefinitions
{
    const char* name;
    void* propertyHandle;
    void* getterHandle;
    void* setterHandle;
    MMethodAttributes getterAttributes;
    MMethodAttributes setterAttributes;
};

MDomain* MCore::CreateDomain(const StringAnsi& domainName)
{
    return nullptr;
}

void MCore::UnloadDomain(const StringAnsi& domainName)
{
}

bool MCore::LoadEngine()
{
    PROFILE_CPU();
    PROFILE_MEM(ScriptingCSharp);

    // Initialize hostfxr
    if (InitHostfxr())
        return true;

    // Prepare managed side
    CallStaticMethod<void>(GetStaticMethodPointer(TEXT("Init")));
#ifdef MCORE_MAIN_MODULE_NAME
    // MCORE_MAIN_MODULE_NAME define is injected by Scripting.Build.cs on platforms that use separate shared library for engine symbols
    ::String flaxLibraryPath(Platform::GetMainDirectory() / TEXT(MACRO_TO_STR(MCORE_MAIN_MODULE_NAME)));
#else
    ::String flaxLibraryPath(Platform::GetExecutableFilePath());
#endif
#if PLATFORM_MAC
    // On some platforms all native binaries are side-by-side with the app in a different folder
    if (!FileSystem::FileExists(flaxLibraryPath))
    {
        flaxLibraryPath = ::String(StringUtils::GetDirectoryName(Platform::GetExecutableFilePath())) / StringUtils::GetFileName(flaxLibraryPath);
    }
#endif
#if !PLATFORM_SWITCH
    if (!FileSystem::FileExists(flaxLibraryPath))
    {
        LOG(Error, "Flax Engine native library file is missing ({0})", flaxLibraryPath);
    }
#endif
    RegisterNativeLibrary("FlaxEngine", flaxLibraryPath.Get());

    MRootDomain = New<MDomain>("Root");
    MDomains.Add(MRootDomain);

    char* buildInfo = CallStaticMethod<char*>(GetStaticMethodPointer(TEXT("GetRuntimeInformation")));
    LOG(Info, ".NET runtime version: {0}", ::String(buildInfo));
    GC::FreeMemory(buildInfo);
    Ready = true;

    return false;
}

void MCore::UnloadEngine()
{
    if (!MRootDomain)
        return;
    PROFILE_CPU();
    CallStaticMethod<void>(GetStaticMethodPointer(TEXT("Exit")));
    Ready = false;
    MDomains.ClearDelete();
    MRootDomain = nullptr;
    ShutdownHostfxr();
}

void MCore::CreateScriptingAssemblyLoadContext()
{
    static void* CreateScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("CreateScriptingAssemblyLoadContext"));
    CallStaticMethod<void>(CreateScriptingAssemblyLoadContextPtr);
}

#if USE_EDITOR

void MCore::UnloadScriptingAssemblyLoadContext()
{
    // Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108)
    for (const auto& e : CachedClassHandles)
    {
        e.Value->_hasCachedAttributes = false;
        e.Value->_attributes.Clear();
    }
    for (const auto& e : CachedAssemblyHandles)
    {
        MAssembly* a = e.Value;
        if (!a->IsLoaded() || !a->_hasCachedClasses)
            continue;
        for (const auto& q : a->GetTypeClasses())
        {
            MClass* c = q.Value;
            c->_hasCachedAttributes = false;
            c->_attributes.Clear();
            if (c->_hasCachedMethods)
            {
                for (MMethod* m : c->GetMethods())
                {
                    m->_hasCachedAttributes = false;
                    m->_attributes.Clear();
                }
            }
            if (c->_hasCachedFields)
            {
                for (MField* f : c->GetFields())
                {
                    f->_hasCachedAttributes = false;
                    f->_attributes.Clear();
                }
            }
            if (c->_hasCachedProperties)
            {
                for (MProperty* p : c->GetProperties())
                {
                    p->_hasCachedAttributes = false;
                    p->_attributes.Clear();
                }
            }
        }
    }

    static void* UnloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("UnloadScriptingAssemblyLoadContext"));
    CallStaticMethod<void>(UnloadScriptingAssemblyLoadContextPtr);
}

#endif

MObject* MCore::Object::Box(void* value, const MClass* klass)
{
    static void* BoxValuePtr = GetStaticMethodPointer(TEXT("BoxValue"));
    return (MObject*)CallStaticMethod<void*, void*, void*>(BoxValuePtr, klass->_handle, value);
}

void* MCore::Object::Unbox(MObject* obj)
{
    static void* UnboxValuePtr = GetStaticMethodPointer(TEXT("UnboxValue"));
    return CallStaticMethod<void*, void*>(UnboxValuePtr, obj);
}

MObject* MCore::Object::New(const MClass* klass)
{
    static void* NewObjectPtr = GetStaticMethodPointer(TEXT("NewObject"));
    return (MObject*)CallStaticMethod<void*, void*>(NewObjectPtr, klass->_handle);
}

void MCore::Object::Init(MObject* obj)
{
    static void* ObjectInitPtr = GetStaticMethodPointer(TEXT("ObjectInit"));
    CallStaticMethod<void, void*>(ObjectInitPtr, obj);
}

MClass* MCore::Object::GetClass(MObject* obj)
{
    ASSERT(obj);
    static void* GetObjectClassPtr = GetStaticMethodPointer(TEXT("GetObjectClass"));
    return (MClass*)CallStaticMethod<MClass*, void*>(GetObjectClassPtr, obj);
}

MString* MCore::Object::ToString(MObject* obj)
{
    static void* GetObjectStringPtr = GetStaticMethodPointer(TEXT("GetObjectString"));
    return (MString*)CallStaticMethod<void*, void*>(GetObjectStringPtr, obj);
}

int32 MCore::Object::GetHashCode(MObject* obj)
{
    static void* GetObjectStringPtr = GetStaticMethodPointer(TEXT("GetObjectHashCode"));
    return CallStaticMethod<int32, void*>(GetObjectStringPtr, obj);
}

MString* MCore::String::GetEmpty(MDomain* domain)
{
    static void* GetStringEmptyPtr = GetStaticMethodPointer(TEXT("GetStringEmpty"));
    return (MString*)CallStaticMethod<void*>(GetStringEmptyPtr);
}

MString* MCore::String::New(const char* str, int32 length, MDomain* domain)
{
    static void* NewStringUTF8Ptr = GetStaticMethodPointer(TEXT("NewStringUTF8"));
    return (MString*)CallStaticMethod<void*, const char*, int>(NewStringUTF8Ptr, str, length);
}

MString* MCore::String::New(const Char* str, int32 length, MDomain* domain)
{
    static void* NewStringUTF16Ptr = GetStaticMethodPointer(TEXT("NewStringUTF16"));
    return (MString*)CallStaticMethod<void*, const Char*, int>(NewStringUTF16Ptr, str, length);
}

StringView MCore::String::GetChars(MString* obj)
{
    int32 length = 0;
    static void* GetStringPointerPtr = GetStaticMethodPointer(TEXT("GetStringPointer"));
    const Char* chars = CallStaticMethod<const Char*, void*, int*>(GetStringPointerPtr, obj, &length);
    return StringView(chars, length);
}

MArray* MCore::Array::New(const MClass* elementKlass, int32 length)
{
    static void* NewArrayPtr = GetStaticMethodPointer(TEXT("NewArray"));
    return (MArray*)CallStaticMethod<void*, void*, long long>(NewArrayPtr, elementKlass->_handle, length);
}

MClass* MCore::Array::GetClass(MClass* elementKlass)
{
    static void* GetArrayTypeFromElementTypePtr = GetStaticMethodPointer(TEXT("GetArrayTypeFromElementType"));
    MType* typeHandle = (MType*)CallStaticMethod<void*, void*>(GetArrayTypeFromElementTypePtr, elementKlass->_handle);
    return GetOrCreateClass(typeHandle);
}

MClass* MCore::Array::GetArrayClass(const MArray* obj)
{
    static void* GetArrayTypeFromWrappedArrayPtr = GetStaticMethodPointer(TEXT("GetArrayTypeFromWrappedArray"));
    MType* typeHandle = (MType*)CallStaticMethod<void*, void*>(GetArrayTypeFromWrappedArrayPtr, (void*)obj);
    return GetOrCreateClass(typeHandle);
}

int32 MCore::Array::GetLength(const MArray* obj)
{
    static void* GetArrayLengthPtr = GetStaticMethodPointer(TEXT("GetArrayLength"));
    return CallStaticMethod<int, void*>(GetArrayLengthPtr, (void*)obj);
}

void* MCore::Array::GetAddress(const MArray* obj)
{
    static void* GetArrayPointerPtr = GetStaticMethodPointer(TEXT("GetArrayPointer"));
    return CallStaticMethod<void*, void*>(GetArrayPointerPtr, (void*)obj);
}

MArray* MCore::Array::Unbox(MObject* obj)
{
    static void* GetArrayPtr = GetStaticMethodPointer(TEXT("GetArray"));
    return (MArray*)CallStaticMethod<void*, void*>(GetArrayPtr, (void*)obj);
}

MGCHandle MCore::GCHandle::New(MObject* obj, bool pinned)
{
    ASSERT(obj);
    static void* NewGCHandlePtr = GetStaticMethodPointer(TEXT("NewGCHandle"));
    return (MGCHandle)CallStaticMethod<void*, void*, bool>(NewGCHandlePtr, obj, pinned);
}

MGCHandle MCore::GCHandle::NewWeak(MObject* obj, bool trackResurrection)
{
    ASSERT(obj);
    static void* NewGCHandleWeakPtr = GetStaticMethodPointer(TEXT("NewGCHandleWeak"));
    return (MGCHandle)CallStaticMethod<void*, void*, bool>(NewGCHandleWeakPtr, obj, trackResurrection);
}

MObject* MCore::GCHandle::GetTarget(const MGCHandle& handle)
{
    return (MObject*)(void*)handle;
}

void MCore::GCHandle::Free(const MGCHandle& handle)
{
    static void* FreeGCHandlePtr = GetStaticMethodPointer(TEXT("FreeGCHandle"));
    CallStaticMethod<void, void*>(FreeGCHandlePtr, (void*)handle);
}

void MCore::GC::Collect()
{
    Collect(MaxGeneration(), MGCCollectionMode::Default, true, false);
}

void MCore::GC::Collect(int32 generation)
{
    Collect(generation, MGCCollectionMode::Default, true, false);
}

void MCore::GC::Collect(int32 generation, MGCCollectionMode collectionMode, bool blocking, bool compacting)
{
    PROFILE_CPU_NAMED("GC Collect");
    ZoneColor(0xe3c349);
    static void* GCCollectPtr = GetStaticMethodPointer(TEXT("GCCollect"));
    CallStaticMethod<void, int, int, bool, bool>(GCCollectPtr, generation, (int)collectionMode, blocking, compacting);
}

int32 MCore::GC::MaxGeneration()
{
    static int32 maxGeneration = CallStaticMethod<int32>(GetStaticMethodPointer(TEXT("GCMaxGeneration")));
    return maxGeneration;
}

void MCore::GC::MemoryInfo(int64& totalCommitted, int64& heapSize)
{
    static void* GCMemoryInfoPtr = GetStaticMethodPointer(TEXT("GCMemoryInfo"));
    CallStaticMethod<void, int64*, int64*>(GCMemoryInfoPtr, &totalCommitted, &heapSize);
}

void MCore::GC::WaitForPendingFinalizers()
{
    PROFILE_CPU_NAMED("GC WaitForPendingFinalizers");
    ZoneColor(TracyWaitZoneColor);
    static void* GCWaitForPendingFinalizersPtr = GetStaticMethodPointer(TEXT("GCWaitForPendingFinalizers"));
    CallStaticMethod<void>(GCWaitForPendingFinalizersPtr);
}

void MCore::GC::WriteRef(void* ptr, MObject* ref)
{
    *(void**)ptr = ref;
}

void MCore::GC::WriteValue(void* dst, void* src, int32 count, const MClass* klass)
{
    const int32 size = klass->GetInstanceSize();
    memcpy(dst, src, count * size);
}

void MCore::GC::WriteArrayRef(MArray* dst, MObject* ref, int32 index)
{
    static void* WriteArrayReferencePtr = GetStaticMethodPointer(TEXT("WriteArrayReference"));
    CallStaticMethod<void, void*, void*, int32>(WriteArrayReferencePtr, dst, ref, index);
}

void MCore::GC::WriteArrayRef(MArray* dst, Span<MObject*> refs)
{
    static void* WriteArrayReferencesPtr = GetStaticMethodPointer(TEXT("WriteArrayReferences"));
    CallStaticMethod<void, void*, void*, int32>(WriteArrayReferencesPtr, dst, refs.Get(), refs.Length());
}

void* MCore::GC::AllocateMemory(int32 size, bool coTaskMem)
{
    static void* AllocMemoryPtr = GetStaticMethodPointer(TEXT("AllocMemory"));
    return CallStaticMethod<void*, int, bool>(AllocMemoryPtr, size, coTaskMem);
}

void MCore::GC::FreeMemory(void* ptr, bool coTaskMem)
{
    if (!ptr)
        return;
    static void* FreeMemoryPtr = GetStaticMethodPointer(TEXT("FreeMemory"));
    CallStaticMethod<void, void*, bool>(FreeMemoryPtr, ptr, coTaskMem);
}

void MCore::Thread::Attach()
{
#if DOTNET_HOST_MONO
    if (!IsInMainThread() && !mono_domain_get())
    {
        mono_thread_attach(MonoDomainHandle);
    }
#endif
}

void MCore::Thread::Exit()
{
}

bool MCore::Thread::IsAttached()
{
    return true;
}

void MCore::Exception::Throw(MObject* exception)
{
    static void* RaiseExceptionPtr = GetStaticMethodPointer(TEXT("RaiseException"));
    CallStaticMethod<void*, void*>(RaiseExceptionPtr, exception);
}

MObject* MCore::Exception::GetNullReference()
{
    static void* GetNullReferenceExceptionPtr = GetStaticMethodPointer(TEXT("GetNullReferenceException"));
    return (MObject*)CallStaticMethod<void*>(GetNullReferenceExceptionPtr);
}

MObject* MCore::Exception::Get(const char* msg)
{
    static void* GetExceptionPtr = GetStaticMethodPointer(TEXT("GetException"));
    return (MObject*)CallStaticMethod<void*, const char*>(GetExceptionPtr, msg);
}

MObject* MCore::Exception::GetArgument(const char* arg, const char* msg)
{
    static void* GetArgumentExceptionPtr = GetStaticMethodPointer(TEXT("GetArgumentException"));
    return (MObject*)CallStaticMethod<void*>(GetArgumentExceptionPtr);
}

MObject* MCore::Exception::GetArgumentNull(const char* arg)
{
    static void* GetArgumentNullExceptionPtr = GetStaticMethodPointer(TEXT("GetArgumentNullException"));
    return (MObject*)CallStaticMethod<void*>(GetArgumentNullExceptionPtr);
}

MObject* MCore::Exception::GetArgumentOutOfRange(const char* arg)
{
    static void* GetArgumentOutOfRangeExceptionPtr = GetStaticMethodPointer(TEXT("GetArgumentOutOfRangeException"));
    return (MObject*)CallStaticMethod<void*>(GetArgumentOutOfRangeExceptionPtr);
}

MObject* MCore::Exception::GetNotSupported(const char* msg)
{
    static void* GetNotSupportedExceptionPtr = GetStaticMethodPointer(TEXT("GetNotSupportedException"));
    return (MObject*)CallStaticMethod<void*>(GetNotSupportedExceptionPtr);
}

::String MCore::Type::ToString(MType* type)
{
    MClass* klass = GetOrCreateClass(type);
    return ::String(klass->GetFullName());
}

MClass* MCore::Type::GetClass(MType* type)
{
    static void* GetTypeClassPtr = GetStaticMethodPointer(TEXT("GetTypeClass"));
    return CallStaticMethod<MClass*, void*>(GetTypeClassPtr, type);
}

MType* MCore::Type::GetElementType(MType* type)
{
    static void* GetElementClassPtr = GetStaticMethodPointer(TEXT("GetElementClass"));
    return (MType*)CallStaticMethod<void*, void*>(GetElementClassPtr, type);
}

int32 MCore::Type::GetSize(MType* type)
{
    return GetOrCreateClass(type)->GetInstanceSize();
}

MTypes MCore::Type::GetType(MType* type)
{
    MClass* klass = GetOrCreateClass(type);
    if (klass->_types == 0)
    {
        static void* GetTypeMTypesEnumPtr = GetStaticMethodPointer(TEXT("GetTypeMTypesEnum"));
        klass->_types = CallStaticMethod<uint32, void*>(GetTypeMTypesEnumPtr, klass->_handle);
    }
    return (MTypes)klass->_types;
}

bool MCore::Type::IsPointer(MType* type)
{
    static void* GetTypeIsPointerPtr = GetStaticMethodPointer(TEXT("GetTypeIsPointer"));
    return CallStaticMethod<bool, void*>(GetTypeIsPointerPtr, type);
}

bool MCore::Type::IsReference(MType* type)
{
    static void* GetTypeIsReferencePtr = GetStaticMethodPointer(TEXT("GetTypeIsReference"));
    return CallStaticMethod<bool, void*>(GetTypeIsReferencePtr, type);
}

void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id)
{
#if PLATFORM_DESKTOP && !USE_MONO_AOT
    static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectSetInternalValues"));
    CallStaticMethod<void, MObject*, void*, const Guid*>(ScriptingObjectSetInternalValuesPtr, object, unmanagedPtr, id);
#elif !USE_EDITOR && PLATFORM_SWITCH // TODO: test this on other AOT platforms (Android with Mono JIT doesn't work)
    static MField* monoUnmanagedPtrField = ::ScriptingObject::GetStaticClass()->GetField("__unmanagedPtr");
    static MField* monoIdField = ::ScriptingObject::GetStaticClass()->GetField("__internalId");
    if (monoUnmanagedPtrField)
        monoUnmanagedPtrField->SetValue(object, &unmanagedPtr);
    if (id != nullptr && monoIdField)
        monoIdField->SetValue(object, (void*)id);
#else
    const MField* monoUnmanagedPtrField = klass->GetField("__unmanagedPtr");
    if (monoUnmanagedPtrField)
        monoUnmanagedPtrField->SetValue(object, &unmanagedPtr);
    const MField* monoIdField = klass->GetField("__internalId");
    if (id != nullptr && monoIdField)
        monoIdField->SetValue(object, (void*)id);
#endif
}

MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id)
{
#if PLATFORM_DESKTOP && !USE_MONO_AOT
    static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectCreate"));
    return CallStaticMethod<MObject*, void*, void*, const Guid*>(ScriptingObjectSetInternalValuesPtr, klass->_handle, unmanagedPtr, id);
#else
    MObject* object = MCore::Object::New(klass);
    if (object)
    {
        MCore::ScriptingObject::SetInternalValues(klass, object, unmanagedPtr, id);
        MCore::Object::Init(object);
    }
    return object;
#endif
}

const MAssembly::ClassesDictionary& MAssembly::GetClasses() const
{
    if (_hasCachedClasses || !IsLoaded())
        return _classes;
    PROFILE_CPU();
    PROFILE_MEM(ScriptingCSharp);
    Stopwatch stopwatch;

#if TRACY_ENABLE
    ZoneText(*_name, _name.Length());
#endif
    ScopeLock lock(BinaryModule::Locker);
    if (_hasCachedClasses)
        return _classes;
    ASSERT(_classes.IsEmpty());

    NativeClassDefinitions* managedClasses;
    int classCount;
    static void* GetManagedClassesPtr = GetStaticMethodPointer(TEXT("GetManagedClasses"));
    CallStaticMethod<void, void*, NativeClassDefinitions**, int*>(GetManagedClassesPtr, _handle, &managedClasses, &classCount);
    _classes.EnsureCapacity(classCount);
    MAssembly* assembly = const_cast<MAssembly*>(this);
    for (int32 i = 0; i < classCount; i++)
    {
        NativeClassDefinitions& managedClass = managedClasses[i];

        // Create class object
        MClass* klass = assembly->Memory.New<MClass>(assembly, managedClass.typeHandle, managedClass.name, managedClass.fullname, managedClass.namespace_, managedClass.typeAttributes);
        _classes.Add(klass->GetFullName(), klass);

        managedClass.nativePointer = klass;

        MCore::GC::FreeMemory((void*)managedClasses[i].name);
        MCore::GC::FreeMemory((void*)managedClasses[i].fullname);
        MCore::GC::FreeMemory((void*)managedClasses[i].namespace_);
    }
    _typeClasses = _classes;

    static void* RegisterManagedClassNativePointersPtr = GetStaticMethodPointer(TEXT("RegisterManagedClassNativePointers"));
    CallStaticMethod<void, NativeClassDefinitions**, int>(RegisterManagedClassNativePointersPtr, &managedClasses, classCount);

    MCore::GC::FreeMemory(managedClasses);

    stopwatch.Stop();
    LOG(Info, "Caching classes for assembly {0} took {1}ms", String(_name), stopwatch.GetMilliseconds());

#if 0
    for (auto i = _classes.Begin(); i.IsNotEnd(); ++i)
        LOG(Info, "Class: {0}", String(i->Value->GetFullName()));
#endif

    _hasCachedClasses = true;
    return _classes;
}

MAssembly::ClassesDictionary& MAssembly::GetTypeClasses() const
{
    GetClasses();
    return _typeClasses;
}

void GetAssemblyName(void* assemblyHandle, StringAnsi& name, StringAnsi& fullname)
{
    static void* GetAssemblyNamePtr = GetStaticMethodPointer(TEXT("GetAssemblyName"));
    const char* name_;
    const char* fullname_;
    CallStaticMethod<void, void*, const char**, const char**>(GetAssemblyNamePtr, assemblyHandle, &name_, &fullname_);
    name = name_;
    fullname = fullname_;
    MCore::GC::FreeMemory((void*)name_);
    MCore::GC::FreeMemory((void*)fullname_);
}

DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* managedClass, void* assemblyHandle)
{
    PROFILE_MEM(ScriptingCSharp);
    ScopeLock lock(BinaryModule::Locker);
    MAssembly* assembly = GetAssembly(assemblyHandle);
    if (assembly == nullptr)
    {
        StringAnsi assemblyName;
        StringAnsi assemblyFullName;
        GetAssemblyName(assemblyHandle, assemblyName, assemblyFullName);
        assembly = New<MAssembly>(nullptr, assemblyName, assemblyFullName, assemblyHandle);
        CachedAssemblyHandles.Add(assemblyHandle, assembly);
    }

    MClass* klass = assembly->Memory.New<MClass>(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes);
    if (assembly != nullptr)
    {
        auto& classes = assembly->GetTypeClasses();
        MClass* oldKlass;
        if (classes.TryGet(klass->GetFullName(), oldKlass))
        {
            LOG(Warning, "Class '{0}' was already added to assembly '{1}'", String(klass->GetFullName()), String(assembly->GetName()));
            Memory::DestructItem(klass);
            klass = oldKlass;
        }
        else
        {
            classes.Add(klass->GetFullName(), klass);
        }
    }
    managedClass->nativePointer = klass;
}

bool MAssembly::LoadCorlib()
{
    if (IsLoaded())
        return false;
    PROFILE_CPU();
    PROFILE_MEM(ScriptingCSharp);
#if TRACY_ENABLE
    const StringAnsiView name("Corlib");
    ZoneText(*name, name.Length());
#endif

    // Ensure to be unloaded
    Unload();

    // Start
    Stopwatch stopwatch;
    OnLoading();

    // Load
    {
        static void* GetAssemblyByNamePtr = GetStaticMethodPointer(TEXT("GetAssemblyByName"));
        _handle = CallStaticMethod<void*, const char*>(GetAssemblyByNamePtr, "System.Private.CoreLib");
        GetAssemblyName(_handle, _name, _fullname);
    }
    if (_handle == nullptr)
    {
        OnLoadFailed();
        return true;
    }
    _hasCachedClasses = false;
    _canReload = false;
    CachedAssemblyHandles.Add(_handle, this);

    // End
    OnLoaded(stopwatch);
    return false;
}

bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePath)
{
    // TODO: Use new hostfxr delegate load_assembly_bytes? (.NET 8+)
    // Open .Net assembly
    static void* LoadAssemblyImagePtr = GetStaticMethodPointer(TEXT("LoadAssemblyImage"));
    _handle = CallStaticMethod<void*, const Char*>(LoadAssemblyImagePtr, assemblyPath.Get());
    if (_handle == nullptr)
    {
        Log::CLRInnerException(TEXT(".NET assembly image is invalid at ") + assemblyPath);
        return true;
    }
    GetAssemblyName(_handle, _name, _fullname);
    CachedAssemblyHandles.Add(_handle, this);

    // Provide new path of hot-reloaded native library path for managed DllImport
    if (nativePath.HasChars())
    {
        StringAnsi nativeName = _name.EndsWith(".CSharp") ? StringAnsi(_name.Get(), _name.Length() - 7) : StringAnsi(_name);
        RegisterNativeLibrary(nativeName.Get(), nativePath.Get());
    }
#if USE_EDITOR
    // Register the editor module location for Assembly resolver
    else
    {
        RegisterNativeLibrary(_name.Get(), assemblyPath.Get());
    }
#endif

    _hasCachedClasses = false;
    _assemblyPath = assemblyPath;
    return false;
}

bool MAssembly::UnloadImage(bool isReloading)
{
    if (_handle && isReloading)
    {
        LOG(Info, "Unloading managed assembly \'{0}\' (is reloading)", String(_name));

        static void* CloseAssemblyPtr = GetStaticMethodPointer(TEXT("CloseAssembly"));
        CallStaticMethod<void, const void*>(CloseAssemblyPtr, _handle);

        CachedAssemblyHandles.Remove(_handle);
        _handle = nullptr;
    }
    return false;
}

MClass::MClass(MAssembly* parentAssembly, void* handle, const char* name, const char* fullname, const char* namespace_, MTypeAttributes attributes)
    : _handle(handle)
    , _name(parentAssembly->AllocString(name))
    , _namespace(parentAssembly->AllocString(namespace_))
    , _fullname(parentAssembly->AllocString(fullname))
    , _assembly(parentAssembly)
    , _methods(&parentAssembly->Memory)
    , _fields(&parentAssembly->Memory)
    , _properties(&parentAssembly->Memory)
    , _attributes(&parentAssembly->Memory)
    , _events(&parentAssembly->Memory)
    , _interfaces(&parentAssembly->Memory)
    , _hasCachedProperties(false)
    , _hasCachedFields(false)
    , _hasCachedMethods(false)
    , _hasCachedAttributes(false)
    , _hasCachedEvents(false)
    , _hasCachedInterfaces(false)
{
    ASSERT(handle != nullptr);
    switch (attributes & MTypeAttributes::VisibilityMask)
    {
    case MTypeAttributes::NotPublic:
    case MTypeAttributes::NestedPrivate:
        _visibility = MVisibility::Private;
        break;
    case MTypeAttributes::Public:
    case MTypeAttributes::NestedPublic:
        _visibility = MVisibility::Public;
        break;
    case MTypeAttributes::NestedFamily:
    case MTypeAttributes::NestedAssembly:
        _visibility = MVisibility::Internal;
        break;
    case MTypeAttributes::NestedFamORAssem:
        _visibility = MVisibility::ProtectedInternal;
        break;
    case MTypeAttributes::NestedFamANDAssem:
        _visibility = MVisibility::PrivateProtected;
        break;
    default:
        CRASH;
    }

    const MTypeAttributes staticClassFlags = MTypeAttributes::Abstract | MTypeAttributes::Sealed;
    _isStatic = (attributes & staticClassFlags) == staticClassFlags;
    _isSealed = !_isStatic && (attributes & MTypeAttributes::Sealed) == MTypeAttributes::Sealed;
    _isAbstract = !_isStatic && (attributes & MTypeAttributes::Abstract) == MTypeAttributes::Abstract;
    _isInterface = (attributes & MTypeAttributes::ClassSemanticsMask) == MTypeAttributes::Interface;

    // TODO: pass type info from C# side at once (pack into flags with attributes)

    static void* TypeIsValueTypePtr = GetStaticMethodPointer(TEXT("TypeIsValueType"));
    _isValueType = CallStaticMethod<bool, void*>(TypeIsValueTypePtr, handle);

    static void* TypeIsEnumPtr = GetStaticMethodPointer(TEXT("TypeIsEnum"));
    _isEnum = CallStaticMethod<bool, void*>(TypeIsEnumPtr, handle);

    _isGeneric = _fullname.FindLast('`') != -1;

    CachedClassHandles[handle] = this;
}

bool MAssembly::ResolveMissingFile(String& assemblyPath) const
{
#if DOTNET_HOST_MONO
    // Fallback to AOT-ed assembly location
    assemblyPath = Globals::BinariesFolder / TEXT("Dotnet") / StringUtils::GetFileName(assemblyPath);
    return !FileSystem::FileExists(assemblyPath);
#endif
    return true;
}

MClass::~MClass()
{
    ArenaAllocator::ClearDelete(_methods);
    ArenaAllocator::ClearDelete(_fields);
    ArenaAllocator::ClearDelete(_properties);
    ArenaAllocator::ClearDelete(_events);

    CachedClassHandles.Remove(_handle);
}

MType* MClass::GetType() const
{
    return (MType*)_handle;
}

MClass* MClass::GetBaseClass() const
{
    static void* GetClassParentPtr = GetStaticMethodPointer(TEXT("GetClassParent"));
    return CallStaticMethod<MClass*, void*>(GetClassParentPtr, _handle);
}

bool MClass::IsSubClassOf(const MClass* klass, bool checkInterfaces) const
{
    static void* TypeIsSubclassOfPtr = GetStaticMethodPointer(TEXT("TypeIsSubclassOf"));
    return klass && CallStaticMethod<bool, void*, void*, bool>(TypeIsSubclassOfPtr, _handle, klass->_handle, checkInterfaces);
}

bool MClass::HasInterface(const MClass* klass) const
{
    static void* TypeIsAssignableFrom = GetStaticMethodPointer(TEXT("TypeIsAssignableFrom"));
    return klass && CallStaticMethod<bool, void*, void*>(TypeIsAssignableFrom, klass->_handle, _handle);
}

bool MClass::IsInstanceOfType(MObject* object) const
{
    if (object == nullptr)
        return false;
    MClass* objectClass = MCore::Object::GetClass(object);
    return IsSubClassOf(objectClass, false);
}

uint32 MClass::GetInstanceSize() const
{
    if (_size != 0)
        return _size;
    static void* NativeSizeOfPtr = GetStaticMethodPointer(TEXT("NativeSizeOf"));
    _size = CallStaticMethod<int, void*>(NativeSizeOfPtr, _handle);
    return _size;
}

MClass* MClass::GetElementClass() const
{
    static void* GetElementClassPtr = GetStaticMethodPointer(TEXT("GetElementClass"));
    return CallStaticMethod<MClass*, void*>(GetElementClassPtr, _handle);
}

MMethod* MClass::GetMethod(const char* name, int32 numParams) const
{
    GetMethods();
    for (MMethod* method : _methods)
    {
        if (method->GetParametersCount() == numParams && method->GetName() == name)
            return method;
    }
    return nullptr;
}

MMethod* MClass::GetMethod(const ScriptingTypeMethodSignature& signature) const
{
    GetMethods();
    for (MMethod* method : _methods)
    {
        if (method->IsStatic() != signature.IsStatic)
            continue;
        if (method->GetName() != signature.Name)
            continue;
        if (method->GetParametersCount() != signature.Params.Count())
            continue;
        bool isValid = true;
        for (int32 paramIdx = 0; paramIdx < signature.Params.Count(); paramIdx++)
        {
            auto& param = signature.Params[paramIdx];
            MType* type = method->GetParameterType(paramIdx);
            if (param.IsOut != method->GetParameterIsOut(paramIdx) ||
                !MUtils::VariantTypeEquals(param.Type, type, param.IsOut))
            {
                isValid = false;
                break;
            }
        }
        if (isValid && (signature.ReturnType.Type == VariantType::Null || MUtils::VariantTypeEquals(signature.ReturnType, method->GetReturnType())))
            return method;
    }
    return nullptr;
}

const Array<MMethod*, ArenaAllocation>& MClass::GetMethods() const
{
    if (_hasCachedMethods)
        return _methods;
    PROFILE_MEM(ScriptingCSharp);
    ScopeLock lock(BinaryModule::Locker);
    if (_hasCachedMethods)
        return _methods;

    NativeMethodDefinitions* methods;
    int methodsCount;
    static void* GetClassMethodsPtr = GetStaticMethodPointer(TEXT("GetClassMethods"));
    CallStaticMethod<void, void*, NativeMethodDefinitions**, int*>(GetClassMethodsPtr, _handle, &methods, &methodsCount);
    _methods.Resize(methodsCount);
    MAssembly* assembly = const_cast<MAssembly*>(_assembly);
    for (int32 i = 0; i < methodsCount; i++)
    {
        NativeMethodDefinitions& definition = methods[i];
        MMethod* method = assembly->Memory.New<MMethod>(const_cast<MClass*>(this), assembly->AllocString(definition.name), definition.handle, definition.numParameters, definition.methodAttributes);
        _methods[i] = method;
        MCore::GC::FreeMemory((void*)definition.name);
    }
    MCore::GC::FreeMemory(methods);

    _hasCachedMethods = true;
    return _methods;
}

MField* MClass::GetField(const char* name) const
{
    GetFields();
    for (int32 i = 0; i < _fields.Count(); i++)
    {
        if (_fields[i]->GetName() == name)
            return _fields[i];
    }
    return nullptr;
}

const Array<MField*, ArenaAllocation>& MClass::GetFields() const
{
    if (_hasCachedFields)
        return _fields;
    PROFILE_MEM(ScriptingCSharp);
    ScopeLock lock(BinaryModule::Locker);
    if (_hasCachedFields)
        return _fields;

    NativeFieldDefinitions* fields;
    int numFields;
    static void* GetClassFieldsPtr = GetStaticMethodPointer(TEXT("GetClassFields"));
    CallStaticMethod<void, void*, NativeFieldDefinitions**, int*>(GetClassFieldsPtr, _handle, &fields, &numFields);
    _fields.Resize(numFields);
    for (int32 i = 0; i < numFields; i++)
    {
        NativeFieldDefinitions& definition = fields[i];
        MField* field = _assembly->Memory.New<MField>(const_cast<MClass*>(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldOffset, definition.fieldAttributes);
        _fields[i] = field;
        MCore::GC::FreeMemory((void*)definition.name);
    }
    MCore::GC::FreeMemory(fields);

    _hasCachedFields = true;
    return _fields;
}

const Array<MEvent*, ArenaAllocation>& MClass::GetEvents() const
{
    if (_hasCachedEvents)
        return _events;
    PROFILE_MEM(ScriptingCSharp);

    // TODO: implement MEvent in .NET

    _hasCachedEvents = true;
    return _events;
}

MProperty* MClass::GetProperty(const char* name) const
{
    GetProperties();
    for (int32 i = 0; i < _properties.Count(); i++)
    {
        if (_properties[i]->GetName() == name)
            return _properties[i];
    }
    return nullptr;
}

const Array<MProperty*, ArenaAllocation>& MClass::GetProperties() const
{
    if (_hasCachedProperties)
        return _properties;
    PROFILE_MEM(ScriptingCSharp);
    ScopeLock lock(BinaryModule::Locker);
    if (_hasCachedProperties)
        return _properties;

    NativePropertyDefinitions* foundProperties;
    int numProperties;
    static void* GetClassPropertiesPtr = GetStaticMethodPointer(TEXT("GetClassProperties"));
    CallStaticMethod<void, void*, NativePropertyDefinitions**, int*>(GetClassPropertiesPtr, _handle, &foundProperties, &numProperties);
    _properties.Resize(numProperties);
    for (int i = 0; i < numProperties; i++)
    {
        const NativePropertyDefinitions& definition = foundProperties[i];
        MProperty* property = _assembly->Memory.New<MProperty>(const_cast<MClass*>(this), definition.name, definition.propertyHandle, definition.getterHandle, definition.setterHandle, definition.getterAttributes, definition.setterAttributes);
        _properties[i] = property;
        MCore::GC::FreeMemory((void*)definition.name);
    }
    MCore::GC::FreeMemory(foundProperties);

    _hasCachedProperties = true;
    return _properties;
}

const Array<MClass*, ArenaAllocation>& MClass::GetInterfaces() const
{
    if (_hasCachedInterfaces)
        return _interfaces;
    PROFILE_MEM(ScriptingCSharp);
    ScopeLock lock(BinaryModule::Locker);
    if (_hasCachedInterfaces)
        return _interfaces;

    MType** foundInterfaceTypes;
    int numInterfaces;
    static void* GetClassInterfacesPtr = GetStaticMethodPointer(TEXT("GetClassInterfaces"));
    CallStaticMethod<void, void*, MType***, int*>(GetClassInterfacesPtr, _handle, &foundInterfaceTypes, &numInterfaces);
    _interfaces.Resize(numInterfaces);
    for (int32 i = 0; i < numInterfaces; i++)
    {
        MClass* interfaceClass = GetOrCreateClass(foundInterfaceTypes[i]);
        _interfaces[i] = interfaceClass;
    }
    MCore::GC::FreeMemory(foundInterfaceTypes);

    _hasCachedInterfaces = true;
    return _interfaces;
}

bool MClass::HasAttribute(const MClass* klass) const
{
    return GetCustomAttribute(GetAttributes(), klass) != nullptr;
}

bool MClass::HasAttribute() const
{
    return !GetAttributes().IsEmpty();
}

MObject* MClass::GetAttribute(const MClass* klass) const
{
    return (MObject*)GetCustomAttribute(GetAttributes(), klass);
}

const Array<MObject*, ArenaAllocation>& MClass::GetAttributes() const
{
    if (_hasCachedAttributes)
        return _attributes;
    PROFILE_MEM(ScriptingCSharp);
    ScopeLock lock(BinaryModule::Locker);
    if (_hasCachedAttributes)
        return _attributes;
    static void* GetClassAttributesPtr = GetStaticMethodPointer(TEXT("GetClassAttributes"));
    GetCustomAttributes(_attributes, _handle, GetClassAttributesPtr);
    _hasCachedAttributes = true;
    return _attributes;
}

bool MDomain::SetCurrentDomain(bool force)
{
    MActiveDomain = this;
    return true;
}

void MDomain::Dispatch() const
{
}

MEvent::MEvent(MClass* parentClass, void* handle, const char* name)
    : _handle(handle)
    , _addMethod(nullptr)
    , _removeMethod(nullptr)
    , _parentClass(parentClass)
    , _name(parentClass->GetAssembly()->AllocString(name))
    , _hasCachedAttributes(false)
    , _hasAddMonoMethod(true)
    , _hasRemoveMonoMethod(true)
    , _attributes(&parentClass->GetAssembly()->Memory)
{
}

MMethod* MEvent::GetAddMethod() const
{
    return nullptr; // TODO: implement MEvent in .NET
}

MMethod* MEvent::GetRemoveMethod() const
{
    return nullptr; // TODO: implement MEvent in .NET
}

bool MEvent::HasAttribute(const MClass* klass) const
{
    return GetCustomAttribute(GetAttributes(), klass) != nullptr;
}

bool MEvent::HasAttribute() const
{
    return !GetAttributes().IsEmpty();
}

MObject* MEvent::GetAttribute(const MClass* klass) const
{
    return (MObject*)GetCustomAttribute(GetAttributes(), klass);
}

const Array<MObject*, ArenaAllocation>& MEvent::GetAttributes() const
{
    if (_hasCachedAttributes)
        return _attributes;
    _hasCachedAttributes = true;

    // TODO: implement MEvent in .NET
    return _attributes;
}

MException::MException(MObject* exception)
    : InnerException(nullptr)
{
    ASSERT(exception);
    MClass* exceptionClass = MCore::Object::GetClass(exception);

    MProperty* exceptionMsgProp = exceptionClass->GetProperty("Message");
    MMethod* exceptionMsgGetter = exceptionMsgProp->GetGetMethod();
    MString* exceptionMsg = (MString*)exceptionMsgGetter->Invoke(exception, nullptr, nullptr);
    Message = MUtils::ToString(exceptionMsg);

    MProperty* exceptionStackProp = exceptionClass->GetProperty("StackTrace");
    MMethod* exceptionStackGetter = exceptionStackProp->GetGetMethod();
    MString* exceptionStackTrace = (MString*)exceptionStackGetter->Invoke(exception, nullptr, nullptr);
    StackTrace = MUtils::ToString(exceptionStackTrace);

    MProperty* innerExceptionProp = exceptionClass->GetProperty("InnerException");
    MMethod* innerExceptionGetter = innerExceptionProp->GetGetMethod();
    MObject* innerException = (MObject*)innerExceptionGetter->Invoke(exception, nullptr, nullptr);
    if (innerException)
        InnerException = New<MException>(innerException);
}

MException::~MException()
{
    if (InnerException)
        Delete(InnerException);
}

MField::MField(MClass* parentClass, void* handle, const char* name, void* type, int fieldOffset, MFieldAttributes attributes)
    : _handle(handle)
    , _type(type)
    , _fieldOffset(fieldOffset)
    , _parentClass(parentClass)
    , _name(parentClass->GetAssembly()->AllocString(name))
    , _hasCachedAttributes(false)
    , _attributes(&parentClass->GetAssembly()->Memory)
{
    switch (attributes & MFieldAttributes::FieldAccessMask)
    {
    case MFieldAttributes::Private:
        _visibility = MVisibility::Private;
        break;
    case MFieldAttributes::FamANDAssem:
        _visibility = MVisibility::PrivateProtected;
        break;
    case MFieldAttributes::Assembly:
        _visibility = MVisibility::Internal;
        break;
    case MFieldAttributes::Family:
        _visibility = MVisibility::Protected;
        break;
    case MFieldAttributes::FamORAssem:
        _visibility = MVisibility::ProtectedInternal;
        break;
    case MFieldAttributes::Public:
        _visibility = MVisibility::Public;
        break;
    default:
        CRASH;
    }
    _isStatic = (attributes & MFieldAttributes::Static) == MFieldAttributes::Static;
}

MType* MField::GetType() const
{
    return (MType*)_type;
}

int32 MField::GetOffset() const
{
    return _fieldOffset;
}

void MField::GetValue(MObject* instance, void* result) const
{
    static void* FieldGetValuePtr = GetStaticMethodPointer(TEXT("FieldGetValue"));
    CallStaticMethod<void, void*, void*, void*>(FieldGetValuePtr, instance, _handle, result);
}

void MField::GetValueReference(MObject* instance, void* result) const
{
    static void* FieldGetValueReferencePtr = GetStaticMethodPointer(TEXT("FieldGetValueReference"));
    CallStaticMethod<void, void*, void*, int, void*>(FieldGetValueReferencePtr, instance, _handle, _fieldOffset, result);
}

MObject* MField::GetValueBoxed(MObject* instance) const
{
    static void* FieldGetValueBoxedPtr = GetStaticMethodPointer(TEXT("FieldGetValueBoxed"));
    return CallStaticMethod<MObject*, void*, void*>(FieldGetValueBoxedPtr, instance, _handle);
}

void MField::SetValue(MObject* instance, void* value) const
{
    static void* FieldSetValuePtr = GetStaticMethodPointer(TEXT("FieldSetValue"));
    CallStaticMethod<void, void*, void*, void*>(FieldSetValuePtr, instance, _handle, value);
}

bool MField::HasAttribute(const MClass* klass) const
{
    return GetCustomAttribute(GetAttributes(), klass) != nullptr;
}

bool MField::HasAttribute() const
{
    return !GetAttributes().IsEmpty();
}

MObject* MField::GetAttribute(const MClass* klass) const
{
    return (MObject*)GetCustomAttribute(GetAttributes(), klass);
}

const Array<MObject*, ArenaAllocation>& MField::GetAttributes() const
{
    if (_hasCachedAttributes)
        return _attributes;
    PROFILE_MEM(ScriptingCSharp);
    ScopeLock lock(BinaryModule::Locker);
    if (_hasCachedAttributes)
        return _attributes;
    static void* GetFieldAttributesPtr = GetStaticMethodPointer(TEXT("GetFieldAttributes"));
    GetCustomAttributes(_attributes, _handle, GetFieldAttributesPtr);
    _hasCachedAttributes = true;
    return _attributes;
}

MMethod::MMethod(MClass* parentClass, StringAnsiView name, void* handle, int32 paramsCount, MMethodAttributes attributes)
    : _handle(handle)
    , _paramsCount(paramsCount)
    , _parentClass(parentClass)
    , _name(name)
    , _hasCachedAttributes(false)
    , _hasCachedSignature(false)
    , _attributes(&parentClass->GetAssembly()->Memory)
{
    switch (attributes & MMethodAttributes::MemberAccessMask)
    {
    case MMethodAttributes::Private:
        _visibility = MVisibility::Private;
        break;
    case MMethodAttributes::FamANDAssem:
        _visibility = MVisibility::PrivateProtected;
        break;
    case MMethodAttributes::Assembly:
        _visibility = MVisibility::Internal;
        break;
    case MMethodAttributes::Family:
        _visibility = MVisibility::Protected;
        break;
    case MMethodAttributes::FamORAssem:
        _visibility = MVisibility::ProtectedInternal;
        break;
    case MMethodAttributes::Public:
        _visibility = MVisibility::Public;
        break;
    default:
        CRASH;
    }
    _isStatic = (attributes & MMethodAttributes::Static) == MMethodAttributes::Static;

#if COMPILE_WITH_PROFILER
    // Setup Tracy profiler entry (use assembly memory)
    const StringAnsiView className = parentClass->GetFullName();
    char* profilerName = (char*)parentClass->GetAssembly()->Memory.Allocate(className.Length() + _name.Length() + 3);
    Platform::MemoryCopy(profilerName, className.Get(), className.Length());
    profilerName[className.Length()] = ':';
    profilerName[className.Length() + 1] = ':';
    Platform::MemoryCopy(profilerName + className.Length() + 2, _name.Get(), _name.Length());
    profilerName[className.Length() + 2 + _name.Length()] = 0;
    ProfilerData.name = profilerName;
    ProfilerData.function = _name.Get();
    ProfilerData.file = nullptr;
    ProfilerData.line = 0;
    ProfilerData.color = 0;
#endif
}

void MMethod::CacheSignature() const
{
    ScopeLock lock(BinaryModule::Locker);
    if (_hasCachedSignature)
        return;
    PROFILE_MEM(ScriptingCSharp);

    static void* GetMethodReturnTypePtr = GetStaticMethodPointer(TEXT("GetMethodReturnType"));
    static void* GetMethodParameterTypesPtr = GetStaticMethodPointer(TEXT("GetMethodParameterTypes"));
    static void* GetMethodParameterIsOutPtr = GetStaticMethodPointer(TEXT("GetMethodParameterIsOut"));
    _returnType = CallStaticMethod<void*, void*>(GetMethodReturnTypePtr, _handle);
    _parameterOuts = 0;
    if (_paramsCount != 0)
    {
        void** parameterTypeHandles;
        CallStaticMethod<void, void*, void***>(GetMethodParameterTypesPtr, _handle, &parameterTypeHandles);
        _parameterTypes.Set(parameterTypeHandles, _paramsCount);
        MCore::GC::FreeMemory(parameterTypeHandles);
        _parameterOuts = CallStaticMethod<uint64, void*>(GetMethodParameterIsOutPtr, _handle);
    }

    _hasCachedSignature = true;
}

MObject* MMethod::Invoke(void* instance, void** params, MObject** exception) const
{
    PROFILE_CPU_SRC_LOC(ProfilerData);
    static void* InvokeMethodPtr = GetStaticMethodPointer(TEXT("InvokeMethod"));
    return (MObject*)CallStaticMethod<void*, void*, void*, void*, void*>(InvokeMethodPtr, instance, _handle, params, exception);
}

MObject* MMethod::InvokeVirtual(MObject* instance, void** params, MObject** exception) const
{
    return Invoke(instance, params, exception);
}

#if !USE_MONO_AOT

void* MMethod::GetThunk()
{
    if (!_cachedThunk)
    {
        static void* GetThunkPtr = GetStaticMethodPointer(TEXT("GetThunk"));
        _cachedThunk = CallStaticMethod<void*, void*>(GetThunkPtr, _handle);
#if !BUILD_RELEASE
        if (!_cachedThunk)
            LOG(Error, "Failed to get C# method thunk for {0}::{1}", String(_parentClass->GetFullName()), String(_name));
#endif
    }
    return _cachedThunk;
}

#endif

MMethod* MMethod::InflateGeneric() const
{
    // This seams to be unused on .NET (Mono required inflating generic class of the script)
    return const_cast<MMethod*>(this);
}

MType* MMethod::GetReturnType() const
{
    if (!_hasCachedSignature)
        CacheSignature();
    return (MType*)_returnType;
}

int32 MMethod::GetParametersCount() const
{
    return _paramsCount;
}

MType* MMethod::GetParameterType(int32 paramIdx) const
{
    if (!_hasCachedSignature)
        CacheSignature();
    ASSERT_LOW_LAYER(paramIdx >= 0 && paramIdx < _paramsCount);
    return (MType*)_parameterTypes.Get()[paramIdx];
}

bool MMethod::GetParameterIsOut(int32 paramIdx) const
{
    if (!_hasCachedSignature)
        CacheSignature();
    ASSERT_LOW_LAYER(paramIdx >= 0 && paramIdx < _paramsCount);
    return _parameterOuts & (1ull << paramIdx);
}

bool MMethod::HasAttribute(const MClass* klass) const
{
    return GetCustomAttribute(GetAttributes(), klass) != nullptr;
}

bool MMethod::HasAttribute() const
{
    return !GetAttributes().IsEmpty();
}

MObject* MMethod::GetAttribute(const MClass* klass) const
{
    return (MObject*)GetCustomAttribute(GetAttributes(), klass);
}

const Array<MObject*, ArenaAllocation>& MMethod::GetAttributes() const
{
    if (_hasCachedAttributes)
        return _attributes;
    PROFILE_MEM(ScriptingCSharp);
    ScopeLock lock(BinaryModule::Locker);
    if (_hasCachedAttributes)
        return _attributes;
    static void* GetMethodAttributesPtr = GetStaticMethodPointer(TEXT("GetMethodAttributes"));
    GetCustomAttributes(_attributes, _handle, GetMethodAttributesPtr);
    _hasCachedAttributes = true;
    return _attributes;
}

FORCE_INLINE StringAnsiView GetPropertyMethodName(MProperty* property, StringAnsiView prefix)
{
    StringAnsiView name = property->GetName();
    char* mem = (char*)property->GetParentClass()->GetAssembly()->Memory.Allocate(name.Length() + prefix.Length() + 1);
    Platform::MemoryCopy(mem, prefix.Get(), prefix.Length());
    Platform::MemoryCopy(mem + prefix.Length(), name.Get(), name.Length());
    mem[name.Length() + prefix.Length()] = 0;
    return StringAnsiView(mem, name.Length() + prefix.Length());
}

MProperty::MProperty(MClass* parentClass, const char* name, void* handle, void* getterHandle, void* setterHandle, MMethodAttributes getterAttributes, MMethodAttributes setterAttributes)
    : _parentClass(parentClass)
    , _name(parentClass->GetAssembly()->AllocString(name))
    , _handle(handle)
    , _hasCachedAttributes(false)
    , _attributes(&parentClass->GetAssembly()->Memory)
{
    _hasGetMethod = getterHandle != nullptr;
    if (_hasGetMethod)
        _getMethod = parentClass->GetAssembly()->Memory.New<MMethod>(parentClass, GetPropertyMethodName(this, StringAnsiView("get_", 4)), getterHandle, 0, getterAttributes);
    else
        _getMethod = nullptr;
    _hasSetMethod = setterHandle != nullptr;
    if (_hasSetMethod)
        _setMethod = parentClass->GetAssembly()->Memory.New<MMethod>(parentClass, GetPropertyMethodName(this, StringAnsiView("set_", 4)), setterHandle, 1, setterAttributes);
    else
        _setMethod = nullptr;
}

MProperty::~MProperty()
{
    if (_getMethod)
        Memory::DestructItem(_getMethod);
    if (_setMethod)
        Memory::DestructItem(_setMethod);
}

MMethod* MProperty::GetGetMethod() const
{
    return _getMethod;
}

MMethod* MProperty::GetSetMethod() const
{
    return _setMethod;
}

MObject* MProperty::GetValue(MObject* instance, MObject** exception) const
{
    CHECK_RETURN(_getMethod, nullptr);
    return _getMethod->Invoke(instance, nullptr, exception);
}

void MProperty::SetValue(MObject* instance, void* value, MObject** exception) const
{
    CHECK(_setMethod);
    void* params[1];
    params[0] = value;
    _setMethod->Invoke(instance, params, exception);
}

bool MProperty::HasAttribute(const MClass* klass) const
{
    return GetCustomAttribute(GetAttributes(), klass) != nullptr;
}

bool MProperty::HasAttribute() const
{
    return !GetAttributes().IsEmpty();
}

MObject* MProperty::GetAttribute(const MClass* klass) const
{
    return (MObject*)GetCustomAttribute(GetAttributes(), klass);
}

const Array<MObject*, ArenaAllocation>& MProperty::GetAttributes() const
{
    if (_hasCachedAttributes)
        return _attributes;
    PROFILE_MEM(ScriptingCSharp);
    ScopeLock lock(BinaryModule::Locker);
    if (_hasCachedAttributes)
        return _attributes;
    static void* GetPropertyAttributesPtr = GetStaticMethodPointer(TEXT("GetPropertyAttributes"));
    GetCustomAttributes(_attributes, _handle, GetPropertyAttributesPtr);
    _hasCachedAttributes = true;
    return _attributes;
}

MAssembly* GetAssembly(void* assemblyHandle)
{
    ScopeLock lock(BinaryModule::Locker);
    MAssembly* assembly;
    if (CachedAssemblyHandles.TryGet(assemblyHandle, assembly))
        return assembly;
    return nullptr;
}

MClass* GetClass(MType* typeHandle)
{
    ScopeLock lock(BinaryModule::Locker);
    MClass* klass = nullptr;
    CachedClassHandles.TryGet(typeHandle, klass);
    return nullptr;
}

MClass* GetOrCreateClass(MType* typeHandle)
{
    if (!typeHandle)
        return nullptr;
    PROFILE_MEM(ScriptingCSharp);
    ScopeLock lock(BinaryModule::Locker);
    MClass* klass;
    if (!CachedClassHandles.TryGet(typeHandle, klass))
    {
        NativeClassDefinitions classInfo;
        void* assemblyHandle;
        static void* GetManagedClassFromTypePtr = GetStaticMethodPointer(TEXT("GetManagedClassFromType"));
        CallStaticMethod<void, void*, void*>(GetManagedClassFromTypePtr, typeHandle, &classInfo, &assemblyHandle);
        MAssembly* assembly = GetAssembly(assemblyHandle);
        klass = assembly->Memory.New<MClass>(assembly, classInfo.typeHandle, classInfo.name, classInfo.fullname, classInfo.namespace_, classInfo.typeAttributes);
        if (assembly != nullptr)
        {
            auto& classes = assembly->GetTypeClasses();
            if (classes.ContainsKey(klass->GetFullName()))
            {
                LOG(Warning, "Class '{0}' was already added to assembly '{1}'", String(klass->GetFullName()), String(assembly->GetName()));
            }
            classes[klass->GetFullName()] = klass;
        }

        if (typeHandle != classInfo.typeHandle)
            CallStaticMethod<void, void*, void*>(GetManagedClassFromTypePtr, typeHandle, &classInfo);

        MCore::GC::FreeMemory((void*)classInfo.name);
        MCore::GC::FreeMemory((void*)classInfo.fullname);
        MCore::GC::FreeMemory((void*)classInfo.namespace_);
    }
    ASSERT(klass != nullptr);
    return klass;
}

MType* GetObjectType(MObject* obj)
{
    static void* GetObjectTypePtr = GetStaticMethodPointer(TEXT("GetObjectType"));
    void* typeHandle = CallStaticMethod<void*, void*>(GetObjectTypePtr, obj);
    return (MType*)typeHandle;
}

#if DOTNET_HOST_CORECLR

const char_t* NativeInteropTypeName = FLAX_CORECLR_TEXT("FlaxEngine.Interop.NativeInterop, FlaxEngine.CSharp");
hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config;
hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line;
hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate;
hostfxr_close_fn hostfxr_close;
load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer;
get_function_pointer_fn get_function_pointer;
hostfxr_set_error_writer_fn hostfxr_set_error_writer;
hostfxr_get_dotnet_environment_info_result_fn hostfxr_get_dotnet_environment_info_result;
hostfxr_run_app_fn hostfxr_run_app;

bool InitHostfxr()
{
    const ::String csharpLibraryPath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.dll");
    const ::String csharpRuntimeConfigPath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.runtimeconfig.json");
    if (!FileSystem::FileExists(csharpLibraryPath))
        LOG(Fatal, "Failed to initialize .NET runtime, missing file: {0}", csharpLibraryPath);
    if (!FileSystem::FileExists(csharpRuntimeConfigPath))
        LOG(Fatal, "Failed to initialize .NET runtime, missing file: {0}", csharpRuntimeConfigPath);
    const FLAX_CORECLR_STRING& libraryPath = FLAX_CORECLR_STRING(csharpLibraryPath);

    // Get path to hostfxr library
    get_hostfxr_parameters get_hostfxr_params;
    get_hostfxr_params.size = sizeof(get_hostfxr_parameters);
    get_hostfxr_params.assembly_path = libraryPath.Get();
#if PLATFORM_MAC
    ::String macOSDotnetRoot = TEXT("/usr/local/share/dotnet");
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64)
    // When emulating x64 on arm
    const ::String dotnetRootEmulated = macOSDotnetRoot / TEXT("x64");
    if (FileSystem::FileExists(dotnetRootEmulated / TEXT("dotnet"))) {
        macOSDotnetRoot = dotnetRootEmulated;
    }
#endif
    const FLAX_CORECLR_STRING& finalDotnetRootPath = FLAX_CORECLR_STRING(macOSDotnetRoot);
    get_hostfxr_params.dotnet_root = finalDotnetRootPath.Get();
#else
    get_hostfxr_params.dotnet_root = nullptr;
#endif
    FLAX_CORECLR_STRING dotnetRoot;
    String dotnetRootEnvVar;
    if (!Platform::GetEnvironmentVariable(TEXT("DOTNET_ROOT"), dotnetRootEnvVar) && FileSystem::DirectoryExists(dotnetRootEnvVar))
    {
        dotnetRoot = FLAX_CORECLR_STRING(dotnetRootEnvVar);
        get_hostfxr_params.dotnet_root = dotnetRoot.Get();
    }
#if !USE_EDITOR
    const String bundledDotnetPath = Globals::ProjectFolder / TEXT("Dotnet");
    if (FileSystem::DirectoryExists(bundledDotnetPath))
    {
        dotnetRoot = FLAX_CORECLR_STRING(bundledDotnetPath);
#if PLATFORM_WINDOWS_FAMILY
        dotnetRoot.Replace('/', '\\');
#endif
        get_hostfxr_params.dotnet_root = dotnetRoot.Get();
    }
#endif
    String platformStr;
    switch (PLATFORM_TYPE)
    {
    case PlatformType::Windows:
    case PlatformType::UWP:
        if (PLATFORM_ARCH == ArchitectureType::x64)
            platformStr = "Windows x64";
        else if (PLATFORM_ARCH == ArchitectureType::ARM64)
            platformStr = "Windows ARM64";
        else
            platformStr = "Windows x86";
        break;
    case PlatformType::Linux:
        platformStr = PLATFORM_ARCH_ARM64 ? "Linux ARM64" : PLATFORM_ARCH_ARM ? "Linux Arm32" : PLATFORM_64BITS ? "Linux x64" : "Linux x86";
        break;
    case PlatformType::Mac:
        platformStr = PLATFORM_ARCH_ARM || PLATFORM_ARCH_ARM64 ? "macOS ARM64" : PLATFORM_64BITS ? "macOS x64" : "macOS x86";
        break;
    default:
        if (PLATFORM_ARCH == ArchitectureType::x64)
            platformStr = "x64";
        else if (PLATFORM_ARCH == ArchitectureType::ARM64)
            platformStr = "ARM64";
        else
            platformStr = "x86";
    }

    char_t hostfxrPath[1024];
    size_t hostfxrPathSize = sizeof(hostfxrPath) / sizeof(char_t);
    int rc = get_hostfxr_path(hostfxrPath, &hostfxrPathSize, &get_hostfxr_params);
    if (rc != 0)
    {
        LOG(Error, "Failed to find hostfxr: {0:x} ({1})", (unsigned int)rc, String(get_hostfxr_params.dotnet_root));

        // Warn user about missing .Net
#if PLATFORM_DESKTOP
        Platform::OpenUrl(TEXT("https://dotnet.microsoft.com/en-us/download/dotnet"));
#endif
#if USE_EDITOR
        LOG(Fatal, "Missing .NET 8 or later SDK installation for {0} is required to run Flax Editor.", platformStr);
#else
        LOG(Fatal, "Missing .NET 8 or later Runtime installation for {0} is required to run this application.", platformStr);
#endif
        return true;
    }
    String path(hostfxrPath);
    LOG(Info, "Found hostfxr in {0}", path);

    // Get API from hostfxr library
    void* hostfxr = Platform::LoadLibrary(path.Get());
    if (hostfxr == nullptr)
    {
        if (FileSystem::FileExists(path))
        {
            LOG(Fatal, "Failed to load hostfxr library, possible platform/architecture mismatch with the library. See log for more information. ({0})", path);
        }
        else
        {
            LOG(Fatal, "Failed to load hostfxr library ({0})", path);
        }
        return true;
    }
    hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)Platform::GetProcAddress(hostfxr, "hostfxr_initialize_for_runtime_config");
    hostfxr_initialize_for_dotnet_command_line = (hostfxr_initialize_for_dotnet_command_line_fn)Platform::GetProcAddress(hostfxr, "hostfxr_initialize_for_dotnet_command_line");
    hostfxr_get_runtime_delegate = (hostfxr_get_runtime_delegate_fn)Platform::GetProcAddress(hostfxr, "hostfxr_get_runtime_delegate");
    hostfxr_close = (hostfxr_close_fn)Platform::GetProcAddress(hostfxr, "hostfxr_close");
    hostfxr_set_error_writer = (hostfxr_set_error_writer_fn)Platform::GetProcAddress(hostfxr, "hostfxr_set_error_writer");
    hostfxr_get_dotnet_environment_info_result = (hostfxr_get_dotnet_environment_info_result_fn)Platform::GetProcAddress(hostfxr, "hostfxr_get_dotnet_environment_info_result");
    hostfxr_run_app = (hostfxr_run_app_fn)Platform::GetProcAddress(hostfxr, "hostfxr_run_app");
    if (!hostfxr_get_runtime_delegate || !hostfxr_run_app)
    {
        LOG(Fatal, "Failed to setup hostfxr API ({0})", path);
        return true;
    }

    // TODO: Implement support for picking RC/beta updates of .NET runtime
    // Uncomment for enabling support for upcoming .NET major release candidates
#if 0
    String dotnetRollForwardPr;
    if (Platform::GetEnvironmentVariable(TEXT("DOTNET_ROLL_FORWARD_TO_PRERELEASE"), dotnetRollForwardPr))
        Platform::SetEnvironmentVariable(TEXT("DOTNET_ROLL_FORWARD_TO_PRERELEASE"), TEXT("1"));
#endif

    // Initialize hosting component
    const char_t* argv[1] = { libraryPath.Get() };
    hostfxr_initialize_parameters init_params;
    init_params.size = sizeof(hostfxr_initialize_parameters);
    init_params.host_path = libraryPath.Get();
    path = String(StringUtils::GetDirectoryName(path)) / TEXT("/../../../");
    StringUtils::PathRemoveRelativeParts(path);
    dotnetRoot = FLAX_CORECLR_STRING(path);
    init_params.dotnet_root = dotnetRoot.Get();
    hostfxr_handle handle = nullptr;
    rc = hostfxr_initialize_for_dotnet_command_line(ARRAY_COUNT(argv), argv, &init_params, &handle);
    if (rc != 0 || handle == nullptr)
    {
        hostfxr_close(handle);
        if (rc == 0x80008096) // FrameworkMissingFailure
        {
            LOG(Fatal, "Failed to resolve compatible .NET runtime version in '{0}'. Make sure the correct platform version for runtime is installed ({1})", platformStr, String(init_params.dotnet_root));
        }
        else
            LOG(Fatal, "Failed to initialize hostfxr: {0:x} ({1})", (unsigned int)rc, String(init_params.dotnet_root));
        return true;
    }

    void* pget_function_pointer = nullptr;
    rc = hostfxr_get_runtime_delegate(handle, hdt_get_function_pointer, &pget_function_pointer);
    if (rc != 0 || pget_function_pointer == nullptr)
    {
        hostfxr_close(handle);
        LOG(Fatal, "Failed to get runtime delegate hdt_get_function_pointer: 0x{0:x}", (unsigned int)rc);
        return true;
    }

    hostfxr_close(handle);
    get_function_pointer = (get_function_pointer_fn)pget_function_pointer;
    return false;
}

void ShutdownHostfxr()
{
}

void* GetStaticMethodPointer(StringView methodName)
{
    void* fun;
    if (CachedFunctions.TryGet(methodName, fun))
        return fun;
    PROFILE_CPU();
    PROFILE_MEM(ScriptingCSharp);
    const int rc = get_function_pointer(NativeInteropTypeName, FLAX_CORECLR_STRING(methodName).Get(), UNMANAGEDCALLERSONLY_METHOD, nullptr, nullptr, &fun);
    if (rc != 0)
        LOG(Fatal, "Failed to get unmanaged function pointer for method '{0}': 0x{1:x}", methodName, (unsigned int)rc);
    CachedFunctions.Add(methodName, fun);
    return fun;
}

#elif DOTNET_HOST_MONO

void OnLogCallback(const char* logDomain, const char* logLevel, const char* message, mono_bool fatal, void* userData)
{
    String msg(message);
    msg.Replace('\n', ' ');

    static const char* monoErrorLevels[] =
    {
        nullptr,
        "error",
        "critical",
        "warning",
        "message",
        "info",
        "debug"
    };

    uint32 errorLevel = 0;
    if (logLevel != nullptr)
    {
        for (uint32 i = 1; i < 7; i++)
        {
            if (strcmp(monoErrorLevels[i], logLevel) == 0)
            {
                errorLevel = i;
                break;
            }
        }
    }

#if 0
	// Print C# stack trace (crash may be caused by the managed code)
	if (mono_domain_get() && Assemblies::FlaxEngine.Assembly->IsLoaded())
	{
		const auto managedStackTrace = DebugLog::GetStackTrace();
		if (managedStackTrace.HasChars())
		{
			LOG(Warning, "Managed stack trace:");
			LOG_STR(Warning, managedStackTrace);
		}
	}
#endif

    if (errorLevel <= 2)
    {
        Log::CLRInnerException(String::Format(TEXT("[Mono] {0}"), msg)).SetLevel(LogType::Error);
    }
    else if (errorLevel <= 3)
    {
        LOG(Warning, "[Mono] {0}", msg);
    }
    else
    {
        LOG(Info, "[Mono] {0}", msg);
    }
#if DOTNET_HOST_MONO && !BUILD_RELEASE
    if (errorLevel <= 2)
    {
        // Mono backend ends with fatal assertions so capture crash info (eg. stack trace)
        CRASH;
    }
#endif
}

void OnPrintCallback(const char* string, mono_bool isStdout)
{
    LOG_STR(Warning, String(string));
}

void OnPrintErrorCallback(const char* string, mono_bool isStdout)
{
    // HACK: ignore this message
    if (string && Platform::MemoryCompare(string, "debugger-agent: Unable to listen on ", 36) == 0)
        return;

    LOG_STR(Error, String(string));
}

static MonoAssembly* OnMonoAssemblyLoad(const char* aname)
{
    PROFILE_CPU();
    ZoneText(aname, StringUtils::Length(aname));

    // Find assembly file
    const String name(aname);
#if DOTNET_HOST_MONO_DEBUG
    LOG(Info, "Loading C# assembly {0}", name);
#endif
    String fileName = name;
    if (!name.EndsWith(TEXT(".dll")) && !name.EndsWith(TEXT(".exe")))
        fileName += TEXT(".dll");
    String path = Globals::ProjectFolder / String(TEXT("/Dotnet/")) / fileName;
    if (!FileSystem::FileExists(path))
    {
        path = Globals::ProjectFolder / String(TEXT("/Dotnet/shared/Microsoft.NETCore.App/")) / fileName;
        if (!FileSystem::FileExists(path))
        {
            path = fileName;
        }
    }

    // Load assembly
#if DOTNET_HOST_MONO_DEBUG
    LOG(Info, "Loading C# assembly from path = {0}, exist = {1}", path, FileSystem::FileExists(path));
#endif
    MonoAssembly* assembly = nullptr;
    MonoImageOpenStatus status = MONO_IMAGE_IMAGE_INVALID;
    if (FileSystem::FileExists(path))
    {
        StringAnsi pathAnsi(path);
#if PLATFORM_IOS
        Array<byte> data;
        File::ReadAllBytes(path, data);
        const auto name = path.ToStringAnsi();
        const auto assemblyImage = mono_image_open_from_data_with_name(reinterpret_cast<char*>(data.Get()), data.Count(), true, &status, false, name.Get());
        if (assemblyImage)
        {
            assembly = mono_assembly_load_from_full(assemblyImage, name.Substring(0, name.Length() - 3).Get(), &status, false);
            mono_image_close(assemblyImage);
        }
#else
        assembly = mono_assembly_open(pathAnsi.Get(), &status);
#endif
    }
    if (!assembly)
    {
        LOG(Error, "Failed to load C# assembly {0}", path);
    }
    return assembly;
}

static MonoAssembly* OnMonoAssemblyPreloadHook(MonoAssemblyName* aname, char** assemblies_path, void* user_data)
{
    return OnMonoAssemblyLoad(mono_assembly_name_get_name(aname));
}

#if 0

static unsigned char* OnMonoLoadAOT(MonoAssembly* assembly, int size, void* user_data, void** out_handle)
{
    MonoAssemblyName* assemblyName = mono_assembly_get_name(assembly);
    const char* assemblyNameStr = mono_assembly_name_get_name(assemblyName);
#if DOTNET_HOST_MONO_DEBUG
    LOG(Info, "Loading AOT data for C# assembly {0}", String(assemblyNameStr));
#endif
    return nullptr;
}

static void OnMonoFreeAOT(MonoAssembly* assembly, int size, void* user_data, void* handle)
{
#if DOTNET_HOST_MONO_DEBUG
    MonoAssemblyName* assemblyName = mono_assembly_get_name(assembly);
    const char* assemblyNameStr = mono_assembly_name_get_name(assemblyName);
    LOG(Info, "Free AOT data for C# assembly {0}", String(assemblyNameStr));
#endif
}

#endif

#if PLATFORM_IOS

#include "Engine/Engine/Globals.h"
#include <mono/utils/mono-dl-fallback.h>
#include <dlfcn.h>

static void* OnMonoDlFallbackLoad(const char* name, int flags, char** err, void* user_data)
{
    PROFILE_CPU();
    ZoneText(name, StringUtils::Length(name));
    const String fileName = StringUtils::GetFileName(String(name));
#if DOTNET_HOST_MONO_DEBUG
    LOG(Info, "Loading dynamic library {0}", fileName);
#endif
    int dlFlags = 0;
    if (flags & MONO_DL_GLOBAL && !(flags & MONO_DL_LOCAL))
		dlFlags |= RTLD_GLOBAL;
	else
		dlFlags |= RTLD_LOCAL;
	if (flags & MONO_DL_LAZY)
		dlFlags |= RTLD_LAZY;
	else
		dlFlags |= RTLD_NOW;
    void* result = dlopen(name, dlFlags);
    if (!result)
    {
        // Try Frameworks location on iOS
        String path = Globals::ProjectFolder / TEXT("Frameworks") / fileName;
        if (!path.EndsWith(TEXT(".dylib")))
            path += TEXT(".dylib");
        result = dlopen(StringAsANSI<>(*path).Get(), dlFlags);
        if (!result)
        {
            LOG(Error, "Failed to load dynamic libary {0}", String(name));
        }
    }
    return result;
}

static void* OnMonoDlFallbackSymbol(void* handle, const char* name, char** err, void* user_data)
{
	return dlsym(handle, name);
}

static void* OnMonoDlFallbackClose(void* handle, void* user_data)
{
	dlclose(handle);
    return 0;
}

#endif

#ifdef USE_MONO_AOT_MODULE

#include "Engine/Threading/ThreadPoolTask.h"
#include "Engine/Engine/EngineService.h"

class MonoAotPreloadTask : public ThreadPoolTask
{
public:
    bool Run() override;
};

// Preloads in-build AOT dynamic module in async 
class MonoAotPreloadService : public EngineService
{
public:
    volatile int64 Ready = 0;
    void* Library = nullptr;

    MonoAotPreloadService()
        : EngineService(TEXT("AOT Preload"), -800)
    {
    }

    bool Init() override
    {
        New<MonoAotPreloadTask>()->Start();
        return false;
    }
};

MonoAotPreloadService MonoAotPreloadServiceInstance;

bool MonoAotPreloadTask::Run()
{
    // Load AOT module
    Stopwatch aotModuleLoadStopwatch;
    //LOG(Info, "Loading Mono AOT module...");
    MonoAotPreloadServiceInstance.Library = Platform::LoadLibrary(TEXT(USE_MONO_AOT_MODULE));
    aotModuleLoadStopwatch.Stop();
    LOG(Info, "Mono AOT module loaded in {0}ms", aotModuleLoadStopwatch.GetMilliseconds());

    Platform::AtomicStore(&MonoAotPreloadServiceInstance.Ready, 1);
    return false;
}

#endif

bool InitHostfxr()
{
#if DOTNET_HOST_MONO_DEBUG
    // Enable detailed Mono logging
    Platform::SetEnvironmentVariable(TEXT("MONO_LOG_LEVEL"), TEXT("debug"));
    Platform::SetEnvironmentVariable(TEXT("MONO_LOG_MASK"), TEXT("all"));
    //Platform::SetEnvironmentVariable(TEXT("MONO_GC_DEBUG"), TEXT("6:gc-log.txt,check-remset-consistency,nursery-canaries"));
#endif

    // Adjust GC threads suspending mode to not block attached native threads (eg. Job System)
    // https://www.mono-project.com/docs/advanced/runtime/docs/coop-suspend/
#if USE_MONO_AOT_COOP
    Platform::SetEnvironmentVariable(TEXT("MONO_THREADS_SUSPEND"), TEXT("coop"));
    Platform::SetEnvironmentVariable(TEXT("MONO_SLEEP_ABORT_LIMIT"), TEXT("5000")); // in ms
#else
    Platform::SetEnvironmentVariable(TEXT("MONO_THREADS_SUSPEND"), TEXT("preemptive"));
#endif

#if defined(USE_MONO_AOT_MODE)
    // Enable AOT mode (per-platform)
    mono_jit_set_aot_mode(USE_MONO_AOT_MODE);
#endif
    
    // Platform-specific setup
#if PLATFORM_IOS || PLATFORM_SWITCH || PLATFORM_PS4 || PLATFORM_PS5
    Platform::SetEnvironmentVariable(TEXT("MONO_AOT_MODE"), TEXT("aot"));
    Platform::SetEnvironmentVariable(TEXT("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"), TEXT("1"));
#endif

#ifdef USE_MONO_AOT_MODULE
    // Wait for AOT module preloading
    while (Platform::AtomicRead(&MonoAotPreloadServiceInstance.Ready) == 0)
        Platform::Yield();

    // Initialize AOT module
    void* libAotModule = MonoAotPreloadServiceInstance.Library;
    if (libAotModule == nullptr)
    {
        LOG(Error, "Failed to laod Mono AOT module (" TEXT(USE_MONO_AOT_MODULE) ")");
        return true;
    }
    MonoAotModuleHandle = libAotModule;
    void* getModulesPtr = Platform::GetProcAddress(libAotModule, "GetMonoModules");
    if (getModulesPtr == nullptr)
    {
        LOG(Error, "Failed to get Mono AOT modules getter.");
        return true;
    }
    typedef int (*GetMonoModulesFunc)(void** buffer, int bufferSize);
    const auto getModules = (GetMonoModulesFunc)getModulesPtr;
    const int32 moduelsCount = getModules(nullptr, 0);
    void** modules = (void**)Allocator::Allocate(moduelsCount * sizeof(void*));
    getModules(modules, moduelsCount);
    for (int32 i = 0; i < moduelsCount; i++)
    {
        mono_aot_register_module((void**)modules[i]);
    }
    Allocator::Free(modules);
#endif

    // Setup debugger
    {
        int32 debuggerLogLevel = 0;
        if (CommandLine::Options.MonoLog.IsTrue() || DOTNET_HOST_MONO_DEBUG)
        {
            LOG(Info, "Using detailed Mono logging");
            mono_trace_set_level_string("debug");
            debuggerLogLevel = 10;
        }
        else
        {
            mono_trace_set_level_string("warning");
        }

#if MONO_DEBUG_ENABLE && !PLATFORM_SWITCH
        StringAnsi debuggerIp = "127.0.0.1";
        uint16 debuggerPort = 41000 + Platform::GetCurrentProcessId() % 1000;
        if (CommandLine::Options.DebuggerAddress.HasValue())
        {
            const auto& address = CommandLine::Options.DebuggerAddress.GetValue();
            const int32 splitIndex = address.Find(':');
            if (splitIndex == INVALID_INDEX)
            {
                debuggerIp = address.ToStringAnsi();
            }
            else
            {
                debuggerIp = address.Left(splitIndex).ToStringAnsi();
                StringUtils::Parse(address.Right(address.Length() - splitIndex - 1).Get(), &debuggerPort);
            }
        }

        char buffer[150];
        sprintf(buffer, "--debugger-agent=transport=dt_socket,address=%s:%d,embedding=1,server=y,suspend=%s,loglevel=%d", debuggerIp.Get(), debuggerPort, CommandLine::Options.WaitForDebugger ? "y,timeout=5000" : "n", debuggerLogLevel);

        const char* options[] = {
            "--soft-breakpoints",
            //"--optimize=float32",
            buffer
        };
        mono_jit_parse_options(ARRAY_COUNT(options), (char**)options);

        mono_debug_init(MONO_DEBUG_FORMAT_MONO, 0);
        LOG(Info, "Mono debugger server at {0}:{1}", ::String(debuggerIp), debuggerPort);
#endif
    }

    // Connect to mono engine callback system
    mono_trace_set_log_handler(OnLogCallback, nullptr);
    mono_trace_set_print_handler(OnPrintCallback);
    mono_trace_set_printerr_handler(OnPrintErrorCallback);

    // Initialize Mono VM
    StringAnsi baseDirectory(Globals::ProjectFolder);
    const char* appctxKeys[] =
    {
        "RUNTIME_IDENTIFIER",
        "APP_CONTEXT_BASE_DIRECTORY",
    };
    const char* appctxValues[] =
    {
        MACRO_TO_STR(DOTNET_HOST_RUNTIME_IDENTIFIER),
        baseDirectory.Get(),
    };
    static_assert(ARRAY_COUNT(appctxKeys) == ARRAY_COUNT(appctxValues), "Invalid appctx setup");
    monovm_initialize(ARRAY_COUNT(appctxKeys), appctxKeys, appctxValues);
    mono_install_assembly_preload_hook(OnMonoAssemblyPreloadHook, nullptr);
#if 0
    mono_install_load_aot_data_hook(OnMonoLoadAOT, OnMonoFreeAOT, nullptr);
#endif
#if PLATFORM_IOS
    mono_dl_fallback_register(OnMonoDlFallbackLoad, OnMonoDlFallbackSymbol, OnMonoDlFallbackClose, nullptr);
#endif

    // Init managed runtime
#if PLATFORM_ANDROID || PLATFORM_IOS
    const char* monoVersion = "mobile";
#else
    const char* monoVersion = ""; // ignored
#endif
    MonoDomainHandle = mono_jit_init_version("Flax", monoVersion);
    if (!MonoDomainHandle)
    {
        LOG(Fatal, "Failed to initialize Mono.");
        return true;
    }
    mono_gc_init_finalizer_thread();

    // Log info
    char* buildInfo = mono_get_runtime_build_info();
    LOG(Info, "Mono runtime version: {0}", String(buildInfo));
    mono_free(buildInfo);

#if PLATFORM_ANDROID
    // Fix native crashes handling on Android
    extern void AndroidRegisterCrashHandling();
    AndroidRegisterCrashHandling();
#endif

    return false;
}

void ShutdownHostfxr()
{
    mono_jit_cleanup(MonoDomainHandle);
    MonoDomainHandle = nullptr;

#ifdef USE_MONO_AOT_MODULE
    Platform::FreeLibrary(MonoAotModuleHandle);
#endif
}

void* GetStaticMethodPointer(StringView methodName)
{
    void* fun;
    if (CachedFunctions.TryGet(methodName, fun))
        return fun;
    PROFILE_CPU();
    PROFILE_MEM(ScriptingCSharp);

    static MonoClass* nativeInteropClass = nullptr;
    if (!nativeInteropClass)
    {
        const char* assemblyName = "FlaxEngine.CSharp";
        const char* className = "FlaxEngine.Interop.NativeInterop";
        MonoAssembly* flaxEngineAssembly = OnMonoAssemblyLoad(assemblyName);
        ASSERT(flaxEngineAssembly);
        MonoType* interopTyp = mono_reflection_type_from_name((char*)className, mono_assembly_get_image(flaxEngineAssembly));
        ASSERT(interopTyp);
        nativeInteropClass = mono_class_from_mono_type(interopTyp);
        ASSERT(nativeInteropClass);
    }
    const StringAsUTF8<40> methodNameAnsi(methodName.Get(), methodName.Length());
    MonoMethod* method = mono_class_get_method_from_name(nativeInteropClass, methodNameAnsi.Get(), -1);
    ASSERT(method);

    MonoError error;
    mono_error_init(&error);
    fun = mono_method_get_unmanaged_callers_only_ftnptr(method, &error);
    if (fun == nullptr)
    {
        const unsigned short errorCode = mono_error_get_error_code(&error);
        const char* errorMessage = mono_error_get_message(&error);
        LOG(Fatal, "Failed to get unmanaged function pointer for method '{0}': 0x{1:x}, {2}", methodName, errorCode, String(errorMessage));
    }
    mono_error_cleanup(&error);

    CachedFunctions.Add(methodName, fun);
    return fun;
}

#endif

#endif
