using NUnit.Framework;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Collections.Tests;
using Unity.Jobs;
using System;
using Unity.Jobs.Tests.ManagedJobs;

[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeArray<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeAppendBuffer>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeBitArray>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeBitArray>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeHashMap<int, int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeHashMap<int, int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeHashSet<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeHashSet<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeList<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeList<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafePtrList<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeParallelHashMap<int, int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeParallelHashMap<int, int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeParallelHashSet<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeParallelHashSet<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeParallelMultiHashMap<int, int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeParallelMultiHashMap<int, int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeQueue<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeQueue<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeReference<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeRingQueue<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeRingQueue<int>>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeStream>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeStream>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<NativeText>))]
[assembly: RegisterGenericJobType(typeof(GenericContainerJob<UnsafeText>))]

internal struct GenericContainerJob<T> : IJob
{
    public T data;
    public void Execute()
    {
        // We just care about creating job dependencies
    }
}

internal class GenericContainerTests : CollectionsTestFixture
{
    UnsafeAppendBuffer CreateEmpty_UnsafeAppendBuffer()
    {
        var container = new UnsafeAppendBuffer(0, 8, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    NativeBitArray CreateEmpty_NativeBitArray()
    {
        var container = new NativeBitArray(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }


    UnsafeBitArray CreateEmpty_UnsafeBitArray()
    {
        var container = new UnsafeBitArray(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    NativeHashMap<int, int> CreateEmpty_NativeHashMap()
    {
        var container = new NativeHashMap<int, int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    UnsafeHashMap<int, int> CreateEmpty_UnsafeHashMap()
    {
        var container = new UnsafeHashMap<int, int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    NativeHashSet<int> CreateEmpty_NativeHashSet()
    {
        var container = new NativeHashSet<int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    UnsafeHashSet<int> CreateEmpty_UnsafeHashSet()
    {
        var container = new UnsafeHashSet<int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    NativeList<int> CreateEmpty_NativeList()
    {
        var container = new NativeList<int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    UnsafeList<int> CreateEmpty_UnsafeList()
    {
        var container = new UnsafeList<int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    UnsafePtrList<int> CreateEmpty_UnsafePtrList()
    {
        var container = new UnsafePtrList<int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    NativeParallelHashMap<int, int> CreateEmpty_NativeParallelHashMap()
    {
        var container = new NativeParallelHashMap<int, int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    UnsafeParallelHashMap<int, int> CreateEmpty_UnsafeParallelHashMap()
    {
        var container = new UnsafeParallelHashMap<int, int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    NativeParallelHashSet<int> CreateEmpty_NativeParallelHashSet()
    {
        var container = new NativeParallelHashSet<int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    UnsafeParallelHashSet<int> CreateEmpty_UnsafeParallelHashSet()
    {
        var container = new UnsafeParallelHashSet<int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    NativeParallelMultiHashMap<int, int> CreateEmpty_NativeParallelMultiHashMap()
    {
        var container = new NativeParallelMultiHashMap<int, int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    UnsafeParallelMultiHashMap<int, int> CreateEmpty_UnsafeParallelMultiHashMap()
    {
        var container = new UnsafeParallelMultiHashMap<int, int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    NativeQueue<int> CreateEmpty_NativeQueue()
    {
        var container = new NativeQueue<int>(Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty());
        return container;
    }

    UnsafeQueue<int> CreateEmpty_UnsafeQueue()
    {
        var container = new UnsafeQueue<int>(Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty());
        return container;
    }

    NativeReference<int> CreateEmpty_NativeReference()
    {
        var container = new NativeReference<int>(Allocator.Persistent);
        Assert.True(container.IsCreated);
        return container;
    }

    NativeRingQueue<int> CreateEmpty_NativeRingQueue()
    {
        var container = new NativeRingQueue<int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    UnsafeRingQueue<int> CreateEmpty_UnsafeRingQueue()
    {
        var container = new UnsafeRingQueue<int>(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    NativeStream CreateEmpty_NativeStream()
    {
        var container = new NativeStream(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty());
        return container;
    }

    UnsafeStream CreateEmpty_UnsafeStream()
    {
        var container = new UnsafeStream(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty());
        return container;
    }

    NativeText CreateEmpty_NativeText()
    {
        var container = new NativeText(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    UnsafeText CreateEmpty_UnsafeText()
    {
        var container = new UnsafeText(0, Allocator.Persistent);
        Assert.True(container.IsCreated);
        Assert.True(container.IsEmpty);
        return container;
    }

    //-------------------------------------------------------------------------------------------------------

    void Test_Dispose_Uninitialized<T>()
        where T : INativeDisposable
    {
        T uninitialized = default;
        Assert.DoesNotThrow(() => uninitialized.Dispose());
        Assert.DoesNotThrow(() => uninitialized.Dispose(default));
    }

    [Test]
    public void INativeDisposable_Dispose_Uninitialized()
    {
        Test_Dispose_Uninitialized<NativeBitArray>();
        Test_Dispose_Uninitialized<NativeHashMap<int, int>>();
        Test_Dispose_Uninitialized<NativeHashSet<int>>();
        Test_Dispose_Uninitialized<NativeList<int>>();
        Test_Dispose_Uninitialized<NativeParallelHashMap<int, int>>();
        Test_Dispose_Uninitialized<NativeParallelHashSet<int>>();
        Test_Dispose_Uninitialized<NativeParallelMultiHashMap<int, int>>();
        Test_Dispose_Uninitialized<NativeQueue<int>>();
        Test_Dispose_Uninitialized<NativeReference<int>>();
        Test_Dispose_Uninitialized<NativeRingQueue<int>>();
        Test_Dispose_Uninitialized<NativeStream>();
        Test_Dispose_Uninitialized<NativeText>();

        Test_Dispose_Uninitialized<UnsafeAppendBuffer>();
        Test_Dispose_Uninitialized<UnsafeBitArray>();
        Test_Dispose_Uninitialized<UnsafeHashMap<int, int>>();
        Test_Dispose_Uninitialized<UnsafeHashSet<int>>();
        Test_Dispose_Uninitialized<UnsafeList<int>>();
        Test_Dispose_Uninitialized<UnsafePtrList<int>>();
        Test_Dispose_Uninitialized<UnsafeParallelHashMap<int, int>>();
        Test_Dispose_Uninitialized<UnsafeParallelHashSet<int>>();
        Test_Dispose_Uninitialized<UnsafeParallelMultiHashMap<int, int>>();
        Test_Dispose_Uninitialized<UnsafeQueue<int>>();
        Test_Dispose_Uninitialized<UnsafeRingQueue<int>>();
        Test_Dispose_Uninitialized<UnsafeStream>();
        Test_Dispose_Uninitialized<UnsafeText>();
    }

    //-------------------------------------------------------------------------------------------------------

    void Test_Unsafe_Double_Dispose<T>(T container)
        where T : INativeDisposable
    {
        Assert.DoesNotThrow(() => container.Dispose());
        Assert.DoesNotThrow(() => container.Dispose());
    }

    void Test_Native_Double_Dispose<T>(T container)
        where T : INativeDisposable
    {
        Assert.DoesNotThrow(() => container.Dispose());
#if ENABLE_UNITY_COLLECTIONS_CHECKS
        Assert.Throws<ObjectDisposedException>(() => container.Dispose());
#else
        Assert.DoesNotThrow(() => container.Dispose());
#endif
    }

    [Test]
    public void INativeDisposable_Init_Double_Dispose()
    {
        Test_Native_Double_Dispose(CreateEmpty_NativeBitArray());
        Test_Native_Double_Dispose(CreateEmpty_NativeHashMap());
        Test_Native_Double_Dispose(CreateEmpty_NativeHashSet());
        Test_Native_Double_Dispose(CreateEmpty_NativeList());
        Test_Native_Double_Dispose(CreateEmpty_NativeParallelHashMap());
        Test_Native_Double_Dispose(CreateEmpty_NativeParallelHashSet());
        Test_Native_Double_Dispose(CreateEmpty_NativeParallelMultiHashMap());
        Test_Native_Double_Dispose(CreateEmpty_NativeQueue());
        Test_Native_Double_Dispose(CreateEmpty_NativeReference());
        Test_Native_Double_Dispose(CreateEmpty_NativeRingQueue());
        Test_Native_Double_Dispose(CreateEmpty_NativeStream());
        Test_Native_Double_Dispose(CreateEmpty_NativeText());

        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeAppendBuffer());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeBitArray());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeHashMap());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeHashSet());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeList());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafePtrList());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeParallelHashMap());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeParallelHashSet());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeParallelMultiHashMap());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeQueue());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeRingQueue());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeStream());
        Test_Unsafe_Double_Dispose(CreateEmpty_UnsafeText());
    }

    //-------------------------------------------------------------------------------------------------------

    void Test_Unsafe_Double_Dispose_Job<T>(T container)
        where T : INativeDisposable
    {
        Assert.DoesNotThrow(() => container.Dispose(default));
        Assert.DoesNotThrow(() => container.Dispose(default));
    }

    void Test_Native_Double_Dispose_Job<T>(T container)
        where T : INativeDisposable
    {
        Assert.DoesNotThrow(() => container.Dispose(default));
#if ENABLE_UNITY_COLLECTIONS_CHECKS
        Assert.Throws<ObjectDisposedException>(() => container.Dispose(default));
#else
        Assert.DoesNotThrow(() => container.Dispose(default));
#endif
    }

    [Test]
    public void INativeDisposable_Init_Double_Dispose_Job()
    {
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeBitArray());
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeHashMap());
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeHashSet());
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeList());
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeParallelHashMap());
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeParallelHashSet());
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeParallelMultiHashMap());
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeQueue());
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeReference());
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeRingQueue());
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeStream());
        Test_Native_Double_Dispose_Job(CreateEmpty_NativeText());

        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeAppendBuffer());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeBitArray());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeHashMap());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeHashSet());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeList());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafePtrList());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeParallelHashMap());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeParallelHashSet());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeParallelMultiHashMap());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeQueue());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeRingQueue());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeStream());
        Test_Unsafe_Double_Dispose_Job(CreateEmpty_UnsafeText());
    }

    //-------------------------------------------------------------------------------------------------------

    void Test_Dispose_Job_Missing_Dependency<T>(T container)
        where T : INativeDisposable
    {
        GenericContainerJob<T> job = new GenericContainerJob<T>() { data = container };
        JobHandle jobHandle = job.Schedule();
        Assert.Throws<InvalidOperationException>(() => container.Dispose(default));
        Assert.DoesNotThrow(() => jobHandle = container.Dispose(jobHandle));
        jobHandle.Complete();
    }

    [Test]
    [TestRequiresCollectionChecks("Tests dispose job while another job is scheduled - crashes without safety system")]
    [DotsRuntimeFixme("DOTS Runtime doesn't detect safety handles in the generic container")]
    public void INativeDisposable_Dispose_Job_Missing_Dependency()
    {
        Test_Dispose_Job_Missing_Dependency(new NativeBitArray(16, Allocator.Persistent));
        Test_Dispose_Job_Missing_Dependency(new NativeHashMap<int, int>(16, Allocator.Persistent));
        Test_Dispose_Job_Missing_Dependency(new NativeHashSet<int>(16, Allocator.Persistent));
        Test_Dispose_Job_Missing_Dependency(new NativeList<int>(16, Allocator.Persistent));
        Test_Dispose_Job_Missing_Dependency(new NativeParallelHashMap<int, int>(16, Allocator.Persistent));
        Test_Dispose_Job_Missing_Dependency(new NativeParallelHashSet<int>(16, Allocator.Persistent));
        Test_Dispose_Job_Missing_Dependency(new NativeParallelMultiHashMap<int, int>(16, Allocator.Persistent));
        Test_Dispose_Job_Missing_Dependency(new NativeQueue<int>(Allocator.Persistent));
        Test_Dispose_Job_Missing_Dependency(new NativeReference<int>(16, Allocator.Persistent));
        Test_Dispose_Job_Missing_Dependency(new NativeRingQueue<int>(16, Allocator.Persistent));
        Test_Dispose_Job_Missing_Dependency(new NativeStream(16, Allocator.Persistent));
        Test_Dispose_Job_Missing_Dependency(new NativeText(16, Allocator.Persistent));
    }

    //-------------------------------------------------------------------------------------------------------

    void Test_Dispose_Job_Then_Schedule_Work<T>(T container)
        where T : INativeDisposable
    {
        GenericContainerJob<T> job = new GenericContainerJob<T>() { data = container };
        JobHandle jobHandle = container.Dispose(default);
        Assert.Throws<InvalidOperationException>(() => job.Schedule(jobHandle));
        jobHandle.Complete();
    }

    [Test]
    [TestRequiresCollectionChecks("Tests job depending on a dispose job with same data - crashes without safety system")]
    [DotsRuntimeFixme("DOTS Runtime doesn't detect safety handles in the generic container")]
    public void INativeDisposable_Dispose_Job_Then_Schedule_Work()
    {
        Test_Dispose_Job_Then_Schedule_Work(new NativeBitArray(16, Allocator.Persistent));
        Test_Dispose_Job_Then_Schedule_Work(new NativeHashMap<int, int>(16, Allocator.Persistent));
        Test_Dispose_Job_Then_Schedule_Work(new NativeHashSet<int>(16, Allocator.Persistent));
        Test_Dispose_Job_Then_Schedule_Work(new NativeList<int>(16, Allocator.Persistent));
        Test_Dispose_Job_Then_Schedule_Work(new NativeParallelHashMap<int, int>(16, Allocator.Persistent));
        Test_Dispose_Job_Then_Schedule_Work(new NativeParallelHashSet<int>(16, Allocator.Persistent));
        Test_Dispose_Job_Then_Schedule_Work(new NativeParallelMultiHashMap<int, int>(16, Allocator.Persistent));
        Test_Dispose_Job_Then_Schedule_Work(new NativeQueue<int>(Allocator.Persistent));
        Test_Dispose_Job_Then_Schedule_Work(new NativeReference<int>(16, Allocator.Persistent));
        Test_Dispose_Job_Then_Schedule_Work(new NativeRingQueue<int>(16, Allocator.Persistent));
        Test_Dispose_Job_Then_Schedule_Work(new NativeStream(16, Allocator.Persistent));
        Test_Dispose_Job_Then_Schedule_Work(new NativeText(16, Allocator.Persistent));
    }

    struct NativeHashMapJobForEach : IJob
    {
        public NativeHashMap<int, int> input;
        public void Execute()
        {
            foreach (var _ in input) { }
        }
    }

    struct NativeHashMapJobForEachReadOnly : IJob
    {
        public NativeHashMap<int, int>.ReadOnly input;
        public void Execute()
        {
            foreach (var _ in input) { }
        }
    }

    struct NativeHashMapJobForEachEnumerator : IJob
    {
        [ReadOnly]
        public NativeHashMap<int, int>.Enumerator input;

        public void Execute()
        {
            while (input.MoveNext()) { }
        }
    }

    [Test]
    [TestRequiresCollectionChecks("Tests depend on safety system to catch incorrect use.")]
    public void ForEach()
    {
        // CreateEmpty_NativeBitArray();

        {
            var container = CreateEmpty_NativeHashMap();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
                new NativeHashMapJobForEach { input = container }.Run();
                new NativeHashMapJobForEachReadOnly { input = ro }.Run();
                new NativeHashMapJobForEachEnumerator { input = container.GetEnumerator() }.Run();
                new NativeHashMapJobForEachEnumerator { input = ro.GetEnumerator() }.Run();
            });

            {
                var job = new NativeHashMapJobForEach { input = container }.Schedule();
                Assert.Throws<InvalidOperationException>(() => { container.Add(123, 456); });
                job.Complete();
                Assert.DoesNotThrow(() => container.Add(123, 456));
                container.Clear();
            }

            {
                var job = new NativeHashMapJobForEachReadOnly { input = ro }.Schedule();
                Assert.Throws<InvalidOperationException>(() => { container.Add(123, 456); });
                job.Complete();
                Assert.DoesNotThrow(() => container.Add(123, 456));
                container.Clear();
            }

            {
                var job = new NativeHashMapJobForEachEnumerator { input = container.GetEnumerator() }.Schedule();
                Assert.Throws<InvalidOperationException>(() => { container.Add(123, 456); });
                job.Complete();
                Assert.DoesNotThrow(() => container.Add(123, 456));
                container.Clear();
            }

            {
                var job = new NativeHashMapJobForEachEnumerator { input = ro.GetEnumerator() }.Schedule();
                Assert.Throws<InvalidOperationException>(() => { container.Add(123, 456); });
                job.Complete();
                Assert.DoesNotThrow(() => container.Add(123, 456));
                container.Clear();
            }

            {
                var iter = container.GetEnumerator();
                container.Add(123, 456);
                Assert.Throws<ObjectDisposedException>(() => { while (iter.MoveNext()) { } });
                Assert.DoesNotThrow(() => container.Remove(123));
                Assert.AreEqual(0, container.Count);
            }

            {
                var iter = container.AsReadOnly().GetEnumerator();
                container.Add(123, 456);
                Assert.Throws<ObjectDisposedException>(() => { while (iter.MoveNext()) { } });
                Assert.DoesNotThrow(() => container.Remove(123));
                Assert.AreEqual(0, container.Count);
            }

            container.Dispose();
        }

        {
            var container = CreateEmpty_NativeHashSet();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        {
            var container = CreateEmpty_NativeList();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        {
            var container = CreateEmpty_NativeParallelHashMap();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        {
            var container = CreateEmpty_NativeParallelHashSet();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        {
            var container = CreateEmpty_NativeParallelMultiHashMap();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        {
            var container = CreateEmpty_NativeQueue();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
//                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        // CreateEmpty_NativeReference();
        // CreateEmpty_NativeRingQueue();
        // CreateEmpty_NativeStream();

        {
            var container = CreateEmpty_NativeText();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        // CreateEmpty_UnsafeAppendBuffer();
        // CreateEmpty_UnsafeBitArray

        {
            var container = CreateEmpty_UnsafeHashMap();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        {
            var container = CreateEmpty_UnsafeHashSet();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        {
            var container = CreateEmpty_UnsafeList();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        // CreateEmpty_UnsafePtrList(); - not possible to implement intefrace because container returns T*, and interface wants T as return value.

        {
            var container = CreateEmpty_UnsafeParallelHashMap();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        {
            var container = CreateEmpty_UnsafeParallelHashSet();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        {
            var container = CreateEmpty_UnsafeParallelMultiHashMap();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        {
            var container = CreateEmpty_UnsafeQueue();
            var ro = container.AsReadOnly();

            GCAllocRecorder.ValidateNoGCAllocs(() =>
            {
//                foreach (var item in container) { }
                foreach (var item in ro) { }
            });

            container.Dispose();
        }

        // CreateEmpty_UnsafeRingQueue
        // CreateEmpty_UnsafeStream
        // CreateEmpty_UnsafeText
    }
}
