using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
#if !UNITY_DOTSPLAYER && !NET_DOTS
using System.Collections.Generic;
using UnityEngine.Scripting;
using System.Linq;
#endif
using System.Text;

namespace Unity.Burst
{
    /// <summary>
    /// The burst compiler runtime frontend.
    /// </summary>
    ///
    public static class BurstCompiler
    {
        /// <summary>
        /// Check if the LoadAdditionalLibrary API is supported by the current version of Unity
        /// </summary>
        /// <returns>True if the LoadAdditionalLibrary API can be used by the current version of Unity</returns>
        public static bool IsLoadAdditionalLibrarySupported()
        {
            return IsApiAvailable("LoadBurstLibrary");
        }

#if !UNITY_DOTSPLAYER && !NET_DOTS
#if UNITY_EDITOR
        static unsafe BurstCompiler()
        {
            // Store pointers to Log and Compile callback methods.
            // For more info about why we need to do this, see comments in CallbackStubManager.
            string GetFunctionPointer<TDelegate>(TDelegate callback)
            {
                GCHandle.Alloc(callback); // Ensure delegate is never garbage-collected.
                var callbackFunctionPointer = Marshal.GetFunctionPointerForDelegate(callback);
                return "0x" + callbackFunctionPointer.ToInt64().ToString("X16");
            }

            EagerCompileLogCallbackFunctionPointer = GetFunctionPointer<LogCallbackDelegate>(EagerCompileLogCallback);
            ManagedResolverFunctionPointer = GetFunctionPointer<ManagedFnPtrResolverDelegate>(ManagedResolverFunction);
            ProgressCallbackFunctionPointer = GetFunctionPointer<ProgressCallbackDelegate>(ProgressCallback);
            ProfileBeginCallbackFunctionPointer = GetFunctionPointer<ProfileBeginCallbackDelegate>(ProfileBeginCallback);
            ProfileEndCallbackFunctionPointer = GetFunctionPointer<ProfileEndCallbackDelegate>(ProfileEndCallback);
        }
#endif

        private class CommandBuilder
        {
            private StringBuilder _builder;
            private bool _hasArgs;

            public CommandBuilder()
            {
                _builder = new StringBuilder();
                _hasArgs = false;
            }

            public CommandBuilder Begin(string cmd)
            {
                _builder.Clear();
                _hasArgs = false;
                _builder.Append(cmd);
                return this;
            }

            public CommandBuilder With(string arg)
            {
                if (!_hasArgs) _builder.Append(' ');
                _hasArgs = true;
                _builder.Append(arg);
                return this;
            }

            public CommandBuilder With(IntPtr arg)
            {
                if (!_hasArgs) _builder.Append(' ');
                _hasArgs = true;
                _builder.AppendFormat("0x{0:X16}", arg.ToInt64());
                return this;
            }

            public CommandBuilder And(char sep = '|')
            {
                _builder.Append(sep);
                return this;
            }

            public string SendToCompiler()
            {
                return SendRawCommandToCompiler(_builder.ToString());
            }
        }

        [ThreadStatic]
        private static CommandBuilder _cmdBuilder;

        private static CommandBuilder BeginCompilerCommand(string cmd)
        {
            if (_cmdBuilder == null)
            {
                _cmdBuilder = new CommandBuilder();
            }

            return _cmdBuilder.Begin(cmd);
        }

#if BURST_INTERNAL
        [ThreadStatic]
        public static Func<object, IntPtr> InternalCompiler;
#endif

        /// <summary>
        /// Internal variable setup by BurstCompilerOptions.
        /// </summary>
#if BURST_INTERNAL

        [ThreadStatic] // As we are changing this boolean via BurstCompilerOptions in btests and we are running multithread tests
                       // we would change a global and it would generate random errors, so specifically for btests, we are using a TLS.
        public
#else
        internal
#endif
        static bool _IsEnabled;

