using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Unity.Jobs;
using Unity.Mathematics;
namespace Unity.Collections.LowLevel.Unsafe
{
    /// 
    /// An unmanaged, untyped, heterogeneous buffer.
    /// 
    /// 
    /// The values written to an individual append buffer can be of different types.
    /// 
    [GenerateTestsForBurstCompatibility]
    public unsafe struct UnsafeAppendBuffer
        : INativeDisposable
    {
        /// 
        /// The internal buffer where the content is stored.
        /// 
        /// The internal buffer where the content is stored.
        [NativeDisableUnsafePtrRestriction]
        public byte* Ptr;
        /// 
        /// The size in bytes of the currently-used portion of the internal buffer.
        /// 
        /// The size in bytes of the currently-used portion of the internal buffer.
        public int Length;
        /// 
        /// The size in bytes of the internal buffer.
        /// 
        /// The size in bytes of the internal buffer.
        public int Capacity;
        /// 
        /// The allocator used to create the internal buffer.
        /// 
        /// The allocator used to create the internal buffer.
        public AllocatorManager.AllocatorHandle Allocator;
        /// 
        /// The byte alignment used when allocating the internal buffer.
        /// 
        /// The byte alignment used when allocating the internal buffer. Is always a non-zero power of 2.
        public readonly int Alignment;
        /// 
        /// Initializes and returns an instance of UnsafeAppendBuffer.
        /// 
        /// The initial allocation size in bytes of the internal buffer.
        /// The byte alignment of the allocation. Must be a non-zero power of 2.
        /// The allocator to use.
        public UnsafeAppendBuffer(int initialCapacity, int alignment, AllocatorManager.AllocatorHandle allocator)
        {
            CheckAlignment(alignment);
            Alignment = alignment;
            Allocator = allocator;
            Ptr = null;
            Length = 0;
            Capacity = 0;
            SetCapacity(math.max(initialCapacity, 1));
        }
        /// 
        /// Initializes and returns an instance of UnsafeAppendBuffer that aliases an existing buffer.
        /// 
        /// The capacity will be set to `length`, and  will be set to 0.
        /// 
        /// The buffer to alias.
        /// The length in bytes of the buffer.
        public UnsafeAppendBuffer(void* ptr, int length)
        {
            Alignment = 0;
            Allocator = AllocatorManager.None;
            Ptr = (byte*)ptr;
            Length = 0;
            Capacity = length;
        }
        /// 
        /// Whether the append buffer is empty.
        /// 
        /// True if the append buffer is empty.
        public readonly bool IsEmpty
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => Length == 0;
        }
        /// 
        /// Whether this append buffer has been allocated (and not yet deallocated).
        /// 
        /// True if this append buffer 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;
            Length = 0;
            Capacity = 0;
        }
        /// 
        /// Creates and schedules a job that will dispose this append buffer.
        /// 
        /// The handle of a job which the new job will depend upon.
        /// The handle of a new job that will dispose this append buffer. 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;
        }
        /// 
        /// Sets the length to 0.
        /// 
        /// Does not change the capacity.
        public void Reset()
        {
            Length = 0;
        }
        /// 
        /// Sets the size in bytes of the internal buffer.
        /// 
        /// Does nothing if the new capacity is less than or equal to the current capacity.
        /// A new capacity in bytes.
        public void SetCapacity(int capacity)
        {
            if (capacity <= Capacity)
            {
                return;
            }
            capacity = math.max(64, math.ceilpow2(capacity));
            var newPtr = (byte*)Memory.Unmanaged.Allocate(capacity, Alignment, Allocator);
            if (Ptr != null)
            {
                UnsafeUtility.MemCpy(newPtr, Ptr, Length);
                Memory.Unmanaged.Free(Ptr, Allocator);
            }
            Ptr = newPtr;
            Capacity = capacity;
        }
        /// 
        /// Sets the length in bytes.
        /// 
        /// If the new length exceeds the capacity, capacity is expanded to the new length.
        /// The new length.
        public void ResizeUninitialized(int length)
        {
            SetCapacity(length);
            Length = length;
        }
        /// 
        /// Appends an element to the end of this append buffer.
        /// 
        /// The type of the element.
        /// The value to be appended.
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
        public void Add(T value) where T : unmanaged
        {
            var structSize = UnsafeUtility.SizeOf();
            SetCapacity(Length + structSize);
            void* addr = Ptr + Length;
            if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf()))
                UnsafeUtility.CopyStructureToPtr(ref value, addr);
            else
                UnsafeUtility.MemCpy(addr, &value, structSize);
            
            Length += structSize;
        }
        /// 
        /// Appends an element to the end of this append buffer.
        /// 
        /// The value itself is stored, not the pointer.
        /// A pointer to the value to be appended.
        /// The size in bytes of the value to be appended.
        public void Add(void* ptr, int structSize)
        {
            SetCapacity(Length + structSize);
            UnsafeUtility.MemCpy(Ptr + Length, ptr, structSize);
            Length += structSize;
        }
        /// 
        /// Appends the elements of a buffer to the end of this append buffer.
        /// 
        /// The type of the buffer's elements.
        /// The values themselves are stored, not their pointers.
        /// A pointer to the buffer whose values will be appended.
        /// The number of elements to append.
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
        public void AddArray(void* ptr, int length) where T : unmanaged
        {
            Add(length);
            if (length != 0)
                Add(ptr, length * UnsafeUtility.SizeOf());
        }
        /// 
        /// Appends all elements of an array to the end of this append buffer.
        /// 
        /// The type of the elements.
        /// The array whose elements will all be appended.
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
        public void Add(NativeArray value) where T : unmanaged
        {
            Add(value.Length);
            Add(NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(value), UnsafeUtility.SizeOf() * value.Length);
        }
        /// 
        /// Removes and returns the last element of this append buffer.
        /// 
        /// The type of the element to remove.
        /// It is your responsibility to specify the correct type. Do not pop when the append buffer is empty.
        /// The element removed from the end of this append buffer.
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
        public T Pop() where T : unmanaged
        {
            int structSize = UnsafeUtility.SizeOf();
            long ptr = (long)Ptr;
            long size = Length;
            long addr = ptr + size - structSize;
            T data;
            if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf()))
                data = UnsafeUtility.ReadArrayElement((void*)addr, 0);
            else
                UnsafeUtility.MemCpy(&data, (void*)addr, structSize);
            Length -= structSize;
            return data;
        }
        /// 
        /// Removes and copies the last element of this append buffer.
        /// 
        /// It is your responsibility to specify the correct `structSize`. Do not pop when the append buffer is empty.
        /// The location to which the removed element will be copied.
        /// The size of the element to remove and copy.
        public void Pop(void* ptr, int structSize)
        {
            long data = (long)Ptr;
            long size = Length;
            long addr = data + size - structSize;
            UnsafeUtility.MemCpy(ptr, (void*)addr, structSize);
            Length -= structSize;
        }
        /// 
        /// Returns a reader for this append buffer.
        /// 
        /// A reader for the append buffer.
        public Reader AsReader()
        {
            return new Reader(ref this);
        }
        /// 
        /// A reader for UnsafeAppendBuffer.
        /// 
        [GenerateTestsForBurstCompatibility]
        public unsafe struct Reader
        {
            /// 
            /// The internal buffer where the content is stored.
            /// 
            /// The internal buffer where the content is stored.
            public readonly byte* Ptr;
            /// 
            /// The length in bytes of the append buffer's content.
            /// 
            /// The length in bytes of the append buffer's content.
            public readonly int Size;
            /// 
            /// The location of the next read (expressed as a byte offset from the start).
            /// 
            /// The location of the next read (expressed as a byte offset from the start).
            public int Offset;
            /// 
            /// Initializes and returns an instance of UnsafeAppendBuffer.Reader.
            /// 
            /// A reference to the append buffer to read.
            public Reader(ref UnsafeAppendBuffer buffer)
            {
                Ptr = buffer.Ptr;
                Size = buffer.Length;
                Offset = 0;
            }
            /// 
            /// Initializes and returns an instance of UnsafeAppendBuffer.Reader that reads from a buffer.
            /// 
            /// The buffer will be read *as if* it is an UnsafeAppendBuffer whether it was originally allocated as one or not.
            /// The buffer to read as an UnsafeAppendBuffer.
            /// The length in bytes of the 
            public Reader(void* ptr, int length)
            {
                Ptr = (byte*)ptr;
                Size = length;
                Offset = 0;
            }
            /// 
            /// Whether the offset has advanced past the last of the append buffer's content.
            /// 
            /// Whether the offset has advanced past the last of the append buffer's content.
            public bool EndOfBuffer => Offset == Size;
            /// 
            /// Reads an element from the append buffer.
            /// 
            /// Advances the reader's offset by the size of T.
            /// The type of element to read.
            /// Output for the element read.
            [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
            public void ReadNext(out T value) where T : unmanaged
            {
                var structSize = UnsafeUtility.SizeOf();
                CheckBounds(structSize);
                void* addr = Ptr + Offset;
                if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf()))
                    UnsafeUtility.CopyPtrToStructure(addr, out value);
                else
                    fixed (void* pValue = &value) UnsafeUtility.MemCpy(pValue, addr, structSize);
                Offset += structSize;
            }
            /// 
            /// Reads an element from the append buffer.
            /// 
            /// Advances the reader's offset by the size of T.
            /// The type of element to read.
            /// The element read.
            [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
            public T ReadNext() where T : unmanaged
            {
                var structSize = UnsafeUtility.SizeOf();
                CheckBounds(structSize);
                T value;
                void* addr = Ptr + Offset;
                if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf()))
                    value = UnsafeUtility.ReadArrayElement(addr, 0);
                else
                    UnsafeUtility.MemCpy(&value, addr, structSize);
                
                Offset += structSize;
                return value;
            }
            /// 
            /// Reads an element from the append buffer.
            /// 
            /// Advances the reader's offset by `structSize`.
            /// The size of the element to read.
            /// A pointer to where the read element resides in the append buffer.
            public void* ReadNext(int structSize)
            {
                CheckBounds(structSize);
                var value = (void*)((IntPtr)Ptr + Offset);
                Offset += structSize;
                return value;
            }
            /// 
            /// Reads an element from the append buffer.
            /// 
            /// Advances the reader's offset by the size of T.
            /// The type of element to read.
            /// Outputs a new array with length of 1. The read element is copied to the single index of this array.
            /// The allocator to use.
            [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
            public void ReadNext(out NativeArray value, AllocatorManager.AllocatorHandle allocator) where T : unmanaged
            {
                var length = ReadNext();
                value = CollectionHelper.CreateNativeArray(length, allocator, NativeArrayOptions.UninitializedMemory);
                var size = length * UnsafeUtility.SizeOf();
                if (size > 0)
                {
                    var ptr = ReadNext(size);
                    UnsafeUtility.MemCpy(NativeArrayUnsafeUtility.GetUnsafePtr(value), ptr, size);
                }
            }
            /// 
            /// Reads an array from the append buffer.
            /// 
            /// An array stored in the append buffer starts with an int specifying the number of values in the array.
            /// The first element of an array immediately follows this int.
            ///
            /// Advances the reader's offset by the size of the array (plus an int).
            /// The type of elements in the array to read.
            /// Output which is the number of elements in the read array.
            /// A pointer to where the first element of the read array resides in the append buffer.
            [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
            public void* ReadNextArray(out int length) where T : unmanaged
            {
                length = ReadNext();
                return (length == 0) ? null : ReadNext(length * UnsafeUtility.SizeOf());
            }
            [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
            void CheckBounds(int structSize)
            {
                if (Offset + structSize > Size)
                {
                    throw new ArgumentException($"Requested value outside bounds of UnsafeAppendOnlyBuffer. Remaining bytes: {Size - Offset} Requested: {structSize}");
                }
            }
        }
        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        static void CheckAlignment(int alignment)
        {
            var zeroAlignment = alignment == 0;
            var powTwoAlignment = ((alignment - 1) & alignment) == 0;
            var validAlignment = (!zeroAlignment) && powTwoAlignment;
            if (!validAlignment)
            {
                throw new ArgumentException($"Specified alignment must be non-zero positive power of two. Requested: {alignment}");
            }
        }
    }
}