namespace FluidSim.Audio
{
    public class SimulationRingBuffer
    {
        private readonly float[] _buffer;
        private readonly int _capacity;
        private int _writeHead;      // monotonic, producer only
        private int _readHead;       // monotonic, consumer advances after consumption

        // Consumer interpolation state
        private double _readPosFrac;
        private bool _consumerInit;

        // Events for signalling
        private readonly AutoResetEvent _spaceAvailable = new AutoResetEvent(false);
        private readonly AutoResetEvent _dataAvailable = new AutoResetEvent(false);

        public SimulationRingBuffer(int capacity)
        {
            if ((capacity & (capacity - 1)) != 0)
                throw new ArgumentException("Capacity must be a power of two.");
            _capacity = capacity;
            _buffer = new float[capacity];
        }

        // ---------- Producer ----------
        public int FreeSpace => _capacity - (_writeHead - Volatile.Read(ref _readHead));

        /// <summary>Number of samples currently available for reading (integer count).</summary>
        public int AvailableSamples => Volatile.Read(ref _writeHead) - Volatile.Read(ref _readHead);

        public void Write(float sample)
        {
            while (FreeSpace == 0)
                _spaceAvailable.WaitOne();

            int w = _writeHead;
            int mask = _capacity - 1;
            _buffer[w & mask] = sample;
            Volatile.Write(ref _writeHead, w + 1);
            _dataAvailable.Set();
        }

        public int Write(float[] data, int count)
        {
            int free = FreeSpace;
            int toWrite = Math.Min(count, free);
            int w = _writeHead;
            int mask = _capacity - 1;
            for (int i = 0; i < toWrite; i++)
                _buffer[(w + i) & mask] = data[i];
            Volatile.Write(ref _writeHead, w + toWrite);
            if (toWrite > 0)
                _dataAvailable.Set();
            return toWrite;
        }

        // ---------- Consumer ----------
        public void ResetConsumer() => _consumerInit = false;

        public int ReadInterpolated(float[] dest, int destCount, double speed)
        {
            if (!_consumerInit)
            {
                _readPosFrac = Volatile.Read(ref _readHead);
                _consumerInit = true;
            }

            int mask = _capacity - 1;
            int writeHead = Volatile.Read(ref _writeHead);
            int produced = 0;

            for (int i = 0; i < destCount; i++)
            {
                int idxFloor = (int)_readPosFrac;
                int idxCeil = idxFloor + 1;
                if (idxCeil >= writeHead)
                    break;

                float y0 = _buffer[idxFloor & mask];
                float y1 = _buffer[idxCeil & mask];
                double frac = _readPosFrac - idxFloor;
                dest[i] = (float)(y0 + (y1 - y0) * frac);

                _readPosFrac += speed;
                produced++;
            }

            int newReadHead = (int)_readPosFrac;
            if (newReadHead > Volatile.Read(ref _readHead))
            {
                Volatile.Write(ref _readHead, newReadHead);
                _spaceAvailable.Set();
            }
            return produced;
        }
    }
}