        /// <summary>
        /// Gets a value indicating whether Burst is enabled.
        /// </summary>
#if UNITY_EDITOR || BURST_INTERNAL
        public static bool IsEnabled => _IsEnabled;
#else
        public static bool IsEnabled => _IsEnabled && BurstCompilerHelper.IsBurstGenerated;
#endif

        /// <summary>
        /// Gets the global options for the burst compiler.
        /// </summary>
        public static readonly BurstCompilerOptions Options = new BurstCompilerOptions(true);

        /// <summary>
        /// Sets the execution mode for all jobs spawned from now on.
        /// </summary>
        /// <param name="mode">Specifiy the required execution mode</param>
        public static void SetExecutionMode(BurstExecutionEnvironment mode)
        {
            Burst.LowLevel.BurstCompilerService.SetCurrentExecutionMode((uint)mode);
        }
        /// <summary>
        /// Retrieve the current execution mode that is configured.
        /// </summary>
        /// <returns>Currently configured execution mode</returns>
        public static BurstExecutionEnvironment GetExecutionMode()
        {
            return (BurstExecutionEnvironment)Burst.LowLevel.BurstCompilerService.GetCurrentExecutionMode();
        }

        /// <summary>
        /// Compile the following delegate with burst and return a new delegate.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="delegateMethod"></param>
        /// <returns></returns>
        /// <remarks>NOT AVAILABLE, unsafe to use</remarks>
        internal static unsafe T CompileDelegate<T>(T delegateMethod) where T : class
        {
            // We have added support for runtime CompileDelegate in 2018.2+
            void* function = Compile(delegateMethod, false);
            object res = System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer((IntPtr)function, delegateMethod.GetType());
            return (T)res;
        }

        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
        private static void VerifyDelegateIsNotMulticast<T>(T delegateMethod) where T : class
        {
            var delegateKind = delegateMethod as Delegate;
            if (delegateKind.GetInvocationList().Length > 1)
            {
                throw new InvalidOperationException($"Burst does not support multicast delegates, please use a regular delegate for `{delegateMethod}'");
            }
        }

        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
        private static void VerifyDelegateHasCorrectUnmanagedFunctionPointerAttribute<T>(T delegateMethod) where T : class
        {
            var attrib = delegateMethod.GetType().GetCustomAttribute<System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute>();
            if (attrib == null || attrib.CallingConvention != CallingConvention.Cdecl)
            {
#if !BURST_INTERNAL
                UnityEngine.Debug.LogWarning($"The delegate type {delegateMethod.GetType().FullName} should be decorated with [UnmanagedFunctionPointer(CallingConvention.Cdecl)] to ensure runtime interoperabilty between managed code and Burst-compiled code.");
#endif
            }
        }

        /// <summary>
        /// DO NOT USE - deprecated.
        /// </summary>
        /// <param name="burstMethodHandle">The Burst method to compile.</param>
        /// <param name="managedMethodHandle">The fallback managed method to use.</param>
        /// <param name="delegateTypeHandle">The type of the delegate used to execute these methods.</param>
        /// <returns>Nothing</returns>
        [Obsolete("This method will be removed in a future version of Burst")]
        public static unsafe IntPtr CompileILPPMethod(RuntimeMethodHandle burstMethodHandle, RuntimeMethodHandle managedMethodHandle, RuntimeTypeHandle delegateTypeHandle)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Compile an IL Post-Processed method.
        /// </summary>
        /// <param name="burstMethodHandle">The Burst method to compile.</param>
        /// <returns>A token that must be passed to <see cref="GetILPPMethodFunctionPointer2"/> to get an actual executable function pointer.</returns>
        public static unsafe IntPtr CompileILPPMethod2(RuntimeMethodHandle burstMethodHandle)
        {
            if (burstMethodHandle.Value == IntPtr.Zero)
            {
                throw new ArgumentNullException(nameof(burstMethodHandle));
            }

            OnCompileILPPMethod2?.Invoke();

            var burstMethod = (MethodInfo)MethodBase.GetMethodFromHandle(burstMethodHandle);

            return (IntPtr)Compile(new FakeDelegate(burstMethod), burstMethod, isFunctionPointer: true, isILPostProcessing: true);
        }

