using Ryujinx.Audio;
using Ryujinx.Audio.Renderer;
using Ryujinx.Audio.Renderer.Integration;
using System;
using System.Collections.Generic;
using System.Threading;

namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
    public class AalHardwareDevice : HardwareDevice
    {
        private IAalOutput _output;
        private int _trackId;
        private int _bufferTag;
        private int _nextTag;
        private AutoResetEvent _releaseEvent;

        private uint _channelCount;
        private uint _sampleRate;

        private short[] _buffer;

        private Queue<long> _releasedTags;

        public AalHardwareDevice(int bufferTag, IAalOutput output, uint channelCount, uint sampleRate)
        {
            _bufferTag = bufferTag;
            _channelCount = channelCount;
            _sampleRate = sampleRate;
            _output = output;
            _releaseEvent = new AutoResetEvent(true);
            _trackId = _output.OpenTrack((int)sampleRate, (int)channelCount, AudioCallback);
            _releasedTags = new Queue<long>();

            _buffer = new short[RendererConstants.TargetSampleCount * channelCount];

            _output.Start(_trackId);
        }

        private void AudioCallback()
        {
            long[] released = _output.GetReleasedBuffers(_trackId, int.MaxValue);

            lock (_releasedTags)
            {
                foreach (long tag in released)
                {
                    _releasedTags.Enqueue(tag);
                }
            }
        }

        private long GetReleasedTag()
        {
            lock (_releasedTags)
            {
                if (_releasedTags.Count > 0)
                {
                    return _releasedTags.Dequeue();
                }

                return (_bufferTag << 16) | (_nextTag++);
            }
        }

        public void AppendBuffer(ReadOnlySpan<short> data, uint channelCount)
        {
            data.CopyTo(_buffer.AsSpan());

            _output.AppendBuffer(_trackId, GetReleasedTag(), _buffer);
        }

        public uint GetChannelCount()
        {
            return _channelCount;
        }

        public uint GetSampleRate()
        {
            return _sampleRate;
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _output.Stop(_trackId);
                _output.CloseTrack(_trackId);
                _releaseEvent.Dispose();
            }
        }
    }
}