using Unity.PerformanceTesting;
using Unity.PerformanceTesting.Benchmark;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
namespace Unity.Collections.PerformanceTests
{
    /// 
    /// Specifies a class containing performance test methods which should be included in container benchmarking.
    /// The values specified in this enum are unlikely to be needed in user code, but user code will specify the enum type
    /// in a couple places:
    /// [Benchmark(typeof(BenchmarkContainerType))]  // <---- HERE
    /// class FooContainerPerformanceTestMethods
    /// and
    /// [Test, Performance]
    /// public unsafe void ContainerPerfTestExample(
    ///     [Values(100000, 1000000, 10000000)] int capacity,
    ///     [Values] BenchmarkContainerType type)  // <---- HERE
    /// {
    /// Though values may be specified in the performance test method parameter, it is recommended to leave the argument implicitly
    /// covering all enum values as seen in the example above.
    /// 
    [BenchmarkComparison(BenchmarkContainerConfig.BCL, "{0} (BCL)")]
    [BenchmarkComparisonDisplay(SampleUnit.Millisecond, 3, BenchmarkContainerConfig.kRankingMethod)]
    public enum BenchmarkContainerType : int
    {
        /// Native container performance test will execute on a managed (not burst compiled) code path
        [BenchmarkName("Native{0} (S)")] Native,
        /// Native container performance test will execute on a burst compile code path, with safety checks enabled
        [BenchmarkName("Native{0} (S+B)")] NativeBurstSafety,
        /// Native container performance test will execute on a burst compile code path, with safety checks disabled
        [BenchmarkName("Native{0} (B)")] NativeBurstNoSafety,
        /// Unsafe container performance test will execute on a managed (not burst compiled) code path
        [BenchmarkName("Unsafe{0} (S)")] Unsafe,
        /// Unsafe container performance test will execute on a burst compile code path, with safety checks enabled
        [BenchmarkName("Unsafe{0} (S+B)")] UnsafeBurstSafety,
        /// Unsafe container performance test will execute on a burst compile code path, with safety checks disabled
        [BenchmarkName("Unsafe{0} (B)")] UnsafeBurstNoSafety,
    }
    /// 
    /// Configuration settings for benchmarking containers.
    /// 
    public static class BenchmarkContainerConfig
    {
        /// 
        /// An additional value to the enum values defined in  which will not be included
        /// in Performance Test Framework test generation but will be included in Benchmark Framework result generation.
        /// 
        public const int BCL = -1;
        internal const BenchmarkRankingStatistic kRankingMethod = BenchmarkRankingStatistic.Median;
        internal const int kCountWarmup = 5;
        internal const int kCountMeasure = 10;
        /// 
        /// Prefix string for individual benchmark menu items
        /// 
        public const string kMenuItemIndividual = "DOTS/Unity.Collections/Generate Individual Container Benchmark/";
#if UNITY_EDITOR
        [UnityEditor.MenuItem("DOTS/Unity.Collections/Generate Container Benchmarks")]
#endif
        static void RunBenchmarks() =>
            BenchmarkGenerator.GenerateMarkdown(
                "Containers",
                typeof(BenchmarkContainerType),
                "../../Packages/com.unity.collections/Documentation~/performance-comparison-containers.md",
                $"The **{kRankingMethod} of {kCountMeasure} sample sets** is compared against the baseline on the far right side of the table."
                    + $"
Multithreaded benchmarks divide the processing amongst the specified number of workers."
                    + $"
{kCountWarmup} extra sample sets are run as warmup."
                    ,
                "Legend",
                new string[]
                {
                    "`(S)` = Safety Enabled",
                    "`(B)` = Burst Compiled *with Safety Disabled*",
                    "`(S+B)` = Burst Compiled *with Safety Enabled*",
                    "`(BCL)` = Base Class Library implementation (such as provided by Mono or .NET)",
                    "",
                    "*`italic`* results are for benchmarking comparison only; these are not included in standard Performance Framework tests",
                });
#if UNITY_EDITOR
        /// 
        /// Runs a benchmark for a particular class with Performance Test Framework/Benchmark methods. For calling
        /// from custom menu items, for instance.
        /// 
        /// 
        public static void RunBenchmark(System.Type testClassType)
        {
            string tempPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(
                UnityEditor.FileUtil.GetUniqueTempPathInProject()), $"Benchmark{testClassType.Name}.md");
            BenchmarkGenerator.GenerateMarkdown(
                $"Individual -- {testClassType.Name}",
                new System.Type[] { testClassType },
                tempPath,
                $"The **{kRankingMethod} of {kCountMeasure} sample sets** is compared against the baseline on the far right side of the table."
                    + $"
Multithreaded benchmarks divide the processing amongst the specified number of workers."
                    + $"
{kCountWarmup} extra sample sets are run as warmup."
                    ,
                "Legend",
                new string[]
                {
                "`(S)` = Safety Enabled",
                "`(B)` = Burst Compiled *with Safety Disabled*",
                "`(S+B)` = Burst Compiled *with Safety Enabled*",
                "`(BCL)` = Base Class Library implementation (such as provided by Mono or .NET)",
                "",
                "*`italic`* results are for benchmarking comparison only; these are not included in standard Performance Framework tests",
                });
            UnityEditor.EditorUtility.RevealInFinder(tempPath);
        }
#endif
    }
    /// 
    /// Interface to implement container performance tests which will run using .
    /// Deriving tests from this interface enables both Performance Test Framework and Benchmark Framework to generate and run
    /// tests for the contexts described by .
    /// 
    public interface IBenchmarkContainer
    {
        /// 
        /// Override this to add extra int arguments to a performance test implementation as fields in the implementing type. These arguments
        /// are optionally passed in through .
        /// 
        /// The initial capacity to requested for the container.
        /// A variable number of extra arguments to passed through to the test implementation
        public void SetParams(int capacity, params int[] args) { }
        /// 
        /// Called during setup for each measurement in a sample set with the capacity to allocate to the native container
        /// when the benchmark type is , ,
        /// or .
        /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container.
        /// 
        /// The capacity to allocate for the managed container. Capacity of 0 will still create a container,
        /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s).
        public void AllocNativeContainer(int capacity);
        /// 
        /// Called during setup for each measurement in a sample set with the capacity to allocate to the unsafe container
        /// when the benchmark type is , ,
        /// or .
        /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container.
        /// 
        /// The capacity to allocate for the managed container. Capacity of 0 will still create a container,
        /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s).
        public void AllocUnsafeContainer(int capacity);  // capacity 0 frees
        /// 
        /// Called during setup for each measurement in a sample set with the capacity to allocate to the managed container
        /// when the benchmark type is .
        /// This is also called during teardown for each measurement in a sample set with '-1' to indicate freeing the container.
        /// 
        /// The capacity to allocate for the managed container. Capacity of 0 will still create a container,
        /// but it will be empty. A capacity of -1 will dispose the container and free associated allocation(s).
        /// A reference to the allocated container when capacity >= 0, and `null` when capacity < 0.
        public object AllocBclContainer(int capacity);
        /// 
        /// The code which will be executed during performance measurement. This should usually be general enough to
        /// work with any native container.
        /// 
        public void MeasureNativeContainer();
        /// 
        /// The code which will be executed during performance measurement. This should usually be general enough to
        /// work with any unsafe container.
        /// 
        public void MeasureUnsafeContainer();
        /// 
        /// The code which will be executed during performance measurement. This should usually be general enough to
        /// work with any managed container provided by the Base Class Library (BCL).
        /// 
        /// A reference to the managed container allocated in 
        public void MeasureBclContainer(object container);
    }
    /// 
    /// Provides the API for running container based Performance Framework tests and Benchmark Framework measurements.
    /// This will typically be the sole call from a performance test. See 
    /// for more information.
    /// 
    /// An implementation conforming to the  interface for running container performance tests and benchmarks.
    [BurstCompile(CompileSynchronously = true)]
    public static class BenchmarkContainerRunner where T : unmanaged, IBenchmarkContainer
    {
        [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)]
        unsafe struct NativeJobBurstST : IJob
        {
            [NativeDisableUnsafePtrRestriction] public T* methods;
            public void Execute() => methods->MeasureNativeContainer();
        }
        [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)]
        unsafe struct NativeJobSafetyBurstST : IJob
        {
            [NativeDisableUnsafePtrRestriction] public T* methods;
            public void Execute() => methods->MeasureNativeContainer();
        }
        [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)]
        unsafe struct UnsafeJobBurstST : IJob
        {
            [NativeDisableUnsafePtrRestriction] public T* methods;
            public void Execute() => methods->MeasureUnsafeContainer();
        }
        [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = false)]
        unsafe struct UnsafeJobSafetyBurstST : IJob
        {
            [NativeDisableUnsafePtrRestriction] public T* methods;
            public void Execute() => methods->MeasureUnsafeContainer();
        }
        /// 
        /// Called from a typical performance test method to provide both Performance Framework measurements as well as
        /// Benchmark Framework measurements. A typical usage is similar to:
        /// [Test, Performance]
        /// [Category("Performance")]
        /// public unsafe void ToNativeArray(
        ///     [Values(100000, 1000000, 10000000)] int capacity,
        ///     [Values] BenchmarkContainerType type)
        /// {
        ///     BenchmarkContainerRunner<HashSetToNativeArray>.RunST(capacity, type);
        /// }
        /// 
        /// The capacity for the container(s) which will be passed to setup methods
        /// The benchmark or performance measurement type to run for containers i.e.  etc.
        /// Optional arguments that can be stored in a test implementation class.
        /// This will run measurements with  or directly called on the main thread.
        public static unsafe void Run(int capacity, BenchmarkContainerType type, params int[] args)
        {
            var methods = new T();
            methods.SetParams(capacity, args);
            switch (type)
            {
                case (BenchmarkContainerType)(BenchmarkContainerConfig.BCL):
                    object container = null;
                    BenchmarkMeasure.Measure(typeof(T),
                        BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure,
                        () => methods.MeasureBclContainer(container),
                        () => container = methods.AllocBclContainer(capacity), () => container = methods.AllocBclContainer(-1));
                    break;
                case BenchmarkContainerType.Native:
                    BenchmarkMeasure.Measure(typeof(T),
                        BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure,
                        () => methods.MeasureNativeContainer(),
                        () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1));
                    break;
                case BenchmarkContainerType.NativeBurstSafety:
                    BenchmarkMeasure.Measure(typeof(T),
                        BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure,
                        () => new NativeJobSafetyBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(),
                        () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1));
                    break;
                case BenchmarkContainerType.NativeBurstNoSafety:
                    BenchmarkMeasure.Measure(typeof(T),
                        BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure,
                        () => new NativeJobBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(),
                        () => methods.AllocNativeContainer(capacity), () => methods.AllocNativeContainer(-1));
                    break;
                case BenchmarkContainerType.Unsafe:
                    BenchmarkMeasure.Measure(typeof(T),
                        BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure,
                        () => methods.MeasureUnsafeContainer(),
                        () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1));
                    break;
                case BenchmarkContainerType.UnsafeBurstSafety:
                    BenchmarkMeasure.Measure(typeof(T),
                        BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure,
                        () => new UnsafeJobSafetyBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(),
                        () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1));
                    break;
                case BenchmarkContainerType.UnsafeBurstNoSafety:
                    BenchmarkMeasure.Measure(typeof(T),
                        BenchmarkContainerConfig.kCountWarmup, BenchmarkContainerConfig.kCountMeasure,
                        () => new UnsafeJobBurstST { methods = (T*)UnsafeUtility.AddressOf(ref methods) }.Run(),
                        () => methods.AllocUnsafeContainer(capacity), () => methods.AllocUnsafeContainer(-1));
                    break;
            }
        }
    }
}