        internal static Action OnCompileILPPMethod2;

        /// <summary>
        /// DO NOT USE - deprecated.
        /// </summary>
        /// <param name="ilppMethod">The result of a previous call to <see cref="CompileILPPMethod"/>.</param>
        /// <returns>Nothing.</returns>
        [Obsolete("This method will be removed in a future version of Burst")]
        public static unsafe void* GetILPPMethodFunctionPointer(IntPtr ilppMethod)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// For a previous call to <see cref="CompileILPPMethod2"/>, get the actual executable function pointer.
        /// </summary>
        /// <param name="ilppMethod">The result of a previous call to <see cref="CompileILPPMethod"/>.</param>
        /// <param name="managedMethodHandle">The fallback managed method to use.</param>
        /// <param name="delegateTypeHandle">The type of the delegate used to execute these methods.</param>
        /// <returns>A pointer into an executable region, for running the function pointer.</returns>
        public static unsafe void* GetILPPMethodFunctionPointer2(IntPtr ilppMethod, RuntimeMethodHandle managedMethodHandle, RuntimeTypeHandle delegateTypeHandle)
        {
            if (ilppMethod == IntPtr.Zero)
            {
                throw new ArgumentNullException(nameof(ilppMethod));
            }

            if (managedMethodHandle.Value == IntPtr.Zero)
            {
                throw new ArgumentNullException(nameof(managedMethodHandle));
            }

            if (delegateTypeHandle.Value == IntPtr.Zero)
            {
                throw new ArgumentNullException(nameof(delegateTypeHandle));
            }

            // If we are in the editor, we need to route a command to the compiler to start compiling the deferred ILPP compilation.
            // Otherwise if we're in Burst's internal testing, or in a player build, we already actually have the actual executable
            // pointer address, and we just return that.
#if UNITY_EDITOR
            var managedMethod = (MethodInfo)MethodBase.GetMethodFromHandle(managedMethodHandle);
            var delegateType = Type.GetTypeFromHandle(delegateTypeHandle);
            var managedFallbackDelegate = Delegate.CreateDelegate(delegateType, managedMethod);

            var handle = GCHandle.Alloc(managedFallbackDelegate);

            var result =
                BeginCompilerCommand(BurstCompilerOptions.CompilerCommandILPPCompilation)
                    .With(ilppMethod).And()
                    .With(ManagedResolverFunctionPointer).And()
                    .With(GCHandle.ToIntPtr(handle))
                    .SendToCompiler();

            return new IntPtr(Convert.ToInt64(result, 16)).ToPointer();
#else
            return ilppMethod.ToPointer();
#endif
        }

        /// <summary>
        /// DO NOT USE - deprecated.
        /// </summary>
        /// <param name="handle">A runtime method handle.</param>
        /// <returns>Nothing.</returns>
        [Obsolete("This method will be removed in a future version of Burst")]
        public static unsafe void* CompileUnsafeStaticMethod(RuntimeMethodHandle handle)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Compile the following delegate into a function pointer with burst, invokable from a Burst Job or from regular C#.
        /// </summary>
        /// <typeparam name="T">Type of the delegate of the function pointer</typeparam>
        /// <param name="delegateMethod">The delegate to compile</param>
        /// <returns>A function pointer invokable from a Burst Job or from regular C#</returns>
        public static unsafe FunctionPointer<T> CompileFunctionPointer<T>(T delegateMethod) where T : class
        {
            VerifyDelegateIsNotMulticast<T>(delegateMethod);
            VerifyDelegateHasCorrectUnmanagedFunctionPointerAttribute<T>(delegateMethod);
            // We have added support for runtime CompileDelegate in 2018.2+
            void* function = Compile(delegateMethod, true);
            return new FunctionPointer<T>(new IntPtr(function));
        }

