using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Profiling.LowLevel;
using Unity.Profiling.LowLevel.Unsafe;

namespace Unity.Profiling
{
    /// <summary>
    /// Reports a value of an integral or floating point type to the Unity Profiler.
    /// </summary>
    /// <typeparam name="T">int, uint, long, ulong, float or double type.</typeparam>
#if ENABLE_PROFILER
    [StructLayout(LayoutKind.Sequential)]
#else
    [StructLayout(LayoutKind.Sequential, Size = 1)]
#endif
    public readonly struct ProfilerCounterValue<T> where T : unmanaged
    {
#if ENABLE_PROFILER
        [NativeDisableUnsafePtrRestriction]
        [NonSerialized]
        readonly unsafe T* m_Value;
#endif

        /// <summary>
        /// Constructs a **ProfilerCounter** that belongs to the generic ProfilerCategory.Scripts category. It is reported at the end of CPU frame to the Unity Profiler.
        /// </summary>
        /// <param name="name">Name of ProfilerCounter.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ProfilerCounterValue(string name)
        {
#if ENABLE_PROFILER
            byte dataType = ProfilerUtility.GetProfilerMarkerDataType<T>();
            unsafe
            {
                m_Value = (T*)ProfilerUnsafeUtility.CreateCounterValue(out var counterPtr, name, ProfilerUnsafeUtility.CategoryScripts, MarkerFlags.Default, dataType, (byte)ProfilerMarkerDataUnit.Undefined, UnsafeUtility.SizeOf<T>(), ProfilerCounterOptions.FlushOnEndOfFrame);
            }
#endif
        }

        /// <summary>
        /// Constructs a **ProfilerCounter** that belongs to the generic ProfilerCategory.Scripts category. It is reported at the end of CPU frame to the Unity Profiler.
        /// </summary>
        /// <param name="name">Name of ProfilerCounter.</param>
        /// <param name="dataUnit">Value unit type.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ProfilerCounterValue(string name, ProfilerMarkerDataUnit dataUnit)
        {
#if ENABLE_PROFILER
            byte dataType = ProfilerUtility.GetProfilerMarkerDataType<T>();
            unsafe
            {
                m_Value = (T*)ProfilerUnsafeUtility.CreateCounterValue(out var counterPtr, name, ProfilerUnsafeUtility.CategoryScripts, MarkerFlags.Default, dataType, (byte)dataUnit, UnsafeUtility.SizeOf<T>(), ProfilerCounterOptions.FlushOnEndOfFrame);
            }
#endif
        }

        /// <summary>
        /// Constructs a **ProfilerCounter** that belongs to generic ProfilerCategory.Scripts category.
        /// </summary>
        /// <param name="name">Name of ProfilerCounter.</param>
        /// <param name="dataUnit">Value unit type.</param>
        /// <param name="counterOptions">ProfilerCounter options.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ProfilerCounterValue(string name, ProfilerMarkerDataUnit dataUnit, ProfilerCounterOptions counterOptions)
        {
#if ENABLE_PROFILER
            byte dataType = ProfilerUtility.GetProfilerMarkerDataType<T>();
            unsafe
            {
                m_Value = (T*)ProfilerUnsafeUtility.CreateCounterValue(out var counterPtr, name, ProfilerUnsafeUtility.CategoryScripts, MarkerFlags.Default, dataType, (byte)dataUnit, UnsafeUtility.SizeOf<T>(), counterOptions);
            }
#endif
        }

        /// <summary>
        /// Constructs a **ProfilerCounter** that is reported at the end of CPU frame to the Unity Profiler.
        /// </summary>
        /// <param name="category">Profiler category.</param>
        /// <param name="name">Name of ProfilerCounter.</param>
        /// <param name="dataUnit">Value unit type.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ProfilerCounterValue(ProfilerCategory category, string name, ProfilerMarkerDataUnit dataUnit)
        {
#if ENABLE_PROFILER
            byte dataType = ProfilerUtility.GetProfilerMarkerDataType<T>();
            unsafe
            {
                m_Value = (T*)ProfilerUnsafeUtility.CreateCounterValue(out var counterPtr, name, category, MarkerFlags.Default, dataType, (byte)dataUnit, UnsafeUtility.SizeOf<T>(), ProfilerCounterOptions.FlushOnEndOfFrame);
            }
#endif
        }

        /// <summary>
        /// Constructs a **ProfilerCounter**.
        /// </summary>
        /// <param name="category">Profiler category.</param>
        /// <param name="name">Name of ProfilerCounter.</param>
        /// <param name="dataUnit">Value unit type.</param>
        /// <param name="counterOptions">ProfilerCounter options.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ProfilerCounterValue(ProfilerCategory category, string name, ProfilerMarkerDataUnit dataUnit, ProfilerCounterOptions counterOptions)
        {
#if ENABLE_PROFILER
            byte dataType = ProfilerUtility.GetProfilerMarkerDataType<T>();
            unsafe
            {
                m_Value = (T*)ProfilerUnsafeUtility.CreateCounterValue(out var counterPtr, name, category, MarkerFlags.Default, dataType, (byte)dataUnit, UnsafeUtility.SizeOf<T>(), counterOptions);
            }
#endif
        }

        /// <summary>
        /// Gets or sets value of the ProfilerCounter.
        /// </summary>
        /// <remarks>Returns 0 and is not implemented in Release Players.</remarks>
        public T Value
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get
            {
#if ENABLE_PROFILER
                unsafe
                {
                    return *m_Value;
                }
#else
                return default;
#endif
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            set
            {
#if ENABLE_PROFILER
                unsafe
                {
                    *m_Value = value;
                }
#endif
            }
        }

        /// <summary>
        /// Sends the value to Unity Profiler immediately.
        /// </summary>
        [Conditional("ENABLE_PROFILER")]
        public void Sample()
        {
#if ENABLE_PROFILER
            unsafe
            {
                ProfilerUnsafeUtility.FlushCounterValue(m_Value);
            }
#endif
        }
    }
}