using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using Unity.Burst;
using Unity.Jobs;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Mathematics;

namespace Unity.Collections.LowLevel.Unsafe
{

    [BurstCompile]
    internal unsafe struct UnsafeDisposeJob : IJob
    {
        [NativeDisableUnsafePtrRestriction]
        public void* Ptr;
        public AllocatorManager.AllocatorHandle Allocator;

        public void Execute()
        {
            AllocatorManager.Free(Allocator, Ptr);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    internal unsafe struct UntypedUnsafeList
    {
#pragma warning disable 169
        // <WARNING>
        // 'Header' of this struct must binary match `UntypedUnsafeList`, `UnsafeList`, `UnsafePtrList`, and `NativeArray` struct.
        [NativeDisableUnsafePtrRestriction]
        internal readonly void* Ptr;
        internal readonly int m_length;
        internal readonly int m_capacity;
        internal readonly AllocatorManager.AllocatorHandle Allocator;
        internal readonly int padding;
#pragma warning restore 169
    }

    /// <summary>
    /// An unmanaged, resizable list.
    /// </summary>
    /// <typeparam name="T">The type of the elements.</typeparam>
    [DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")]
    [DebuggerTypeProxy(typeof(UnsafeListTDebugView<>))]
    [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct UnsafeList<T>
        : INativeDisposable
        , INativeList<T>
        , IEnumerable<T> // Used by collection initializers.
        where T : unmanaged
    {
        // <WARNING>
        // 'Header' of this struct must binary match `UntypedUnsafeList`, `UnsafeList`, `UnsafePtrList`, and `NativeArray` struct.
        // Fields must match UntypedUnsafeList structure, please don't reorder and don't insert anything in between first 4 fields

        /// <summary>
        /// The internal buffer of this list.
        /// </summary>
        [NativeDisableUnsafePtrRestriction]
        public T* Ptr;

        /// <summary>
        /// The number of elements.
        /// </summary>
        public int m_length;

        /// <summary>
        /// The number of elements that can fit in the internal buffer.
        /// </summary>
        public int m_capacity;

        /// <summary>
        /// The allocator used to create the internal buffer.
        /// </summary>
        public AllocatorManager.AllocatorHandle Allocator;

        readonly int padding;

        /// <summary>
        /// The number of elements.
        /// </summary>
        /// <value>The number of elements.</value>
        public int Length
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            readonly get => CollectionHelper.AssumePositive(m_length);

            set
            {
                if (value > Capacity)
                {
                    Resize(value);
                }
                else
                {
                    m_length = value;
                }
            }
        }

        /// <summary>
        /// The number of elements that can fit in the internal buffer.
        /// </summary>
        /// <value>The number of elements that can fit in the internal buffer.</value>
        public int Capacity
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            readonly get => CollectionHelper.AssumePositive(m_capacity);
            set => SetCapacity(value);
        }

        /// <summary>
        /// The element at an index.
        /// </summary>
        /// <param name="index">An index.</param>
        /// <value>The element at the index.</value>
        public T this[int index]
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get
            {
                CollectionHelper.CheckIndexInRange(index, m_length);
                return Ptr[CollectionHelper.AssumePositive(index)];
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            set
            {
                CollectionHelper.CheckIndexInRange(index, m_length);
                Ptr[CollectionHelper.AssumePositive(index)] = value;
            }
        }

        /// <summary>
        /// Returns a reference to the element at a given index.
        /// </summary>
        /// <param name="index">The index to access. Must be in the range of [0..Length).</param>
        /// <returns>A reference to the element at the index.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ref T ElementAt(int index)
        {
            CollectionHelper.CheckIndexInRange(index, m_length);
            return ref Ptr[CollectionHelper.AssumePositive(index)];
        }

        /// <summary>
        /// Initializes and returns an instance of UnsafeList.
        /// </summary>
        /// <param name="ptr">An existing byte array to set as the internal buffer.</param>
        /// <param name="length">The length.</param>
        public UnsafeList(T* ptr, int length) : this()
        {
            Ptr = ptr;
            m_length = length;
            m_capacity = length;
            Allocator = AllocatorManager.None;
        }

        /// <summary>
        /// Initializes and returns an instance of UnsafeList.
        /// </summary>
        /// <param name="initialCapacity">The initial capacity of the list.</param>
        /// <param name="allocator">The allocator to use.</param>
        /// <param name="options">Whether newly allocated bytes should be zeroed out.</param>
        public UnsafeList(int initialCapacity, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory)
        {
            Ptr = null;
            m_length = 0;
            m_capacity = 0;
            Allocator = allocator;
            padding = 0;

            SetCapacity(math.max(initialCapacity, 1));

            if (options == NativeArrayOptions.ClearMemory && Ptr != null)
            {
                var sizeOf = sizeof(T);
                UnsafeUtility.MemClear(Ptr, Capacity * sizeOf);
            }
        }

        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(AllocatorManager.AllocatorHandle) })]
        internal static UnsafeList<T>* Create<U>(int initialCapacity, ref U allocator, NativeArrayOptions options) where U : unmanaged, AllocatorManager.IAllocator
        {
            UnsafeList<T>* listData = allocator.Allocate(default(UnsafeList<T>), 1);
            *listData = new UnsafeList<T>(initialCapacity, allocator.Handle, options);
            return listData;
        }

        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(AllocatorManager.AllocatorHandle) })]
        internal static void Destroy<U>(UnsafeList<T>* listData, ref U allocator) where U : unmanaged, AllocatorManager.IAllocator
        {
            CheckNull(listData);
            listData->Dispose(ref allocator);
            allocator.Free(listData, sizeof(UnsafeList<T>), UnsafeUtility.AlignOf<UnsafeList<T>>(), 1);
        }

        /// <summary>
        /// Returns a new list.
        /// </summary>
        /// <param name="initialCapacity">The initial capacity of the list.</param>
        /// <param name="allocator">The allocator to use.</param>
        /// <param name="options">Whether newly allocated bytes should be zeroed out.</param>
        /// <returns>A pointer to the new list.</returns>
        public static UnsafeList<T>* Create(int initialCapacity, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory)
        {
            UnsafeList<T>* listData = AllocatorManager.Allocate<UnsafeList<T>>(allocator);
            *listData = new UnsafeList<T>(initialCapacity, allocator, options);

            return listData;
        }

        /// <summary>
        /// Destroys the list.
        /// </summary>
        /// <param name="listData">The list to destroy.</param>
        public static void Destroy(UnsafeList<T>* listData)
        {
            CheckNull(listData);
            var allocator = listData->Allocator;
            listData->Dispose();
            AllocatorManager.Free(allocator, listData);
        }

        /// <summary>
        /// Whether the list is empty.
        /// </summary>
        /// <value>True if the list is empty or the list has not been constructed.</value>
        public readonly bool IsEmpty
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => !IsCreated || m_length == 0;
        }

        /// <summary>
        /// Whether this list has been allocated (and not yet deallocated).
        /// </summary>
        /// <value>True if this list has been allocated (and not yet deallocated).</value>
        public readonly bool IsCreated
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => Ptr != null;
        }

        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(AllocatorManager.AllocatorHandle) })]
        internal void Dispose<U>(ref U allocator) where U : unmanaged, AllocatorManager.IAllocator
        {
            allocator.Free(Ptr, m_capacity);
            Ptr = null;
            m_length = 0;
            m_capacity = 0;
        }

        /// <summary>
        /// Releases all resources (memory).
        /// </summary>
        public void Dispose()
        {
            if (!IsCreated)
            {
                return;
            }

            if (CollectionHelper.ShouldDeallocate(Allocator))
            {
                AllocatorManager.Free(Allocator, Ptr, m_capacity);
                Allocator = AllocatorManager.Invalid;
            }

            Ptr = null;
            m_length = 0;
            m_capacity = 0;
        }

        /// <summary>
        /// Creates and schedules a job that frees the memory of this list.
        /// </summary>
        /// <param name="inputDeps">The dependency for the new job.</param>
        /// <returns>The handle of the new job. The job depends upon `inputDeps` and frees the memory of this list.</returns>
        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;
        }

        /// <summary>
        /// Sets the length to 0.
        /// </summary>
        /// <remarks>Does not change the capacity.</remarks>
        public void Clear()
        {
            m_length = 0;
        }

        /// <summary>
        /// Sets the length, expanding the capacity if necessary.
        /// </summary>
        /// <param name="length">The new length.</param>
        /// <param name="options">Whether newly allocated bytes should be zeroed out.</param>
        public void Resize(int length, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory)
        {
            var oldLength = m_length;

            if (length > Capacity)
            {
                SetCapacity(length);
            }

            m_length = length;

            if (options == NativeArrayOptions.ClearMemory && oldLength < length)
            {
                var num = length - oldLength;
                byte* ptr = (byte*)Ptr;
                var sizeOf = sizeof(T);
                UnsafeUtility.MemClear(ptr + oldLength * sizeOf, num * sizeOf);
            }
        }

        void ResizeExact<U>(ref U allocator, int newCapacity) where U : unmanaged, AllocatorManager.IAllocator
        {
            newCapacity = math.max(0, newCapacity);

            CollectionHelper.CheckAllocator(Allocator);
            T* newPointer = null;

            var alignOf = UnsafeUtility.AlignOf<T>();
            var sizeOf = sizeof(T);

            if (newCapacity > 0)
            {
                newPointer = (T*)allocator.Allocate(sizeOf, alignOf, newCapacity);

                if (Ptr != null && m_capacity > 0)
                {
                    var itemsToCopy = math.min(newCapacity, Capacity);
                    var bytesToCopy = itemsToCopy * sizeOf;
                    UnsafeUtility.MemCpy(newPointer, Ptr, bytesToCopy);
                }
            }

            allocator.Free(Ptr, Capacity);

            Ptr = newPointer;
            m_capacity = newCapacity;
            m_length = math.min(m_length, newCapacity);
        }

        void ResizeExact(int capacity)
        {
            ResizeExact(ref Allocator, capacity);
        }

        void SetCapacity<U>(ref U allocator, int capacity) where U : unmanaged, AllocatorManager.IAllocator
        {
            CollectionHelper.CheckCapacityInRange(capacity, Length);

            var sizeOf = sizeof(T);
            var newCapacity = math.max(capacity, CollectionHelper.CacheLineSize / sizeOf);
            newCapacity = math.ceilpow2(newCapacity);

            if (newCapacity == Capacity)
            {
                return;
            }

            ResizeExact(ref allocator, newCapacity);
        }

        /// <summary>
        /// Sets the capacity.
        /// </summary>
        /// <param name="capacity">The new capacity.</param>
        public void SetCapacity(int capacity)
        {
            SetCapacity(ref Allocator, capacity);
        }

        /// <summary>
        /// Sets the capacity to match the length.
        /// </summary>
        public void TrimExcess()
        {
            if (Capacity != m_length)
            {
                ResizeExact(m_length);
            }
        }

        /// <summary>
        /// Adds an element to the end of this list.
        /// </summary>
        /// <remarks>
        /// Increments the length by 1. Never increases the capacity.
        /// </remarks>
        /// <param name="value">The value to add to the end of the list.</param>
        /// <exception cref="InvalidOperationException">Thrown if incrementing the length would exceed the capacity.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void AddNoResize(T value)
        {
            CheckNoResizeHasEnoughCapacity(1);
            UnsafeUtility.WriteArrayElement(Ptr, m_length, value);
            m_length += 1;
        }

        /// <summary>
        /// Copies elements from a buffer to the end of this list.
        /// </summary>
        /// <remarks>
        /// Increments the length by `count`. Never increases the capacity.
        /// </remarks>
        /// <param name="ptr">The buffer to copy from.</param>
        /// <param name="count">The number of elements to copy from the buffer.</param>
        /// <exception cref="InvalidOperationExceptionv">Thrown if the increased length would exceed the capacity.</exception>
        public void AddRangeNoResize(void* ptr, int count)
        {
            CheckNoResizeHasEnoughCapacity(count);
            var sizeOf = sizeof(T);
            void* dst = (byte*)Ptr + m_length * sizeOf;
            UnsafeUtility.MemCpy(dst, ptr, count * sizeOf);
            m_length += count;
        }

        /// <summary>
        /// Copies the elements of another list to the end of this list.
        /// </summary>
        /// <param name="list">The other list to copy from.</param>
        /// <remarks>
        /// Increments the length by the length of the other list. Never increases the capacity.
        /// </remarks>
        /// <exception cref="InvalidOperationException">Thrown if the increased length would exceed the capacity.</exception>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
        public void AddRangeNoResize(UnsafeList<T> list)
        {
            AddRangeNoResize(list.Ptr, CollectionHelper.AssumePositive(list.Length));
        }

        /// <summary>
        /// Adds an element to the end of the list.
        /// </summary>
        /// <param name="value">The value to add to the end of this list.</param>
        /// <remarks>
        /// Increments the length by 1. Increases the capacity if necessary.
        /// </remarks>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Add(in T value)
        {
            var idx = m_length;
            if (m_length < m_capacity)
            {
                Ptr[idx] = value;
                m_length++;
                return;
            }
            
            Resize(idx + 1);
            Ptr[idx] = value;
        }

        /// <summary>
        /// Copies the elements of a buffer to the end of this list.
        /// </summary>
        /// <param name="ptr">The buffer to copy from.</param>
        /// <param name="count">The number of elements to copy from the buffer.</param>
        /// <remarks>
        /// Increments the length by `count`. Increases the capacity if necessary.
        /// </remarks>
        public void AddRange(void* ptr, int count)
        {
            var idx = m_length;

            if (m_length + count > Capacity)
            {
                Resize(m_length + count);
            }
            else
            {
                m_length += count;
            }

            var sizeOf = sizeof(T);
            void* dst = (byte*)Ptr + idx * sizeOf;
            UnsafeUtility.MemCpy(dst, ptr, count * sizeOf);
        }

        /// <summary>
        /// Copies the elements of another list to the end of the list.
        /// </summary>
        /// <param name="list">The list to copy from.</param>
        /// <remarks>
        /// The length is increased by the length of the other list. Increases the capacity if necessary.
        /// </remarks>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
        public void AddRange(UnsafeList<T> list)
        {
            AddRange(list.Ptr, list.Length);
        }

        /// <summary>
        /// Appends value count times to the end of this list.
        /// </summary>
        /// <param name="value">The value to add to the end of this list.</param>
        /// <param name="count">The number of times to replicate the value.</param>
        /// <remarks>
        /// Length is incremented by count. If necessary, the capacity is increased.
        /// </remarks>
        public void AddReplicate(in T value, int count)
        {
            var idx = m_length;
            if (m_length + count > Capacity)
            {
                Resize(m_length + count);
            }
            else
            {
                m_length += count;
            }

            fixed (void* ptr = &value)
            {
                UnsafeUtility.MemCpyReplicate(Ptr + idx, ptr, UnsafeUtility.SizeOf<T>(), count);
            }
        }

        /// <summary>
        /// Shifts elements toward the end of this list, increasing its length.
        /// </summary>
        /// <remarks>
        /// Right-shifts elements in the list so as to create 'free' slots at the beginning or in the middle.
        ///
        /// The length is increased by `end - begin`. If necessary, the capacity will be increased accordingly.
        ///
        /// If `end` equals `begin`, the method does nothing.
        ///
        /// The element at index `begin` will be copied to index `end`, the element at index `begin + 1` will be copied to `end + 1`, and so forth.
        ///
        /// The indexes `begin` up to `end` are not cleared: they will contain whatever values they held prior.
        /// </remarks>
        /// <param name="begin">The index of the first element that will be shifted up.</param>
        /// <param name="end">The index where the first shifted element will end up.</param>
        /// <exception cref="ArgumentException">Thrown if `end &lt; begin`.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if `begin` or `end` are out of bounds.</exception>
        public void InsertRangeWithBeginEnd(int begin, int end)
        {
            CheckBeginEndNoLength(begin, end);

            // Because we've checked begin and end in `CheckBeginEnd` above, we can now
            // assume they are positive.
            begin = CollectionHelper.AssumePositive(begin);
            end = CollectionHelper.AssumePositive(end);

            int items = end - begin;
            if (items < 1)
            {
                return;
            }

            var oldLength = m_length;

            if (m_length + items > Capacity)
            {
                Resize(m_length + items);
            }
            else
            {
                m_length += items;
            }

            var itemsToCopy = oldLength - begin;

            if (itemsToCopy < 1)
            {
                return;
            }

            var sizeOf = sizeof(T);
            var bytesToCopy = itemsToCopy * sizeOf;
            unsafe
            {
                byte* ptr = (byte*)Ptr;
                byte* dest = ptr + end * sizeOf;
                byte* src = ptr + begin * sizeOf;
                UnsafeUtility.MemMove(dest, src, bytesToCopy);
            }
        }

        /// <summary>
        /// Shifts elements toward the end of this list, increasing its length.
        /// </summary>
        /// <remarks>
        /// Right-shifts elements in the list so as to create 'free' slots at the beginning or in the middle.
        ///
        /// The length is increased by `count`. If necessary, the capacity will be increased accordingly.
        ///
        /// If `count` equals `0`, the method does nothing.
        ///
        /// The element at index `index` will be copied to index `index + count`, the element at index `index + 1` will be copied to `index + count + 1`, and so forth.
        ///
        /// The indexes `index` up to `index + count` are not cleared: they will contain whatever values they held prior.
        /// </remarks>
        /// <param name="index">The index of the first element that will be shifted up.</param>
        /// <param name="count">The number of elements to insert.</param>
        /// <exception cref="ArgumentException">Thrown if `count` is negative.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if `index` is out of bounds.</exception>
        public void InsertRange(int index, int count) => InsertRangeWithBeginEnd(index, index + count);

        /// <summary>
        /// Copies the last element of this list to the specified index. Decrements the length by 1.
        /// </summary>
        /// <remarks>Useful as a cheap way to remove an element from this list when you don't care about preserving order.</remarks>
        /// <param name="index">The index to overwrite with the last element.</param>
        /// <exception cref="IndexOutOfRangeException">Thrown if `index` is out of bounds.</exception>
        public void RemoveAtSwapBack(int index)
        {
            RemoveRangeSwapBack(index, 1);
        }

        /// <summary>
        /// Copies the last *N* elements of this list to a range in this list. Decrements the length by *N*.
        /// </summary>
        /// <remarks>
        /// Copies the last `count` elements to the indexes `index` up to `index + count`.
        ///
        /// Useful as a cheap way to remove elements from a list when you don't care about preserving order.
        /// </remarks>
        /// <param name="index">The index of the first element to overwrite.</param>
        /// <param name="count">The number of elements to copy and remove.</param>
        /// <exception cref="IndexOutOfRangeException">Thrown if `index` is out of bounds</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if `count` is negative,
        /// or `index + count` exceeds the length.</exception>
        public void RemoveRangeSwapBack(int index, int count)
        {
            CheckIndexCount(index, count);

            index = CollectionHelper.AssumePositive(index);
            count = CollectionHelper.AssumePositive(count);

            if (count > 0)
            {
                int copyFrom = math.max(m_length - count, index + count);
                var sizeOf = sizeof(T);
                void* dst = (byte*)Ptr + index * sizeOf;
                void* src = (byte*)Ptr + copyFrom * sizeOf;
                UnsafeUtility.MemCpy(dst, src, (m_length - copyFrom) * sizeOf);
                m_length -= count;
            }
        }

        /// <summary>
        /// Removes the element at an index, shifting everything above it down by one. Decrements the length by 1.
        /// </summary>
        /// <param name="index">The index of the element to remove.</param>
        /// <remarks>
        /// If you don't care about preserving the order of the elements, <see cref="RemoveAtSwapBack(int)"/> is a more efficient way to remove elements.
        /// </remarks>
        /// <exception cref="IndexOutOfRangeException">Thrown if `index` is out of bounds.</exception>
        public void RemoveAt(int index)
        {
            CollectionHelper.CheckIndexInRange(index, m_length);

            index = CollectionHelper.AssumePositive(index);

            T* dst = Ptr + index;
            T* src = dst + 1;
            m_length--;

            // Because these tend to be smaller (< 1MB), and the cost of jumping context to native and back is
            // so high, this consistently optimizes to better code than UnsafeUtility.MemCpy
            for (int i = index; i < m_length; i++)
            {
                *dst++ = *src++;
            }
        }

        /// <summary>
        /// Removes *N* elements in a range, shifting everything above the range down by *N*. Decrements the length by *N*.
        /// </summary>
        /// <param name="index">The index of the first element to remove.</param>
        /// <param name="count">The number of elements to remove.</param>
        /// <remarks>
        /// If you don't care about preserving the order of the elements, `RemoveRangeSwapBackWithBeginEnd`
        /// is a more efficient way to remove elements.
        /// </remarks>
        /// <exception cref="IndexOutOfRangeException">Thrown if `index` is out of bounds</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if `count` is negative,
        /// or `index + count` exceeds the length.</exception>
        public void RemoveRange(int index, int count)
        {
            CheckIndexCount(index, count);

            index = CollectionHelper.AssumePositive(index);
            count = CollectionHelper.AssumePositive(count);

            if (count > 0)
            {
                int copyFrom = math.min(index + count, m_length);
                var sizeOf = sizeof(T);
                void* dst = (byte*)Ptr + index * sizeOf;
                void* src = (byte*)Ptr + copyFrom * sizeOf;
                UnsafeUtility.MemCpy(dst, src, (m_length - copyFrom) * sizeOf);
                m_length -= count;
            }
        }

        /// <summary>
        /// Returns a read only of this list.
        /// </summary>
        /// <returns>A read only of this list.</returns>
        public ReadOnly AsReadOnly()
        {
            return new ReadOnly(Ptr, Length);
        }

        /// <summary>
        /// A read only for an UnsafeList&lt;T&gt;.
        /// </summary>
        /// <remarks>
        /// Use <see cref="AsReadOnly"/> to create a read only for a list.
        /// </remarks>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
        public unsafe struct ReadOnly
            : IEnumerable<T>
        {
            /// <summary>
            /// The internal buffer of the list.
            /// </summary>
            [NativeDisableUnsafePtrRestriction]
            public readonly T* Ptr;

            /// <summary>
            /// The number of elements.
            /// </summary>
            public readonly int Length;

            internal ReadOnly(T* ptr, int length)
            {
                Ptr = ptr;
                Length = length;
            }

            /// <summary>
            /// Returns an enumerator over the elements of the list.
            /// </summary>
            /// <returns>An enumerator over the elements of the list.</returns>
            public Enumerator GetEnumerator()
            {
                return new Enumerator { m_Ptr = Ptr, m_Length = Length, m_Index = -1 };
            }

            /// <summary>
            /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
            /// </summary>
            /// <returns>Throws NotImplementedException.</returns>
            /// <exception cref="NotImplementedException">Method is not implemented.</exception>
            IEnumerator IEnumerable.GetEnumerator()
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
            /// </summary>
            /// <returns>Throws NotImplementedException.</returns>
            /// <exception cref="NotImplementedException">Method is not implemented.</exception>
            IEnumerator<T> IEnumerable<T>.GetEnumerator()
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// **Obsolete.** Use <see cref="AsReadOnly"/> instead.
        /// </summary>
        /// <returns>A parallel reader of this list.</returns>
//        [Obsolete("'AsParallelReader' has been deprecated; use 'AsReadOnly' instead. (UnityUpgradable) -> AsReadOnly")]
        public ParallelReader AsParallelReader()
        {
            return new ParallelReader(Ptr, Length);
        }

        /// <summary>
        /// **Obsolete.** Use <see cref="ReadOnly"/> instead.
        /// </summary>
        /// <remarks>
        /// Use <see cref="AsParallelReader"/> to create a parallel reader for a list.
        /// </remarks>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
//        [Obsolete("'ParallelReader' has been deprecated; use 'ReadOnly' instead. (UnityUpgradable) -> ReadOnly")]
        public unsafe struct ParallelReader
        {
            /// <summary>
            /// The internal buffer of the list.
            /// </summary>
            [NativeDisableUnsafePtrRestriction]
            public readonly T* Ptr;

            /// <summary>
            /// The number of elements.
            /// </summary>
            public readonly int Length;

            internal ParallelReader(T* ptr, int length)
            {
                Ptr = ptr;
                Length = length;
            }
        }

        /// <summary>
        /// Returns a parallel writer of this list.
        /// </summary>
        /// <returns>A parallel writer of this list.</returns>
        public ParallelWriter AsParallelWriter()
        {
            return new ParallelWriter((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
        }

        /// <summary>
        /// A parallel writer for an UnsafeList&lt;T&gt;.
        /// </summary>
        /// <remarks>
        /// Use <see cref="AsParallelWriter"/> to create a parallel writer for a list.
        /// </remarks>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
        public unsafe struct ParallelWriter
        {
            /// <summary>
            /// The data of the list.
            /// </summary>
            public readonly void* Ptr
            {
                [MethodImpl(MethodImplOptions.AggressiveInlining)]
                get
                {
                    return ListData->Ptr;
                }
            }

            /// <summary>
            /// The UnsafeList to write to.
            /// </summary>
            [NativeDisableUnsafePtrRestriction]
            public UnsafeList<T>* ListData;

            internal unsafe ParallelWriter(UnsafeList<T>* listData)
            {
                ListData = listData;
            }

            /// <summary>
            /// Adds an element to the end of the list.
            /// </summary>
            /// <param name="value">The value to add to the end of the list.</param>
            /// <remarks>
            /// Increments the length by 1. Never increases the capacity.
            /// </remarks>
            /// <exception cref="InvalidOperationException">Thrown if incrementing the length would exceed the capacity.</exception>
            [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
            public void AddNoResize(T value)
            {
                var idx = Interlocked.Increment(ref ListData->m_length) - 1;
                ListData->CheckNoResizeHasEnoughCapacity(idx, 1);
                UnsafeUtility.WriteArrayElement(ListData->Ptr, idx, value);
            }

            /// <summary>
            /// Copies elements from a buffer to the end of the list.
            /// </summary>
            /// <param name="ptr">The buffer to copy from.</param>
            /// <param name="count">The number of elements to copy from the buffer.</param>
            /// <remarks>
            /// Increments the length by `count`. Never increases the capacity.
            /// </remarks>
            /// <exception cref="InvalidOperationException">Thrown if the increased length would exceed the capacity.</exception>
            [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
            public void AddRangeNoResize(void* ptr, int count)
            {
                var idx = Interlocked.Add(ref ListData->m_length, count) - count;
                ListData->CheckNoResizeHasEnoughCapacity(idx, count);
                void* dst = (byte*)ListData->Ptr + idx * sizeof(T);
                UnsafeUtility.MemCpy(dst, ptr, count * sizeof(T));
            }

            /// <summary>
            /// Copies the elements of another list to the end of this list.
            /// </summary>
            /// <param name="list">The other list to copy from.</param>
            /// <remarks>
            /// Increments the length by the length of the other list. Never increases the capacity.
            /// </remarks>
            /// <exception cref="InvalidOperationException">Thrown if the increased length would exceed the capacity.</exception>
            [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
            public void AddRangeNoResize(UnsafeList<T> list)
            {
                AddRangeNoResize(list.Ptr, list.Length);
            }
        }

        /// <summary>
        /// Copies all elements of specified container to this container.
        /// </summary>
        /// <param name="other">An container to copy into this container.</param>
        public void CopyFrom(in NativeArray<T> other)
        {
            Resize(other.Length);
            UnsafeUtility.MemCpy(Ptr, other.GetUnsafeReadOnlyPtr<T>(), UnsafeUtility.SizeOf<T>() * other.Length);
        }

        /// <summary>
        /// Copies all elements of specified container to this container.
        /// </summary>
        /// <param name="other">An container to copy into this container.</param>
        public void CopyFrom(in UnsafeList<T> other)
        {
            Resize(other.Length);
            UnsafeUtility.MemCpy(Ptr, other.Ptr, UnsafeUtility.SizeOf<T>() * other.Length);
        }

        /// <summary>
        /// Returns an enumerator over the elements of the list.
        /// </summary>
        /// <returns>An enumerator over the elements of the list.</returns>
        public Enumerator GetEnumerator()
        {
            return new Enumerator { m_Ptr = Ptr, m_Length = Length, m_Index = -1 };
        }

        /// <summary>
        /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
        /// </summary>
        /// <returns>Throws NotImplementedException.</returns>
        /// <exception cref="NotImplementedException">Method is not implemented.</exception>
        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
        /// </summary>
        /// <returns>Throws NotImplementedException.</returns>
        /// <exception cref="NotImplementedException">Method is not implemented.</exception>
        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// An enumerator over the elements of a list.
        /// </summary>
        /// <remarks>
        /// In an enumerator's initial state, <see cref="Current"/> is invalid.
        /// The first <see cref="MoveNext"/> call advances the enumerator to the first element of the list.
        /// </remarks>
        public struct Enumerator : IEnumerator<T>
        {
            internal T* m_Ptr;
            internal int m_Length;
            internal int m_Index;

            /// <summary>
            /// Does nothing.
            /// </summary>
            public void Dispose() { }

            /// <summary>
            /// Advances the enumerator to the next element of the list.
            /// </summary>
            /// <remarks>
            /// The first `MoveNext` call advances the enumerator to the first element of the list. Before this call, `Current` is not valid to read.
            /// </remarks>
            /// <returns>True if `Current` is valid to read after the call.</returns>
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public bool MoveNext() => ++m_Index < m_Length;

            /// <summary>
            /// Resets the enumerator to its initial state.
            /// </summary>
            public void Reset() => m_Index = -1;

            /// <summary>
            /// The current element.
            /// </summary>
            /// <value>The current element.</value>
            public T Current
            {
                [MethodImpl(MethodImplOptions.AggressiveInlining)]
                get => m_Ptr[m_Index];
            }

            object IEnumerator.Current => Current;
        }

        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        internal static void CheckNull(void* listData)
        {
            if (listData == null)
            {
                throw new InvalidOperationException("UnsafeList has yet to be created or has been destroyed!");
            }
        }

        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        void CheckIndexCount(int index, int count)
        {
            if (count < 0)
            {
                throw new ArgumentOutOfRangeException($"Value for count {count} must be positive.");
            }

            if (index < 0)
            {
                throw new IndexOutOfRangeException($"Value for index {index} must be positive.");
            }

            if (index >= Length)
            {
                throw new IndexOutOfRangeException($"Value for index {index} is out of bounds.");
            }

            if (index + count > Length)
            {
                throw new ArgumentOutOfRangeException($"Value for count {count} is out of bounds.");
            }
        }

        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        void CheckBeginEndNoLength(int begin, int end)
        {
            if (begin > end)
            {
                throw new ArgumentException($"Value for begin {begin} index must less or equal to end {end}.");
            }

            if (begin < 0)
            {
                throw new ArgumentOutOfRangeException($"Value for begin {begin} must be positive.");
            }
        }

        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        void CheckBeginEnd(int begin, int end)
        {
            CheckBeginEndNoLength(begin, end);

            if (begin > Length)
            {
                throw new ArgumentOutOfRangeException($"Value for begin {begin} is out of bounds.");
            }

            if (end > Length)
            {
                throw new ArgumentOutOfRangeException($"Value for end {end} is out of bounds.");
            }
        }

        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        void CheckNoResizeHasEnoughCapacity(int length)
        {
            CheckNoResizeHasEnoughCapacity(length, Length);
        }

        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        void CheckNoResizeHasEnoughCapacity(int length, int index)
        {
            if (Capacity < index + length)
            {
                throw new InvalidOperationException($"AddNoResize assumes that list capacity is sufficient (Capacity {Capacity}, Length {Length}), requested length {length}!");
            }
        }
    }

    /// <summary>
    /// Provides extension methods for UnsafeList.
    /// </summary>
    [GenerateTestsForBurstCompatibility]
    public unsafe static class UnsafeListExtensions
    {
        /// <summary>
        /// Finds the index of the first occurrence of a particular value in this list.
        /// </summary>
        /// <typeparam name="T">The type of elements in this list.</typeparam>
        /// <typeparam name="U">The type of value to locate.</typeparam>
        /// <param name="list">This list.</param>
        /// <param name="value">A value to locate.</param>
        /// <returns>The zero-based index of the first occurrence of the value if it is found. Returns -1 if no occurrence is found.</returns>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
        public static int IndexOf<T, U>(this UnsafeList<T> list, U value) where T : unmanaged, IEquatable<U>
        {
            return NativeArrayExtensions.IndexOf<T, U>(list.Ptr, list.Length, value);
        }

        /// <summary>
        /// Returns true if a particular value is present in this list.
        /// </summary>
        /// <typeparam name="T">The type of elements in the list.</typeparam>
        /// <typeparam name="U">The type of value to locate.</typeparam>
        /// <param name="list">This list.</param>
        /// <param name="value">The value to locate.</param>
        /// <returns>True if the value is present in this list.</returns>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
        public static bool Contains<T, U>(this UnsafeList<T> list, U value) where T : unmanaged, IEquatable<U>
        {
            return list.IndexOf(value) != -1;
        }

        /// <summary>
        /// Finds the index of the first occurrence of a particular value in the list.
        /// </summary>
        /// <typeparam name="T">The type of elements in the list.</typeparam>
        /// <typeparam name="U">The type of value to locate.</typeparam>
        /// <param name="list">This reader of the list.</param>
        /// <param name="value">A value to locate.</param>
        /// <returns>The zero-based index of the first occurrence of the value if it is found. Returns -1 if no occurrence is found.</returns>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int), typeof(int) })]
        public static int IndexOf<T, U>(this UnsafeList<T>.ReadOnly list, U value) where T : unmanaged, IEquatable<U>
        {
            return NativeArrayExtensions.IndexOf<T, U>(list.Ptr, list.Length, value);
        }

        /// <summary>
        /// Returns true if a particular value is present in the list.
        /// </summary>
        /// <typeparam name="T">The type of elements in the list.</typeparam>
        /// <typeparam name="U">The type of value to locate.</typeparam>
        /// <param name="list">This reader of the list.</param>
        /// <param name="value">The value to locate.</param>
        /// <returns>True if the value is present in the list.</returns>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int), typeof(int) })]
        public static bool Contains<T, U>(this UnsafeList<T>.ReadOnly list, U value) where T : unmanaged, IEquatable<U>
        {
            return list.IndexOf(value) != -1;
        }

        /// <summary>
        /// **Obsolete.** Use <see cref="UnsafeList{T}.ReadOnly"/> instead.
        /// </summary>
        /// <typeparam name="T">The type of elements in the list.</typeparam>
        /// <typeparam name="U">The type of value to locate.</typeparam>
        /// <param name="list">This reader of the list.</param>
        /// <param name="value">A value to locate.</param>
        /// <returns>The zero-based index of the first occurrence of the value if it is found. Returns -1 if no occurrence is found.</returns>
//        [Obsolete("'UnsafeList<T>.ParallelReader' has been deprecated; use 'UnsafeList<T>.ReadOnly' instead.")]
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
        public static int IndexOf<T, U>(this UnsafeList<T>.ParallelReader list, U value) where T : unmanaged, IEquatable<U>
        {
            return NativeArrayExtensions.IndexOf<T, U>(list.Ptr, list.Length, value);
        }

        /// <summary>
        /// **Obsolete.** Use <see cref="UnsafeList{T}.ReadOnly"/> instead. 
        /// </summary>
        /// <typeparam name="T">The type of elements in the list.</typeparam>
        /// <typeparam name="U">The type of value to locate.</typeparam>
        /// <param name="list">This reader of the list.</param>
        /// <param name="value">The value to locate.</param>
        /// <returns>True if the value is present in the list.</returns>
//        [Obsolete("'UnsafeList<T>.ParallelReader' has been deprecated; use 'UnsafeList<T>.ReadOnly' instead.")]
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
        public static bool Contains<T, U>(this UnsafeList<T>.ParallelReader list, U value) where T : unmanaged, IEquatable<U>
        {
            return list.IndexOf(value) != -1;
        }

        /// <summary>
        /// Returns true if this container and another have equal length and content.
        /// </summary>
        /// <typeparam name="T">The type of the source container's elements.</typeparam>
        /// <param name="container">The container to compare for equality.</param>
        /// <param name="other">The other container to compare for equality.</param>
        /// <returns>True if the containers have equal length and content.</returns>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
        public static bool ArraysEqual<T>(this UnsafeList<T> container, in UnsafeList<T> other)
            where T : unmanaged, IEquatable<T>
        {
            if (container.Length != other.Length)
                return false;

            for (int i = 0; i != container.Length; i++)
            {
                if (!container[i].Equals(other[i]))
                    return false;
            }

            return true;
        }

    }

    internal sealed class UnsafeListTDebugView<T>
        where T : unmanaged
    {
        UnsafeList<T> Data;

        public UnsafeListTDebugView(UnsafeList<T> data)
        {
            Data = data;
        }

        public unsafe T[] Items
        {
            get
            {
                T[] result = new T[Data.Length];

                for (var i = 0; i < result.Length; ++i)
                {
                    result[i] = Data.Ptr[i];
                }

                return result;
            }
        }
    }

    /// <summary>
    /// An unmanaged, resizable list of pointers.
    /// </summary>
    /// <typeparam name="T">The type of pointer element.</typeparam>
    [DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")]
    [DebuggerTypeProxy(typeof(UnsafePtrListDebugView<>))]
    [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct UnsafePtrList<T>
        : INativeDisposable
        // IIndexable<T> and INativeList<T> can't be implemented because this[index] and ElementAt return T* instead of T.
        , IEnumerable<IntPtr> // Used by collection initializers.
        where T : unmanaged
    {
        // <WARNING>
        // 'Header' of this struct must binary match `UntypedUnsafeList`, `UnsafeList`, `UnsafePtrList`, and `NativeArray` struct.
        // Fields must match UntypedUnsafeList structure, please don't reorder and don't insert anything in between first 4 fields

        /// <summary>
        /// The internal buffer of this list.
        /// </summary>
        [NativeDisableUnsafePtrRestriction]
        public readonly T** Ptr;

        /// <summary>
        /// The number of elements.
        /// </summary>
        public readonly int m_length;

        /// <summary>
        /// The number of elements that can fit in the internal buffer.
        /// </summary>
        public readonly int m_capacity;

        /// <summary>
        /// The allocator used to create the internal buffer.
        /// </summary>
        public readonly AllocatorManager.AllocatorHandle Allocator;

        readonly int padding;

        /// <summary>
        /// The number of elements.
        /// </summary>
        /// <value>The number of elements.</value>
        public int Length
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            readonly get => this.ListDataRO().Length;
            set => this.ListData().Length = value;
        }

        /// <summary>
        /// The number of elements that can fit in the internal buffer.
        /// </summary>
        /// <value>The number of elements that can fit in the internal buffer.</value>
        public int Capacity
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            readonly get => this.ListDataRO().Capacity;
            set => this.ListData().Capacity = value;
        }

        /// <summary>
        /// The element at an index.
        /// </summary>
        /// <param name="index">An index.</param>
        /// <value>The element at the index.</value>
        public T* this[int index]
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get
            {
                CollectionHelper.CheckIndexInRange(index, Length);
                return Ptr[CollectionHelper.AssumePositive(index)];
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            set
            {
                CollectionHelper.CheckIndexInRange(index, Length);
                Ptr[CollectionHelper.AssumePositive(index)] = value;
            }
        }

        /// <summary>
        /// Returns a reference to the element at a given index.
        /// </summary>
        /// <param name="index">The index to access. Must be in the range of [0..Length).</param>
        /// <returns>A reference to the element at the index.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ref T* ElementAt(int index)
        {
            CollectionHelper.CheckIndexInRange(index, Length);
            return ref Ptr[CollectionHelper.AssumePositive(index)];
        }

        /// <summary>
        /// Initializes and returns an instance of UnsafePtrList.
        /// </summary>
        /// <param name="ptr">An existing pointer array to set as the internal buffer.</param>
        /// <param name="length">The length.</param>
        public unsafe UnsafePtrList(T** ptr, int length) : this()
        {
            Ptr = ptr;
            m_length = length;
            m_capacity = length;
            Allocator = AllocatorManager.None;
        }

        /// <summary>
        /// Initializes and returns an instance of UnsafePtrList.
        /// </summary>
        /// <param name="initialCapacity">The initial capacity of the list.</param>
        /// <param name="allocator">The allocator to use.</param>
        /// <param name="options">Whether newly allocated bytes should be zeroed out.</param>
        public unsafe UnsafePtrList(int initialCapacity, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory)
        {
            Ptr = null;
            m_length = 0;
            m_capacity = 0;
            padding = 0;
            Allocator = AllocatorManager.None;

            this.ListData() = new UnsafeList<IntPtr>(initialCapacity, allocator, options);
        }

        /// <summary>
        /// Returns a new list of pointers.
        /// </summary>
        /// <param name="ptr">An existing pointer array to set as the internal buffer.</param>
        /// <param name="length">The length.</param>
        /// <returns>A pointer to the new list.</returns>
        public static UnsafePtrList<T>* Create(T** ptr, int length)
        {
            UnsafePtrList<T>* listData = AllocatorManager.Allocate<UnsafePtrList<T>>(AllocatorManager.Persistent);
            *listData = new UnsafePtrList<T>(ptr, length);
            return listData;
        }

        /// <summary>
        /// Returns a new list of pointers.
        /// </summary>
        /// <param name="initialCapacity">The initial capacity of the list.</param>
        /// <param name="allocator">The allocator to use.</param>
        /// <param name="options">Whether newly allocated bytes should be zeroed out.</param>
        /// <returns>A pointer to the new list.</returns>
        public static UnsafePtrList<T>* Create(int initialCapacity, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory)
        {
            UnsafePtrList<T>* listData = AllocatorManager.Allocate<UnsafePtrList<T>>(allocator);
            *listData = new UnsafePtrList<T>(initialCapacity, allocator, options);
            return listData;
        }

        /// <summary>
        /// Destroys the list.
        /// </summary>
        /// <param name="listData">The list to destroy.</param>
        public static void Destroy(UnsafePtrList<T>* listData)
        {
            UnsafeList<IntPtr>.CheckNull(listData);
            var allocator = listData->ListData().Allocator.Value == AllocatorManager.Invalid.Value
                ? AllocatorManager.Persistent
                : listData->ListData().Allocator
            ;
            listData->Dispose();
            AllocatorManager.Free(allocator, listData);
        }

        /// <summary>
        /// Whether the list is empty.
        /// </summary>
        /// <value>True if the list is empty or the list has not been constructed.</value>
        public readonly bool IsEmpty
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => !IsCreated || Length == 0;
        }

        /// <summary>
        /// Whether this list has been allocated (and not yet deallocated).
        /// </summary>
        /// <value>True if this list has been allocated (and not yet deallocated).</value>
        public readonly bool IsCreated
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => Ptr != null;
        }

        /// <summary>
        /// Releases all resources (memory).
        /// </summary>
        public void Dispose()
        {
            this.ListData().Dispose();
        }

        /// <summary>
        /// Creates and schedules a job that frees the memory of this list.
        /// </summary>
        /// <param name="inputDeps">The dependency for the new job.</param>
        /// <returns>The handle of the new job. The job depends upon `inputDeps` and frees the memory of this list.</returns>
        public JobHandle Dispose(JobHandle inputDeps) => this.ListData().Dispose(inputDeps);

        /// <summary>
        /// Sets the length to 0.
        /// </summary>
        /// <remarks>Does not change the capacity.</remarks>
        public void Clear() => this.ListData().Clear();

        /// <summary>
        /// Sets the length, expanding the capacity if necessary.
        /// </summary>
        /// <param name="length">The new length.</param>
        /// <param name="options">Whether newly allocated bytes should be zeroed out.</param>
        public void Resize(int length, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) => this.ListData().Resize(length, options);

        /// <summary>
        /// Sets the capacity.
        /// </summary>
        /// <param name="capacity">The new capacity.</param>
        public void SetCapacity(int capacity) => this.ListData().SetCapacity(capacity);

        /// <summary>
        /// Sets the capacity to match the length.
        /// </summary>
        public void TrimExcess() => this.ListData().TrimExcess();

        /// <summary>
        /// Returns the index of the first occurrence of a specific pointer in the list.
        /// </summary>
        /// <param name="ptr">The pointer to search for in the list.</param>
        /// <returns>The index of the first occurrence of the pointer. Returns -1 if it is not found in the list.</returns>
        public int IndexOf(void* ptr)
        {
            for (int i = 0; i < Length; ++i)
            {
                if (Ptr[i] == ptr) return i;
            }

            return -1;
        }

        /// <summary>
        /// Returns true if the list contains at least one occurrence of a specific pointer.
        /// </summary>
        /// <param name="ptr">The pointer to search for in the list.</param>
        /// <returns>True if the list contains at least one occurrence of the pointer.</returns>
        public bool Contains(void* ptr)
        {
            return IndexOf(ptr) != -1;
        }

        /// <summary>
        /// Adds a pointer to the end of this list.
        /// </summary>
        /// <remarks>
        /// Increments the length by 1. Never increases the capacity.
        /// </remarks>
        /// <param name="value">The pointer to add to the end of the list.</param>
        /// <exception cref="InvalidOperationException">Thrown if incrementing the length would exceed the capacity.</exception>
        public void AddNoResize(void* value)
        {
            this.ListData().AddNoResize((IntPtr)value);
        }

        /// <summary>
        /// Copies pointers from a buffer to the end of this list.
        /// </summary>
        /// <remarks>
        /// Increments the length by `count`. Never increases the capacity.
        /// </remarks>
        /// <param name="ptr">The buffer to copy from.</param>
        /// <param name="count">The number of pointers to copy from the buffer.</param>
        /// <exception cref="InvalidOperationException">Thrown if the increased length would exceed the capacity.</exception>
        public void AddRangeNoResize(void** ptr, int count) => this.ListData().AddRangeNoResize(ptr, count);

        /// <summary>
        /// Copies the pointers of another list to the end of this list.
        /// </summary>
        /// <param name="list">The other list to copy from.</param>
        /// <remarks>
        /// Increments the length by the length of the other list. Never increases the capacity.
        /// </remarks>
        /// <exception cref="InvalidOperationException">Thrown if the increased length would exceed the capacity.</exception>
        public void AddRangeNoResize(UnsafePtrList<T> list) => this.ListData().AddRangeNoResize(list.Ptr, list.Length);

        /// <summary>
        /// Adds a pointer to the end of the list.
        /// </summary>
        /// <param name="value">The pointer to add to the end of this list.</param>
        /// <remarks>
        /// Increments the length by 1. Increases the capacity if necessary.
        /// </remarks>
        public void Add(in IntPtr value)
        {
            this.ListData().Add(value);
        }

        /// <summary>
        /// Adds a pointer to the end of the list.
        /// </summary>
        /// <param name="value">The pointer to add to the end of this list.</param>
        /// <remarks>
        /// Increments the length by 1. Increases the capacity if necessary.
        /// </remarks>
        public void Add(void* value)
        {
            this.ListData().Add((IntPtr)value);
        }

        /// <summary>
        /// Adds elements from a buffer to this list.
        /// </summary>
        /// <param name="ptr">A pointer to the buffer.</param>
        /// <param name="length">The number of elements to add to the list.</param>
        public void AddRange(void* ptr, int length) => this.ListData().AddRange(ptr, length);

        /// <summary>
        /// Copies the elements of another list to the end of this list.
        /// </summary>
        /// <param name="list">The other list to copy from.</param>
        /// <remarks>
        /// Increments the length by the length of the other list. Increases the capacity if necessary.
        /// </remarks>
        public void AddRange(UnsafePtrList<T> list) => this.ListData().AddRange(list.ListData());

        /// <summary>
        /// Shifts pointers toward the end of this list, increasing its length.
        /// </summary>
        /// <remarks>
        /// Right-shifts pointers in the list so as to create 'free' slots at the beginning or in the middle.
        ///
        /// The length is increased by `end - begin`. If necessary, the capacity will be increased accordingly.
        ///
        /// If `end` equals `begin`, the method does nothing.
        ///
        /// The pointer at index `begin` will be copied to index `end`, the pointer at index `begin + 1` will be copied to `end + 1`, and so forth.
        ///
        /// The indexes `begin` up to `end` are not cleared: they will contain whatever pointers they held prior.
        /// </remarks>
        /// <param name="begin">The index of the first pointer that will be shifted up.</param>
        /// <param name="end">The index where the first shifted pointer will end up.</param>
        /// <exception cref="ArgumentException">Thrown if `end &lt; begin`.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if `begin` or `end` are out of bounds.</exception>
        public void InsertRangeWithBeginEnd(int begin, int end) => this.ListData().InsertRangeWithBeginEnd(begin, end);

        /// <summary>
        /// Copies the last pointer of this list to the specified index. Decrements the length by 1.
        /// </summary>
        /// <remarks>Useful as a cheap way to remove a pointer from this list when you don't care about preserving order.</remarks>
        /// <param name="index">The index to overwrite with the last pointer.</param>
        /// <exception cref="IndexOutOfRangeException">Thrown if `index` is out of bounds.</exception>
        public void RemoveAtSwapBack(int index) => this.ListData().RemoveAtSwapBack(index);

        /// <summary>
        /// Copies the last *N* pointer of this list to a range in this list. Decrements the length by *N*.
        /// </summary>
        /// <remarks>
        /// Copies the last `count` pointers to the indexes `index` up to `index + count`.
        ///
        /// Useful as a cheap way to remove pointers from a list when you don't care about preserving order.
        /// </remarks>
        /// <param name="index">The index of the first pointer to overwrite.</param>
        /// <param name="count">The number of pointers to copy and remove.</param>
        /// <exception cref="IndexOutOfRangeException">Thrown if `index` is out of bounds</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if `count` is negative,
        /// or `index + count` exceeds the length.</exception>
        public void RemoveRangeSwapBack(int index, int count) => this.ListData().RemoveRangeSwapBack(index, count);

        /// <summary>
        /// Removes the pointer at an index, shifting everything above it down by one. Decrements the length by 1.
        /// </summary>
        /// <param name="index">The index of the pointer to remove.</param>
        /// <remarks>
        /// If you don't care about preserving the order of the pointers, <see cref="RemoveAtSwapBack(int)"/> is a more efficient way to remove pointers.
        /// </remarks>
        /// <exception cref="IndexOutOfRangeException">Thrown if `index` is out of bounds.</exception>
        public void RemoveAt(int index) => this.ListData().RemoveAt(index);

        /// <summary>
        /// Removes *N* pointers in a range, shifting everything above the range down by *N*. Decrements the length by *N*.
        /// </summary>
        /// <param name="index">The index of the first pointer to remove.</param>
        /// <param name="count">The number of pointers to remove.</param>
        /// <remarks>
        /// If you don't care about preserving the order of the pointers, `RemoveRangeSwapBackWithBeginEnd`
        /// is a more efficient way to remove pointers.
        /// </remarks>
        /// <exception cref="IndexOutOfRangeException">Thrown if `index` is out of bounds</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if `count` is negative,
        /// or `index + count` exceeds the length.</exception>
        public void RemoveRange(int index, int count) => this.ListData().RemoveRange(index, count);

        /// <summary>
        /// This method is not implemented. It will throw NotImplementedException if it is used.
        /// </summary>
        /// <remarks>Use Enumerator GetEnumerator() instead.</remarks>
        /// <returns>Throws NotImplementedException.</returns>
        /// <exception cref="NotImplementedException">Method is not implemented.</exception>
        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// This method is not implemented. It will throw NotImplementedException if it is used.
        /// </summary>
        /// <remarks>Use Enumerator GetEnumerator() instead.</remarks>
        /// <returns>Throws NotImplementedException.</returns>
        /// <exception cref="NotImplementedException">Method is not implemented.</exception>
        IEnumerator<IntPtr> IEnumerable<IntPtr>.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Returns a read only of this list.
        /// </summary>
        /// <returns>A read only of this list.</returns>
        public ReadOnly AsReadOnly()
        {
            return new ReadOnly(Ptr, Length);
        }

        /// <summary>
        /// A read only for an UnsafePtrList&lt;T&gt;.
        /// </summary>
        /// <remarks>
        /// Use <see cref="AsReadOnly"/> to create a read only for a list.
        /// </remarks>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
        public unsafe struct ReadOnly
        {
            /// <summary>
            /// The internal buffer of the list.
            /// </summary>
            [NativeDisableUnsafePtrRestriction]
            public readonly T** Ptr;

            /// <summary>
            /// The number of elements.
            /// </summary>
            public readonly int Length;

            internal ReadOnly(T** ptr, int length)
            {
                Ptr = ptr;
                Length = length;
            }

            /// <summary>
            /// Returns the index of the first occurrence of a specific pointer in the list.
            /// </summary>
            /// <param name="ptr">The pointer to search for in the list.</param>
            /// <returns>The index of the first occurrence of the pointer. Returns -1 if it is not found in the list.</returns>
            public int IndexOf(void* ptr)
            {
                for (int i = 0; i < Length; ++i)
                {
                    if (Ptr[i] == ptr) return i;
                }
                return -1;
            }

            /// <summary>
            /// Returns true if the list contains at least one occurrence of a specific pointer.
            /// </summary>
            /// <param name="ptr">The pointer to search for in the list.</param>
            /// <returns>True if the list contains at least one occurrence of the pointer.</returns>
            public bool Contains(void* ptr)
            {
                return IndexOf(ptr) != -1;
            }
        }

        /// <summary>
        /// **Obsolete**. Use <see cref="AsReadOnly"/> instead.
        /// </summary>
        /// <returns>A parallel reader of this list.</returns>
//        [Obsolete("'AsParallelReader' has been deprecated; use 'AsReadOnly' instead. (UnityUpgradable) -> AsReadOnly")]
        public ParallelReader AsParallelReader()
        {
            return new ParallelReader(Ptr, Length);
        }

        /// <summary>
        /// **Obsolete.** Use <see cref="ReadOnly"/> instead.
        /// </summary>
        /// <remarks>
        /// Use <see cref="AsParallelReader"/> to create a parallel reader for a list.
        /// </remarks>
//        [Obsolete("'ParallelReader' has been deprecated; use 'ReadOnly' instead. (UnityUpgradable) -> ReadOnly")]
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
        public unsafe struct ParallelReader
        {
            /// <summary>
            /// The internal buffer of the list.
            /// </summary>
            [NativeDisableUnsafePtrRestriction]
            public readonly T** Ptr;

            /// <summary>
            /// The number of elements.
            /// </summary>
            public readonly int Length;

            internal ParallelReader(T** ptr, int length)
            {
                Ptr = ptr;
                Length = length;
            }

            /// <summary>
            /// Returns the index of the first occurrence of a specific pointer in the list.
            /// </summary>
            /// <param name="ptr">The pointer to search for in the list.</param>
            /// <returns>The index of the first occurrence of the pointer. Returns -1 if it is not found in the list.</returns>
            public int IndexOf(void* ptr)
            {
                for (int i = 0; i < Length; ++i)
                {
                    if (Ptr[i] == ptr) return i;
                }
                return -1;
            }

            /// <summary>
            /// Returns true if the list contains at least one occurrence of a specific pointer.
            /// </summary>
            /// <param name="ptr">The pointer to search for in the list.</param>
            /// <returns>True if the list contains at least one occurrence of the pointer.</returns>
            public bool Contains(void* ptr)
            {
                return IndexOf(ptr) != -1;
            }
        }

        /// <summary>
        /// Returns a parallel writer of this list.
        /// </summary>
        /// <returns>A parallel writer of this list.</returns>
        public ParallelWriter AsParallelWriter()
        {
            return new ParallelWriter(Ptr, (UnsafeList<IntPtr>*)UnsafeUtility.AddressOf(ref this));
        }

        /// <summary>
        /// A parallel writer for an UnsafePtrList&lt;T&gt;.
        /// </summary>
        /// <remarks>
        /// Use <see cref="AsParallelWriter"/> to create a parallel writer for a list.
        /// </remarks>
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
        public unsafe struct ParallelWriter
        {
            /// <summary>
            /// The data of the list.
            /// </summary>
            [NativeDisableUnsafePtrRestriction]
            public readonly T** Ptr;

            /// <summary>
            /// The UnsafeList to write to.
            /// </summary>
            [NativeDisableUnsafePtrRestriction]
            public UnsafeList<IntPtr>* ListData;

            internal unsafe ParallelWriter(T** ptr, UnsafeList<IntPtr>* listData)
            {
                Ptr = ptr;
                ListData = listData;
            }

            /// <summary>
            /// Adds a pointer to the end of the list.
            /// </summary>
            /// <param name="value">The pointer to add to the end of the list.</param>
            /// <remarks>
            /// Increments the length by 1. Never increases the capacity.
            /// </remarks>
            /// <exception cref="InvalidOperationException">Thrown if incrementing the length would exceed the capacity.</exception>
            public void AddNoResize(T* value) => ListData->AddNoResize((IntPtr)value);

            /// <summary>
            /// Copies pointers from a buffer to the end of the list.
            /// </summary>
            /// <param name="ptr">The buffer to copy from.</param>
            /// <param name="count">The number of pointers to copy from the buffer.</param>
            /// <remarks>
            /// Increments the length by `count`. Never increases the capacity.
            /// </remarks>
            /// <exception cref="InvalidOperationException">Thrown if the increased length would exceed the capacity.</exception>
            public void AddRangeNoResize(T** ptr, int count) => ListData->AddRangeNoResize(ptr, count);

            /// <summary>
            /// Copies the pointers of another list to the end of this list.
            /// </summary>
            /// <param name="list">The other list to copy from.</param>
            /// <remarks>
            /// Increments the length by the length of the other list. Never increases the capacity.
            /// </remarks>
            /// <exception cref="InvalidOperationException">Thrown if the increased length would exceed the capacity.</exception>
            public void AddRangeNoResize(UnsafePtrList<T> list) => ListData->AddRangeNoResize(list.Ptr, list.Length);
        }
    }

    [GenerateTestsForBurstCompatibility]
    internal static class UnsafePtrListExtensions
    {
        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static ref UnsafeList<IntPtr> ListData<T>(ref this UnsafePtrList<T> from) where T : unmanaged => ref UnsafeUtility.As<UnsafePtrList<T>, UnsafeList<IntPtr>>(ref from);

        [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static UnsafeList<IntPtr> ListDataRO<T>(this UnsafePtrList<T> from) where T : unmanaged => UnsafeUtility.As<UnsafePtrList<T>, UnsafeList<IntPtr>>(ref from);
    }

    internal sealed class UnsafePtrListDebugView<T>
        where T : unmanaged
    {
        UnsafePtrList<T> Data;

        public UnsafePtrListDebugView(UnsafePtrList<T> data)
        {
            Data = data;
        }

        public unsafe T*[] Items
        {
            get
            {
                T*[] result = new T*[Data.Length];

                for (var i = 0; i < result.Length; ++i)
                {
                    result[i] = Data.Ptr[i];
                }

                return result;
            }
        }
    }
}