        [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
        internal class StaticTypeReinitAttribute : Attribute
        {
            public readonly Type reinitType;

            public StaticTypeReinitAttribute(Type toReinit)
            {
                reinitType = toReinit;
            }
        }

        private static unsafe void* Compile(object delegateObj, bool isFunctionPointer)
        {
            if (!(delegateObj is Delegate)) throw new ArgumentException("object instance must be a System.Delegate", nameof(delegateObj));
            var delegateMethod = (Delegate)delegateObj;
            return Compile(delegateMethod, delegateMethod.Method, isFunctionPointer, false);
        }

        private static unsafe void* Compile(object delegateObj, MethodInfo methodInfo, bool isFunctionPointer, bool isILPostProcessing)
        {
            if (delegateObj == null) throw new ArgumentNullException(nameof(delegateObj));

            if (delegateObj.GetType().IsGenericType)
            {
                throw new InvalidOperationException($"The delegate type `{delegateObj.GetType()}` must be a non-generic type");
            }
            if (!methodInfo.IsStatic)
            {
                throw new InvalidOperationException($"The method `{methodInfo}` must be static. Instance methods are not supported");
            }
            if (methodInfo.IsGenericMethod)
            {
                throw new InvalidOperationException($"The method `{methodInfo}` must be a non-generic method");
            }

#if ENABLE_IL2CPP
            if (isFunctionPointer && !isILPostProcessing &&
                methodInfo.GetCustomAttributes().All(s => s.GetType().Name != "MonoPInvokeCallbackAttribute"))
            {
                UnityEngine.Debug.Log($"The method `{methodInfo}` must have `MonoPInvokeCallback` attribute to be compatible with IL2CPP!");
            }
#endif

            void* function;

#if BURST_INTERNAL
            // Internally in Burst tests, we callback the C# method instead
            function = (void*)InternalCompiler(delegateObj);
#else

            Delegate managedFallbackDelegateMethod = null;

            if (!isILPostProcessing)
            {
                managedFallbackDelegateMethod = delegateObj as Delegate;
            }

            var delegateMethod = delegateObj as Delegate;

#if UNITY_EDITOR
            string defaultOptions;

            // In case Burst is disabled entirely from the command line
            if (BurstCompilerOptions.ForceDisableBurstCompilation)
            {
                if (isILPostProcessing)
                {
                    return null;
                }
                else
                {
                    GCHandle.Alloc(managedFallbackDelegateMethod);
                    function = (void*)Marshal.GetFunctionPointerForDelegate(managedFallbackDelegateMethod);
                    return function;
                }
            }

            if (isILPostProcessing)
            {
                defaultOptions = "--" + BurstCompilerOptions.OptionJitIsForFunctionPointer + "\n";
            }
            else if (isFunctionPointer)
            {
                defaultOptions = "--" + BurstCompilerOptions.OptionJitIsForFunctionPointer + "\n";
                // Make sure that the delegate will never be collected
                var delHandle = GCHandle.Alloc(managedFallbackDelegateMethod);
                defaultOptions += "--" + BurstCompilerOptions.OptionJitManagedDelegateHandle + "0x" + ManagedResolverFunctionPointer + "|" + "0x" + GCHandle.ToIntPtr(delHandle).ToInt64().ToString("X16");
            }
            else
            {
                defaultOptions = "--" + BurstCompilerOptions.OptionJitEnableSynchronousCompilation;
            }

            string extraOptions;
            // The attribute is directly on the method, so we recover the underlying method here
            if (Options.TryGetOptions(methodInfo, out extraOptions, isForILPostProcessing: isILPostProcessing))
            {
                if (!string.IsNullOrWhiteSpace(extraOptions))
                {
                    defaultOptions += "\n" + extraOptions;
                }

                var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(delegateObj, defaultOptions);
                function = Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId);
            }
#else
            // The attribute is directly on the method, so we recover the underlying method here
            if (BurstCompilerOptions.HasBurstCompileAttribute(methodInfo))
            {
                if (Options.EnableBurstCompilation && BurstCompilerHelper.IsBurstGenerated)
                {
                    var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(delegateObj, string.Empty);
                    function = Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId);
                }
                else
                {
                    // If this is for direct-call, and we're in a player, with Burst disabled, then we should return null,
                    // since we don't actually have a managedFallbackDelegateMethod at this point.
                    if (isILPostProcessing)
                    {
                        return null;
                    }

                    // Make sure that the delegate will never be collected
                    GCHandle.Alloc(managedFallbackDelegateMethod);
                    // If we are in a standalone player, and burst is disabled and we are actually
                    // trying to load a function pointer, in that case we need to support it
                    // so we are then going to use the managed function directly
                    // NOTE: When running under IL2CPP, this could lead to a `System.NotSupportedException : To marshal a managed method, please add an attribute named 'MonoPInvokeCallback' to the method definition.`
                    // so in that case, the method needs to have `MonoPInvokeCallback`
                    // but that's a requirement for IL2CPP, not an issue with burst
                    function = (void*)Marshal.GetFunctionPointerForDelegate(managedFallbackDelegateMethod);
                }
            }
#endif
            else
            {
                throw new InvalidOperationException($"Burst cannot compile the function pointer `{methodInfo}` because the `[BurstCompile]` attribute is missing");
            }
#endif
            // Should not happen but in that case, we are still trying to generated an error
            // It can be null if we are trying to compile a function in a standalone player
            // and the function was not compiled. In that case, we need to output an error
            if (function == null)
            {
                throw new InvalidOperationException($"Burst failed to compile the function pointer `{methodInfo}`");
            }

