using System;
using System.Diagnostics;
using UnityEngine.Assertions;
using Unity.Collections.LowLevel.Unsafe;

namespace Unity.Collections
{
    /// <summary>
    /// A double rewindable allocators <see cref="RewindableAllocator"/>.
    /// </summary>
    unsafe public struct DoubleRewindableAllocators : IDisposable
    {
        RewindableAllocator* Pointer;
        AllocatorHelper<RewindableAllocator> UpdateAllocatorHelper0;
        AllocatorHelper<RewindableAllocator> UpdateAllocatorHelper1;

        /// <summary>
        /// Update the double rewindable allocators, switch Pointer to another allocator and rewind the newly switched allocator.
        /// </summary>
        public void Update()
        {
            var UpdateAllocator0 = (RewindableAllocator*)UnsafeUtility.AddressOf(ref UpdateAllocatorHelper0.Allocator);
            var UpdateAllocator1 = (RewindableAllocator*)UnsafeUtility.AddressOf(ref UpdateAllocatorHelper1.Allocator);
            Pointer = (Pointer == UpdateAllocator0) ? UpdateAllocator1 : UpdateAllocator0;
            CheckIsCreated();
            Allocator.Rewind();
        }

        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        void CheckIsCreated()
        {
            if (!IsCreated)
            {
                throw new InvalidOperationException($"DoubleRewindableAllocators is not created.");
            }
        }

        /// <summary>
        /// Retrieve the current rewindable allocator.
        /// </summary>
        /// <value>The Allocator retrieved.</value>
        public ref RewindableAllocator Allocator
        {
            get
            {
                CheckIsCreated();
                return ref UnsafeUtility.AsRef<RewindableAllocator>(Pointer);
            }
        }

        /// <summary>
        /// Check whether the double rewindable allocators is created.
        /// </summary>
        /// <value>True if current allocator is not null, otherwise false.</value>
        public bool IsCreated => Pointer != null;

        /// <summary>
        /// Construct a double rewindable allocators by allocating the allocators from backingAllocator and registering them.
        /// </summary>
        /// <param name="backingAllocator">Allocator used to allocate the double rewindable allocators.</param>
        /// <param name="initialSizeInBytes">The initial capacity of the allocators, in bytes</param>
        public DoubleRewindableAllocators(AllocatorManager.AllocatorHandle backingAllocator, int initialSizeInBytes)
        {
            this = default;
            Initialize(backingAllocator, initialSizeInBytes);
        }

        /// <summary>
        /// Initialize a double rewindable allocators by allocating the allocators from backingAllocator and registering them.
        /// </summary>
        /// <param name="backingAllocator">Allocator used to allocate the double rewindable allocators.</param>
        /// <param name="initialSizeInBytes">The initial capacity of the allocators, in bytes</param>
        public void Initialize(AllocatorManager.AllocatorHandle backingAllocator, int initialSizeInBytes)
        {
            UpdateAllocatorHelper0 = new AllocatorHelper<RewindableAllocator>(backingAllocator);
            UpdateAllocatorHelper1 = new AllocatorHelper<RewindableAllocator>(backingAllocator);
            UpdateAllocatorHelper0.Allocator.Initialize(initialSizeInBytes);
            UpdateAllocatorHelper1.Allocator.Initialize(initialSizeInBytes);
            Pointer = null;
            Update();
        }

        /// <summary>
        /// the double rewindable allocators and unregister it.
        /// </summary>
        public void Dispose()
        {
            if (!IsCreated)
            {
                return;
            }

            UpdateAllocatorHelper0.Allocator.Dispose();
            UpdateAllocatorHelper1.Allocator.Dispose();

            UpdateAllocatorHelper0.Dispose();
            UpdateAllocatorHelper1.Dispose();
        }

        internal bool EnableBlockFree
        {
            get
            {
                Assert.IsTrue(UpdateAllocatorHelper0.Allocator.EnableBlockFree == UpdateAllocatorHelper1.Allocator.EnableBlockFree);
                return UpdateAllocatorHelper0.Allocator.EnableBlockFree;
            }
            set
            {
                UpdateAllocatorHelper0.Allocator.EnableBlockFree = value;
                UpdateAllocatorHelper1.Allocator.EnableBlockFree = value;
            }
        }
    }
}