using System.Threading;
using Unity.Mathematics;
namespace Unity.Collections.LowLevel.Unsafe
{
    /// 
    /// A 32-bit atomic counter.
    /// 
    /// Rather than have its own int, a counter *points* to an int. This arrangement lets counters in different jobs share reference to the same underlying int.
    [GenerateTestsForBurstCompatibility]
    public unsafe struct UnsafeAtomicCounter32
    {
        /// 
        /// The int that is modified by this counter.
        /// 
        /// The int that is modified by this counter.
        public int* Counter;
        /// 
        /// Initializes and returns an instance of UnsafeAtomicCounter32.
        /// 
        /// A pointer to the int to be modified by this counter.
        public UnsafeAtomicCounter32(void* ptr)
        {
            Counter = (int*)ptr;
        }
        /// 
        /// Non-atomically sets this counter to a value.
        /// 
        /// The value to set. Defaults to 0
        public void Reset(int value = 0)
        {
            *Counter = value;
        }
        /// 
        /// Atomically adds a value to this counter.
        /// 
        /// The value to add.
        /// The original value before the add.
        public int Add(int value)
        {
            return Interlocked.Add(ref UnsafeUtility.AsRef(Counter), value) - value;
        }
        /// 
        /// Atomically subtracts a value from this counter.
        /// 
        /// The value to subtract.
        /// The original value before the subtract.
        public int Sub(int value) => Add(-value);
        /// 
        /// Atomically adds a value to this counter. The result will not be greater than a maximum value.
        /// 
        /// The value to add to this counter.
        /// The maximum which the result will not be greater than.
        /// The original value before the add.
        public int AddSat(int value, int max = int.MaxValue)
        {
            int oldVal;
            int newVal = *Counter;
            do
            {
                oldVal = newVal;
                newVal = newVal >= max ? max : math.min(max, newVal + value);
                newVal = Interlocked.CompareExchange(ref UnsafeUtility.AsRef(Counter), newVal, oldVal);
            }
            while (oldVal != newVal && oldVal != max);
            return oldVal;
        }
        /// 
        /// Atomically subtracts a value from this counter. The result will not be less than a minimum value.
        /// 
        /// The value to subtract from this counter.
        /// The minimum which the result will not be less than.
        /// The original value before the subtract.
        public int SubSat(int value, int min = int.MinValue)
        {
            int oldVal;
            int newVal = *Counter;
            do
            {
                oldVal = newVal;
                newVal = newVal <= min ? min : math.max(min, newVal - value);
                newVal = Interlocked.CompareExchange(ref UnsafeUtility.AsRef(Counter), newVal, oldVal);
            }
            while (oldVal != newVal && oldVal != min);
            return oldVal;
        }
    }
    /// 
    /// A 64-bit atomic counter.
    /// 
    /// Rather than have its own long, a counter *points* to a long. This arrangement lets counters in different jobs share reference to the same underlying long.
    [GenerateTestsForBurstCompatibility]
    public unsafe struct UnsafeAtomicCounter64
    {
        /// 
        /// The long that is modified by this counter.
        /// 
        /// The long that is modified by this counter.
        public long* Counter;
        /// 
        /// Initializes and returns an instance of UnsafeAtomicCounter64.
        /// 
        /// A pointer to the long to be modified by this counter.
        public UnsafeAtomicCounter64(void* ptr)
        {
            Counter = (long*)ptr;
        }
        /// 
        /// Non-atomically sets this counter to a value.
        /// 
        /// The value to set. Defaults to 0
        public void Reset(long value = 0)
        {
            *Counter = value;
        }
        /// 
        /// Atomically adds a value to this counter.
        /// 
        /// The value to add.
        /// The original value before the add.
        public long Add(long value)
        {
            return Interlocked.Add(ref UnsafeUtility.AsRef(Counter), value) - value;
        }
        /// 
        /// Atomically subtracts a value from this counter.
        /// 
        /// The value to subtract.
        /// The original value before the subtract.
        public long Sub(long value) => Add(-value);
        /// 
        /// Atomically adds a value to this counter. The result will not be greater than a maximum value.
        /// 
        /// The value to add to this counter.
        /// The maximum which the result will not be greater than.
        /// The original value before the add.
        public long AddSat(long value, long max = long.MaxValue)
        {
            long oldVal;
            long newVal = *Counter;
            do
            {
                oldVal = newVal;
                newVal = newVal >= max ? max : math.min(max, newVal + value);
                newVal = Interlocked.CompareExchange(ref UnsafeUtility.AsRef(Counter), newVal, oldVal);
            }
            while (oldVal != newVal && oldVal != max);
            return oldVal;
        }
        /// 
        /// Atomically subtracts a value from this counter. The result will not be less than a minimum value.
        /// 
        /// The value to subtract from this counter.
        /// The minimum which the result will not be less than.
        /// The original value before the subtract.
        public long SubSat(long value, long min = long.MinValue)
        {
            long oldVal;
            long newVal = *Counter;
            do
            {
                oldVal = newVal;
                newVal = newVal <= min ? min : math.max(min, newVal - value);
                newVal = Interlocked.CompareExchange(ref UnsafeUtility.AsRef(Counter), newVal, oldVal);
            }
            while (oldVal != newVal && oldVal != min);
            return oldVal;
        }
    }
}