            // When burst compilation is disabled, we are still returning a valid stub function pointer (the a pointer to the managed function)
            // so that CompileFunctionPointer actually returns a delegate in all cases
            return function;
        }

        /// <summary>
        /// Lets the compiler service know we are shutting down, called by the event on OnDomainUnload, if EditorApplication.quitting was called
        /// </summary>
        internal static void Shutdown()
        {
#if UNITY_EDITOR
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandShutdown);
#endif
        }

#if UNITY_EDITOR
        internal static void SetDefaultOptions()
        {
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandSetDefaultOptions, Options.GetOptions(isForCompilerClient: true));
        }
#endif

#if UNITY_EDITOR
        // We need this to be queried each domain reload in a static constructor so that it is called on the main thread only!
        internal static readonly bool IsScriptDebugInfoEnabled = UnityEditor.Compilation.CompilationPipeline.IsScriptDebugInfoEnabled();

        private sealed class DomainReloadStateSingleton : UnityEditor.ScriptableSingleton<DomainReloadStateSingleton>
        {
            public bool AlreadyLoaded = false;
            public bool IsScriptDebugInfoEnabled = false;
        }

        internal static bool WasScriptDebugInfoEnabledAtDomainReload => DomainReloadStateSingleton.instance.IsScriptDebugInfoEnabled;

        internal static void DomainReload()
        {
            const string parameterSeparator = "***";
            const string assemblySeparator = "```";

            var isScriptDebugInfoEnabled = IsScriptDebugInfoEnabled;

            var cmdBuilder =
                BeginCompilerCommand(BurstCompilerOptions.CompilerCommandDomainReload)
                    .With(ProgressCallbackFunctionPointer)
                    .With(parameterSeparator)
                    .With(EagerCompileLogCallbackFunctionPointer)
                    .With(parameterSeparator)
                    .With(isScriptDebugInfoEnabled ? "Debug" : "Release")
                    .With(parameterSeparator);

            // We need to send the list of assemblies if
            // (a) we have never done that before in this Editor instance, or
            // (b) we have done it before, but now the scripting code optimization mode has changed
            //     from Debug to Release or vice-versa.
            // This is because these are the two cases in which CompilerClient will be
            // destroyed and recreated.
            if (!DomainReloadStateSingleton.instance.AlreadyLoaded ||
                DomainReloadStateSingleton.instance.IsScriptDebugInfoEnabled != isScriptDebugInfoEnabled)
            {
                // Gather list of assemblies to compile (only actually used at Editor startup)
                var assemblyNames = UnityEditor.Compilation.CompilationPipeline
                    .GetAssemblies(UnityEditor.Compilation.AssembliesType.Editor)
                    .Where(x => File.Exists(x.outputPath)) // If C# compilation fails, it won't exist on disk
                    .Select(x => $"{x.name}|{string.Join(";", x.defines)}");

                foreach (var assemblyName in assemblyNames)
                {
                    cmdBuilder.With(assemblyName)
                              .With(assemblySeparator);
                }

                DomainReloadStateSingleton.instance.AlreadyLoaded = true;
                DomainReloadStateSingleton.instance.IsScriptDebugInfoEnabled = IsScriptDebugInfoEnabled;
            }

            cmdBuilder.SendToCompiler();
        }

        internal static string VersionNotify(string version)
        {
            return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandVersionNotification, version);
        }

        internal static string GetTargetCpuFromHost()
        {
            return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandGetTargetCpuFromHost);
        }
