using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Unity.Jobs;
using System.Runtime.InteropServices;
namespace Unity.Collections.LowLevel.Unsafe
{
    /// 
    /// A fixed-size circular buffer.
    /// 
    /// The type of the elements.
    [DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")]
    [DebuggerTypeProxy(typeof(UnsafeRingQueueDebugView<>))]
    [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct UnsafeRingQueue
        : INativeDisposable
        where T : unmanaged
    {
        /// 
        /// The internal buffer where the content is stored.
        /// 
        /// The internal buffer where the content is stored.
        [NativeDisableUnsafePtrRestriction]
        public T* Ptr;
        /// 
        /// The allocator used to create the internal buffer.
        /// 
        /// The allocator used to create the internal buffer.
        public AllocatorManager.AllocatorHandle Allocator;
        internal readonly int m_Capacity;
        internal int m_Filled;
        internal int m_Write;
        internal int m_Read;
        /// 
        /// Whether the queue is empty.
        /// 
        /// True if the queue is empty or the queue has not been constructed.
        public readonly bool IsEmpty
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => m_Filled == 0;
        }
        /// 
        /// The number of elements currently in this queue.
        /// 
        /// The number of elements currently in this queue.
        public readonly int Length
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => m_Filled;
        }
        /// 
        /// The number of elements that fit in the internal buffer.
        /// 
        /// The number of elements that fit in the internal buffer.
        public readonly int Capacity
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => m_Capacity;
        }
        /// 
        /// Initializes and returns an instance of UnsafeRingQueue which aliasing an existing buffer.
        /// 
        /// An existing buffer to set as the internal buffer.
        /// The capacity.
        public UnsafeRingQueue(T* ptr, int capacity)
        {
            Ptr = ptr;
            Allocator = AllocatorManager.None;
            m_Capacity = capacity;
            m_Filled = 0;
            m_Write = 0;
            m_Read = 0;
        }
        /// 
        /// Initializes and returns an instance of UnsafeRingQueue.
        /// 
        /// The capacity.
        /// The allocator to use.
        /// Whether newly allocated bytes should be zeroed out.
        public UnsafeRingQueue(int capacity, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
        {
            Allocator = allocator;
            m_Capacity = capacity;
            m_Filled = 0;
            m_Write = 0;
            m_Read = 0;
            var sizeInBytes = capacity * UnsafeUtility.SizeOf();
            Ptr = (T*)Memory.Unmanaged.Allocate(sizeInBytes, 16, allocator);
            if (options == NativeArrayOptions.ClearMemory)
            {
                UnsafeUtility.MemClear(Ptr, sizeInBytes);
            }
        }
        internal static UnsafeRingQueue* Alloc(AllocatorManager.AllocatorHandle allocator)
        {
            UnsafeRingQueue* data = (UnsafeRingQueue*)Memory.Unmanaged.Allocate(sizeof(UnsafeRingQueue), UnsafeUtility.AlignOf>(), allocator);
            return data;
        }
        internal static void Free(UnsafeRingQueue* data)
        {
            if (data == null)
            {
                throw new InvalidOperationException("UnsafeRingQueue has yet to be created or has been destroyed!");
            }
            var allocator = data->Allocator;
            data->Dispose();
            Memory.Unmanaged.Free(data, allocator);
        }
        /// 
        /// Whether this queue has been allocated (and not yet deallocated).
        /// 
        /// True if this queue has been allocated (and not yet deallocated).
        public readonly bool IsCreated
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => Ptr != null;
        }
        /// 
        /// Releases all resources (memory and safety handles).
        /// 
        public void Dispose()
        {
            if (!IsCreated)
            {
                return;
            }
            if (CollectionHelper.ShouldDeallocate(Allocator))
            {
                Memory.Unmanaged.Free(Ptr, Allocator);
                Allocator = AllocatorManager.Invalid;
            }
            Ptr = null;
        }
        /// 
        /// Creates and schedules a job that will dispose this queue.
        /// 
        /// The handle of a job which the new job will depend upon.
        /// The handle of a new job that will dispose this queue. The new job depends upon inputDeps.
        public JobHandle Dispose(JobHandle inputDeps)
        {
            if (!IsCreated)
            {
                return inputDeps;
            }
            if (CollectionHelper.ShouldDeallocate(Allocator))
            {
                var jobHandle = new UnsafeDisposeJob { Ptr = Ptr, Allocator = Allocator }.Schedule(inputDeps);
                Ptr = null;
                Allocator = AllocatorManager.Invalid;
                return jobHandle;
            }
            Ptr = null;
            return inputDeps;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        bool TryEnqueueInternal(T value)
        {
            if (m_Filled == m_Capacity)
                return false;
            Ptr[m_Write] = value;
            m_Write++;
            if (m_Write == m_Capacity)
                m_Write = 0;
            m_Filled++;
            return true;
        }
        /// 
        /// Adds an element at the front of the queue.
        /// 
        /// Does nothing if the queue is full.
        /// The value to be added.
        /// True if the value was added.
        public bool TryEnqueue(T value)
        {
            return TryEnqueueInternal(value);
        }
        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        static void ThrowQueueFull()
        {
            throw new InvalidOperationException("Trying to enqueue into full queue.");
        }
        /// 
        /// Adds an element at the front of the queue.
        /// 
        /// The value to be added.
        /// Thrown if the queue was full.
        public void Enqueue(T value)
        {
            if (!TryEnqueueInternal(value))
            {
                ThrowQueueFull();
            }
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        bool TryDequeueInternal(out T item)
        {
            item = Ptr[m_Read];
            if (m_Filled == 0)
                return false;
            m_Read = m_Read + 1;
            if (m_Read == m_Capacity)
                m_Read = 0;
            m_Filled--;
            return true;
        }
        /// 
        /// Removes the element from the end of the queue.
        /// 
        /// Does nothing if the queue is empty.
        /// Outputs the element removed.
        /// True if an element was removed.
        public bool TryDequeue(out T item)
        {
            return TryDequeueInternal(out item);
        }
        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        static void ThrowQueueEmpty()
        {
            throw new InvalidOperationException("Trying to dequeue from an empty queue");
        }
        /// 
        /// Removes the element from the end of the queue.
        /// 
        /// Thrown if the queue was empty.
        /// Returns the removed element.
        public T Dequeue()
        {
            if (!TryDequeueInternal(out T item))
            {
                ThrowQueueEmpty();
            }
            return item;
        }
    }
    internal sealed class UnsafeRingQueueDebugView
        where T : unmanaged
    {
        UnsafeRingQueue Data;
        public UnsafeRingQueueDebugView(UnsafeRingQueue data)
        {
            Data = data;
        }
        public unsafe T[] Items
        {
            get
            {
                T[] result = new T[Data.Length];
                var read = Data.m_Read;
                var capacity = Data.m_Capacity;
                for (var i = 0; i < result.Length; ++i)
                {
                    result[i] = Data.Ptr[(read + i) % capacity];
                }
                return result;
            }
        }
    }
}