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;
using UnityEngine.TestTools;
using UnityEngine;

[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 struct GenericContainerReadonlyJob<T> : IJob
{
    [ReadOnly] 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;
    }

    void NativeBitArray_IsCreated_Uninitialized()
    {
        NativeBitArray container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        NativeBitArray.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void UnsafeBitArray_IsCreated_Uninitialized()
    {
        UnsafeBitArray container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        UnsafeBitArray.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void NativeHashMap_IsCreated_Uninitialized()
    {
        NativeHashMap<int, int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        NativeHashMap<int, int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void UnsafeHashMap_IsCreated_Uninitialized()
    {
        UnsafeHashMap<int, int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        UnsafeHashMap<int, int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void NativeHashSet_IsCreated_Uninitialized()
    {
        NativeHashSet<int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        NativeHashSet<int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void UnsafeHashSet_IsCreated_Uninitialized()
    {
        UnsafeHashSet<int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        UnsafeHashSet<int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void NativeList_IsCreated_Uninitialized()
    {
        NativeList<int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        NativeArray<int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        // NativeArray doesn't have IsEmpty property -> Assert.True(containerRo.IsEmpty);
    }

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

    void UnsafeList_IsCreated_Uninitialized()
    {
        UnsafeList<int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        UnsafeList<int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void UnsafePtrList_IsCreated_Uninitialized()
    {
        UnsafePtrList<int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        UnsafePtrList<int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void NativeParallelHashMap_IsCreated_Uninitialized()
    {
        NativeParallelHashMap<int, int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        NativeParallelHashMap<int, int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void UnsafeParallelHashMap_IsCreated_Uninitialized()
    {
        UnsafeParallelHashMap<int, int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        UnsafeParallelHashMap<int, int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void NativeParallelHashSet_IsCreated_Uninitialized()
    {
        NativeParallelHashSet<int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        NativeParallelHashSet<int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void UnsafeParallelHashSet_IsCreated_Uninitialized()
    {
        UnsafeParallelHashSet<int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        UnsafeParallelHashSet<int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void NativeParallelMultiHashMap_IsCreated_Uninitialized()
    {
        NativeParallelMultiHashMap<int, int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        NativeParallelMultiHashMap<int, int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void UnsafeParallelMultiHashMap_IsCreated_Uninitialized()
    {
        UnsafeParallelMultiHashMap<int, int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);

        UnsafeParallelMultiHashMap<int, int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
        Assert.True(containerRo.IsEmpty);
    }

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

    void NativeQueue_IsCreated_Uninitialized()
    {
        NativeQueue<int> container = default;
        Assert.False(container.IsCreated);

        NativeQueue<int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
    }

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

    void UnsafeQueue_IsCreated_Uninitialized()
    {
        UnsafeQueue<int> container = default;
        Assert.False(container.IsCreated);

        UnsafeQueue<int>.ReadOnly containerRo = default;
        Assert.False(containerRo.IsCreated);
    }

    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;
    }

    void NativeRingQueue_IsCreated_Uninitialized()
    {
        NativeRingQueue<int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);
    }

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

    void UnsafeRingQueue_IsCreated_Uninitialized()
    {
        UnsafeRingQueue<int> container = default;
        Assert.False(container.IsCreated);
        Assert.True(container.IsEmpty);
    }

    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;
    }

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

    [Test]
    public void Test_IsCreated_Uninitialized()
    {
        NativeBitArray_IsCreated_Uninitialized();
        UnsafeBitArray_IsCreated_Uninitialized();

        NativeHashMap_IsCreated_Uninitialized();
        UnsafeHashMap_IsCreated_Uninitialized();

        NativeHashSet_IsCreated_Uninitialized();
        UnsafeHashSet_IsCreated_Uninitialized();

        NativeList_IsCreated_Uninitialized();
        UnsafeList_IsCreated_Uninitialized();

        UnsafePtrList_IsCreated_Uninitialized();

        NativeParallelHashMap_IsCreated_Uninitialized();
        UnsafeParallelHashMap_IsCreated_Uninitialized();

        NativeParallelHashSet_IsCreated_Uninitialized();
        UnsafeParallelHashSet_IsCreated_Uninitialized();

        NativeParallelMultiHashMap_IsCreated_Uninitialized();
        UnsafeParallelMultiHashMap_IsCreated_Uninitialized();

        NativeQueue_IsCreated_Uninitialized();
        UnsafeQueue_IsCreated_Uninitialized();

        NativeRingQueue_IsCreated_Uninitialized();
        UnsafeRingQueue_IsCreated_Uninitialized();
    }

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

    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")]
    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")]
    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));
    }

    [Test]
    [TestRequiresCollectionChecks("Tests job depending argument range checks - crashes without safety system")]
    [Timeout(20*60000 /* 20 minutes */)]
    [UnityPlatform(RuntimePlatform.WindowsEditor)] // some unnamed platforms don't have enough memory to run this test
    public unsafe void Test_Container_Initialize_NumElements_To_MaxCapacity()
    {
        int[] range = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        bool runSlowTests = false;

        {
            var container = new NativeArray<int>(int.MaxValue, Allocator.Persistent);
            Assert.AreEqual(int.MaxValue, container.Length);
            container.Dispose();
        }

        {
            var container = new NativeBitArray(NativeBitArray.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(NativeBitArray.MaxCapacity, container.Capacity);
            container.Dispose();
        }

        {
            var container = new UnsafeBitArray(UnsafeBitArray.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(UnsafeBitArray.MaxCapacity, container.Capacity);
            container.Dispose();
        }

        {
            var container = new NativeHashMap<int, int>(NativeHashMap<int, int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(NativeHashMap<int, int>.MaxCapacity, container.Capacity);

            if (runSlowTests)
            {
                for (int i = 0; i < container.Capacity; ++i)
                {
                    container.Add(i, i);
                }

                Assert.IsFalse(container.TryAdd(NativeHashMap<int, int>.MaxCapacity + 0x1234, 0x5678));
                Assert.Throws<InvalidOperationException>(() => container.Add(NativeHashMap<int, int>.MaxCapacity + 0x1234, 0x5678));
            }

            container.Dispose();
        }

        {
            var container = new UnsafeHashMap<int, int>(UnsafeHashMap<int, int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(UnsafeHashMap<int, int>.MaxCapacity, container.Capacity);

            if (runSlowTests)
            {
                for (int i = 0; i < container.Capacity; ++i)
                {
                    container.Add(i, i);
                }

                Assert.IsFalse(container.TryAdd(UnsafeHashMap<int, int>.MaxCapacity + 0x1234, 0x5678));
                Assert.Throws<InvalidOperationException>(() => container.Add(UnsafeHashMap<int, int>.MaxCapacity + 0x1234, 0x5678));
            }

            container.Dispose();
        }

        {
            var container = new NativeHashSet<int>(NativeHashSet<int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(NativeHashSet<int>.MaxCapacity, container.Capacity);
            container.Dispose();
        }

        {
            var container = new UnsafeHashSet<int>(UnsafeHashSet<int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(UnsafeHashSet<int>.MaxCapacity, container.Capacity);
            container.Dispose();
        }

        {
            var container = new NativeList<int>(NativeList<int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(NativeList<int>.MaxCapacity, container.Capacity);

            container.Length = container.Capacity;
            Assert.AreEqual(NativeList<int>.MaxCapacity, container.Length);

            Assert.Throws<InvalidOperationException>(() => container.Add(0x1234));
            Assert.Throws<InvalidOperationException>(() => container.AddReplicate(0x1234, 10));
            Assert.Throws<InvalidOperationException>(() => container.InsertRangeWithBeginEnd(10, 20));
            Assert.Throws<InvalidOperationException>(() => container.InsertRange(10, 10));

            container.Dispose();
        }

        {
            var container = new UnsafeList<int>(UnsafeList<int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(UnsafeList<int>.MaxCapacity, container.Capacity);

            container.Length = container.Capacity;
            Assert.AreEqual(UnsafeList<int>.MaxCapacity, container.Length);

            Assert.Throws<InvalidOperationException>(() => container.Add(0x1234));
            Assert.Throws<InvalidOperationException>(() => container.AddReplicate(0x1234, 10));
            Assert.Throws<InvalidOperationException>(() => container.InsertRangeWithBeginEnd(10, 20));
            Assert.Throws<InvalidOperationException>(() => container.InsertRange(10, 10));

            container.Dispose();
        }

        {
            var container = new NativeParallelHashMap<int, int>(NativeParallelHashMap<int, int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(NativeParallelHashMap<int, int>.MaxCapacity, container.Capacity);

            if (runSlowTests)
            {
                for (int i = 0; i < container.Capacity; ++i)
                {
                    container.Add(i, i);
                }

                Assert.IsFalse(container.TryAdd(NativeParallelHashMap<int, int>.MaxCapacity + 0x1234, 0x5678));
                Assert.Throws<InvalidOperationException>(() => container.Add(NativeParallelHashMap<int, int>.MaxCapacity + 0x1234, 0x5678));
            }

            container.Dispose();
        }

        {
            var container = new UnsafeParallelHashMap<int, int>(UnsafeParallelHashMap<int, int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(UnsafeParallelHashMap<int, int>.MaxCapacity, container.Capacity);

            if (runSlowTests)
            {
                for (int i = 0; i < container.Capacity; ++i)
                {
                    container.Add(i, i);
                }

                Assert.IsFalse(container.TryAdd(UnsafeParallelHashMap<int, int>.MaxCapacity + 0x1234, 0x5678));
                Assert.Throws<InvalidOperationException>(() => container.Add(UnsafeParallelHashMap<int, int>.MaxCapacity + 0x1234, 0x5678));
            }

            container.Dispose();
        }

        {
            var container = new NativeParallelMultiHashMap<int, int>(NativeParallelMultiHashMap<int, int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(NativeParallelMultiHashMap<int, int>.MaxCapacity, container.Capacity);
            container.Dispose();
        }

        {
            var container = new UnsafeParallelMultiHashMap<int, int>(UnsafeParallelMultiHashMap<int, int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(UnsafeParallelMultiHashMap<int, int>.MaxCapacity, container.Capacity);
            container.Dispose();
        }

        {
            var container = new NativeParallelHashSet<int>(NativeParallelHashSet<int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(NativeParallelHashSet<int>.MaxCapacity, container.Capacity);
            container.Dispose();
        }

        {
            var container = new UnsafeParallelHashSet<int>(UnsafeParallelHashSet<int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(UnsafeParallelHashSet<int>.MaxCapacity, container.Capacity);
            container.Dispose();
        }

        {
            var container = new NativeRingQueue<int>(NativeRingQueue<int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(NativeRingQueue<int>.MaxCapacity, container.Capacity);
            container.Dispose();
        }

        {
            var container = new UnsafeRingQueue<int>(UnsafeRingQueue<int>.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(UnsafeRingQueue<int>.MaxCapacity, container.Capacity);
            container.Dispose();
        }

        {
            var container = new NativeText(NativeText.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(NativeText.MaxCapacity, container.Capacity);
            container.Dispose();
        }

        {
            var container = new UnsafeText(UnsafeText.MaxCapacity, Allocator.Persistent);
            Assert.AreEqual(UnsafeText.MaxCapacity, container.Capacity);
            container.Dispose();
        }
    }

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

    // Avoid running this test when on older unity releases since those editor versions
    // used a global safety handle for temp allocations which could lead to invalid safety errors
    // if we were to perform the safety checks when writing the Length that this test validates
    // (The test uses Persistent allocations, but the code in NativeList.Length is conditional on
    // this define so we make the tests conditional as well)
#if UNITY_2022_2_16F1_OR_NEWER
    void Test_Change_Length_Missing_Dependency<T, U>(T container)
        where T : unmanaged, IIndexable<U>
        where U : unmanaged
    {
        int localLength = 0;
        // Readonly Job
        {
            var job = new GenericContainerReadonlyJob<T>() { data = container };
            var jobHandle = job.Schedule();
            Assert.DoesNotThrow(() => localLength = container.Length); // Reading is safe
            Assert.Throws<InvalidOperationException>(() => container.Length = 0); // Writing while a job is in flight it not safe
            jobHandle.Complete();
            Assert.DoesNotThrow(() => container.Length = 0);
        }

        // ReadWrite job
        {
            var job = new GenericContainerJob<T>() { data = container };
            var jobHandle = job.Schedule();
            Assert.Throws<InvalidOperationException>(() => localLength = container.Length); // Reading is not safe
            Assert.Throws<InvalidOperationException>(() => container.Length = 0); // Writing while a job is in flight it not safe
            jobHandle.Complete();
            Assert.DoesNotThrow(() => localLength = container.Length);
            Assert.DoesNotThrow(() => container.Length = 0);
        }
    }

    [Test]
    [TestRequiresCollectionChecks()]
    public void IIndexable_Change_Length_Missing_Dependency()
    {
        var container = new NativeList<int>(16, Allocator.Persistent);
        Test_Change_Length_Missing_Dependency<NativeList<int>, int>(container);
        container.Dispose();
    }
#endif
    //-------------------------------------------------------------------------------------------------------

    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
    }
}