#endif

        /// <summary>
        /// Cancel any compilation being processed by the JIT Compiler in the background.
        /// </summary>
        internal static void Cancel()
        {
#if UNITY_EDITOR
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandCancel);
#endif
        }

        /// <summary>
        /// Check if there is any job pending related to the last compilation ID.
        /// </summary>
        internal static bool IsCurrentCompilationDone()
        {
#if UNITY_EDITOR
            return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandIsCurrentCompilationDone) == "True";
#else
            return true;
#endif
        }

        internal static void Enable()
        {
#if UNITY_EDITOR
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandEnableCompiler);
#endif
        }

        internal static void Disable()
        {
#if UNITY_EDITOR
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandDisableCompiler);
#endif
        }

        internal static bool IsHostEditorArm()
        {
#if UNITY_EDITOR
            return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandIsArmTestEnv)=="true";
#else
            return false;
#endif
        }

        internal static void TriggerUnsafeStaticMethodRecompilation()
        {
            foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                var reinitAttributes = asm.GetCustomAttributes().Where(
                    x => x.GetType().FullName == "Unity.Burst.BurstCompiler+StaticTypeReinitAttribute"
                    );
                foreach (var attribute in reinitAttributes)
                {
                    var ourAttribute = attribute as StaticTypeReinitAttribute;
                    var type = ourAttribute.reinitType;
                    var method = type.GetMethod("Constructor",BindingFlags.Static|BindingFlags.Public);
                    method.Invoke(null, new object[] { });
                }
            }
        }

        internal static void TriggerRecompilation()
        {
#if UNITY_EDITOR
            SetDefaultOptions();

            // This is done separately from CompilerCommandTriggerRecompilation below,
            // because CompilerCommandTriggerRecompilation will cause all jobs to re-request
            // their function pointers from Burst, and we need to have actually triggered
            // compilation by that point.
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandTriggerSetupRecompilation);

            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandTriggerRecompilation, Options.RequiresSynchronousCompilation.ToString());
#endif
        }

        internal static void UnloadAdditionalLibraries()
        {
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandUnloadBurstNatives);
        }

        internal static void InitialiseDebuggerHooks()
        {
            if (IsApiAvailable("BurstManagedDebuggerPluginV1"))
            {
                SendCommandToCompiler(SendCommandToCompiler(BurstCompilerOptions.CompilerCommandRequestInitialiseDebuggerCommmand));
            }
        }

        internal static bool IsApiAvailable(string apiName)
        {
            return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandIsNativeApiAvailable, apiName) == "True";
        }

        internal static int RequestSetProtocolVersion(int version)
        {
            // Ask editor for the maximum version of the protocol we support, then inform the rest of the systems the negotiated version
            var editorVersion = SendCommandToCompiler(BurstCompilerOptions.CompilerCommandRequestSetProtocolVersionEditor, $"{version}");
            if (string.IsNullOrEmpty(editorVersion) || !int.TryParse(editorVersion, out var result))
            {
                result=0;
            }
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandSetProtocolVersionBurst, $"{result}");
            return result;
        }



#if UNITY_EDITOR
        private unsafe delegate void LogCallbackDelegate(void* userData, int logType, byte* message, byte* fileName, int lineNumber);

        private static unsafe void EagerCompileLogCallback(void* userData, int logType, byte* message, byte* fileName, int lineNumber)
        {
            if (EagerCompilationLoggingEnabled)
            {
                BurstRuntime.Log(message, logType, fileName, lineNumber);
            }
        }


        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate IntPtr ManagedFnPtrResolverDelegate(IntPtr handleVal);

        private static IntPtr ManagedResolverFunction(IntPtr handleVal)
        {
            var delegateObj = GCHandle.FromIntPtr(handleVal).Target;
            var fnptr = Marshal.GetFunctionPointerForDelegate(delegateObj);
            return fnptr;
        }

        internal static bool EagerCompilationLoggingEnabled = false;

        private static readonly string EagerCompileLogCallbackFunctionPointer;
        private static readonly string ManagedResolverFunctionPointer;
#endif

        internal static void Initialize(string[] assemblyFolders)
        {
#if UNITY_EDITOR
            var assemblyFoldersSerialized = SafeStringArrayHelper.SerialiseStringArraySafe(assemblyFolders);
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandInitialize, assemblyFoldersSerialized);
#endif
        }

        internal static void NotifyCompilationStarted(string[] assemblyFolders)
        {
#if UNITY_EDITOR
            var assemblyFoldersSerialized = SafeStringArrayHelper.SerialiseStringArraySafe(assemblyFolders);
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyCompilationStarted, assemblyFoldersSerialized);
#endif
        }

        internal static void NotifyAssemblyCompilationNotRequired(string assemblyName)
        {
#if UNITY_EDITOR
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyAssemblyCompilationNotRequired, assemblyName);
#endif
        }

        internal static void NotifyAssemblyCompilationFinished(string assemblyName, string[] defines)
        {
#if UNITY_EDITOR
            BeginCompilerCommand(BurstCompilerOptions.CompilerCommandNotifyAssemblyCompilationFinished)
                .With(assemblyName).And()
                .With(string.Join(";", defines))
                .SendToCompiler();
#endif
        }

        internal static void NotifyCompilationFinished()
        {
#if UNITY_EDITOR
            SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyCompilationFinished);
#endif
        }

        internal static string AotCompilation(string[] assemblyFolders, string[] assemblyRoots, string options)
        {
            var result = "failed";
#if UNITY_EDITOR
            result = SendCommandToCompiler(
                BurstCompilerOptions.CompilerCommandAotCompilation,
                BurstCompilerOptions.SerialiseCompilationOptionsSafe(assemblyRoots, assemblyFolders, options));
#endif
            return result;
        }

#if UNITY_EDITOR
        private static readonly string ProgressCallbackFunctionPointer;

        private delegate void ProgressCallbackDelegate(int current, int total);

        private static void ProgressCallback(int current, int total)
        {
            OnProgress?.Invoke(current, total);
        }

        internal static event Action<int, int> OnProgress;
#endif

        internal static void SetProfilerCallbacks()
        {
#if UNITY_EDITOR
            BeginCompilerCommand(BurstCompilerOptions.CompilerCommandSetProfileCallbacks)
                .With(ProfileBeginCallbackFunctionPointer).And(';')
                .With(ProfileEndCallbackFunctionPointer)
                .SendToCompiler();
#endif
        }

#if UNITY_EDITOR
        internal delegate void ProfileBeginCallbackDelegate(string markerName, string metadataName, string metadataValue);
        internal delegate void ProfileEndCallbackDelegate(string markerName);

        private static readonly string ProfileBeginCallbackFunctionPointer;
        private static readonly string ProfileEndCallbackFunctionPointer;

        private static void ProfileBeginCallback(string markerName, string metadataName, string metadataValue) => OnProfileBegin?.Invoke(markerName, metadataName, metadataValue);
        private static void ProfileEndCallback(string markerName) => OnProfileEnd?.Invoke(markerName);

        internal static event ProfileBeginCallbackDelegate OnProfileBegin;
        internal static event ProfileEndCallbackDelegate OnProfileEnd;
#endif


        private static string SendRawCommandToCompiler(string command)
        {
            var results = Unity.Burst.LowLevel.BurstCompilerService.GetDisassembly(DummyMethodInfo, command);
            if (!string.IsNullOrEmpty(results))
                return results.TrimStart('\n');
            return "";
        }

        private static string SendCommandToCompiler(string commandName, string commandArgs = null)
        {
            if (commandName == null) throw new ArgumentNullException(nameof(commandName));

            if (commandArgs == null)
            {
                // If there are no arguments then there's no reason to go through the builder
                return SendRawCommandToCompiler(commandName);
            }

            // Otherwise use the builder for building the final command
            return BeginCompilerCommand(commandName)
                    .With(commandArgs)
                    .SendToCompiler();
        }

        private static readonly MethodInfo DummyMethodInfo = typeof(BurstCompiler).GetMethod(nameof(DummyMethod), BindingFlags.Static | BindingFlags.NonPublic);

        /// <summary>
        /// Dummy empty method for being able to send a command to the compiler
        /// </summary>
        private static void DummyMethod() { }

#if !UNITY_EDITOR && !BURST_INTERNAL
        /// <summary>
        /// Internal class to detect at standalone player time if AOT settings were enabling burst.
        /// </summary>
        [BurstCompile]
        internal static class BurstCompilerHelper
        {
            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
            private delegate bool IsBurstEnabledDelegate();
            private static readonly IsBurstEnabledDelegate IsBurstEnabledImpl = new IsBurstEnabledDelegate(IsBurstEnabled);

            [BurstCompile]
            [AOT.MonoPInvokeCallback(typeof(IsBurstEnabledDelegate))]
            private static bool IsBurstEnabled()
            {
                bool result = true;
                DiscardedMethod(ref result);
                return result;
            }

            [BurstDiscard]
            private static void DiscardedMethod(ref bool value)
            {
                value = false;
            }

            private static unsafe bool IsCompiledByBurst(Delegate del)
            {
                var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(del, string.Empty);
                // We don't try to run the method, having a pointer is already enough to tell us that burst was active for AOT settings
                return Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId) != (void*)0;
            }

            /// <summary>
            /// Gets a boolean indicating whether burst was enabled for standalone player, used only at runtime.
            /// </summary>
            public static readonly bool IsBurstGenerated = IsCompiledByBurst(IsBurstEnabledImpl);
        }
#endif // !UNITY_EDITOR && !BURST_INTERNAL

        /// <summary>
        /// Fake delegate class to make BurstCompilerService.CompileAsyncDelegateMethod happy
        /// so that it can access the underlying static method via the property get_Method.
        /// So this class is not a delegate.
        /// </summary>
        private class FakeDelegate
        {
            public FakeDelegate(MethodInfo method)
            {
                Method = method;
            }

            [Preserve]
            public MethodInfo Method { get; }
        }

#else    // UNITY_DOTSPLAYER || NET_DOTS

        /// <summary>
        /// Compile the following delegate into a function pointer with burst, invokable from a Burst Job or from regular C#.
        /// </summary>
        /// <typeparam name="T">Type of the delegate of the function pointer</typeparam>
        /// <param name="delegateMethod">The delegate to compile</param>
        /// <returns>A function pointer invokable from a Burst Job or from regular C#</returns>
        public static unsafe FunctionPointer<T> CompileFunctionPointer<T>(T delegateMethod) where T : System.Delegate
        {
            // Make sure that the delegate will never be collected
            GCHandle.Alloc(delegateMethod);
            return new FunctionPointer<T>(Marshal.GetFunctionPointerForDelegate(delegateMethod));
        }

        internal static bool IsApiAvailable(string apiName)
        {
            return false;
        }
#endif
        }
}