using Core.Interfaces;
using System.IO;

namespace Core.Audio
{
    public class SmsApu
    {
        // Your existing variables
        public ushort[] Registers { get; private set; } = new ushort[8];
        private int _latchedRegister = 0;

        // THE NEW CONNECTION: Where we send the audio!
        public IAudioDevice AudioDevice { get; set; }

        // --- TIMING VARIABLES ---
        private const double ClockRate = 3579545.0; // NTSC Master System speed
        private const int SampleRate = 44100;       // CD-Quality Audio
        private double _cyclesPerSample = ClockRate / SampleRate;
        private double _sampleCycleTracker = 0;
        private int _psgCycleTracker = 0;

        // --- SYNTHESIZER STATE ---
        private int[] _counters = new int[4];
        private int[] _polarities = new int[4] { 1, 1, 1, 1 }; // 1 = High, -1 = Low
        private ushort _lfsr = 0x8000; // Linear Feedback Shift Register (For Noise)
        // --- FILTER VARIABLES ---
        private float _previousSample = 0f;
        private float _previousFiltered = 0f;

        // The SN76489 Volume Table reduces amplitude by exactly 2 decibels per step.
        private static readonly float[] VolumeTable = {
            1.0f, 0.7943f, 0.6309f, 0.5011f, 0.3981f, 0.3162f, 0.2511f, 0.1995f,
            0.1584f, 0.1258f, 0.1000f, 0.0794f, 0.0630f, 0.0501f, 0.0398f, 0.0f
        };

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
        public SmsApu()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
        {
            Registers[1] = 0x0F;
            Registers[3] = 0x0F;
            Registers[5] = 0x0F;
            Registers[7] = 0x0F;
        }

        // [KEEP YOUR EXISTING WritePort7F METHOD HERE]

        public void Update(int tStates)
        {
            for (int i = 0; i < tStates; i++)
            {
                // 1. The hardware chip only updates its wave counters every 16 CPU cycles
                _psgCycleTracker++;
                if (_psgCycleTracker >= 16)
                {
                    _psgCycleTracker = 0;
                    TickChannels();
                }

                // 2. We only want to generate 44,100 samples per second, not 3.58 million!
                _sampleCycleTracker++;
                if (_sampleCycleTracker >= _cyclesPerSample)
                {
                    _sampleCycleTracker -= _cyclesPerSample;
                    MixAndOutputSample();
                }
            }
        }

        private void TickChannels()
        {
            // --- TONE CHANNELS (0, 1, and 2) ---
            for (int i = 0; i < 3; i++)
            {
                _counters[i]--;
                if (_counters[i] <= 0)
                {
                    // Reload the counter from the Tone Register. 
                    // HARDWARE QUIRK: A tone register of 0 acts as 1024!
                    int tone = Registers[i * 2];
                    _counters[i] = (tone == 0) ? 1024 : tone;

                    // Flip the wave polarity! (This creates the vibration of the sound)
                    _polarities[i] *= -1;
                }
            }

            // --- NOISE CHANNEL (3) ---
            _counters[3]--;
            if (_counters[3] <= 0)
            {
                // 1. Reload the counter
                int shiftRate = Registers[6] & 0x03;
                if (shiftRate == 0) _counters[3] = 0x10;      // Fast
                else if (shiftRate == 1) _counters[3] = 0x20; // Medium
                else if (shiftRate == 2) _counters[3] = 0x40; // Slow
                else _counters[3] = (Registers[4] == 0) ? 1024 : Registers[4]; // Linked to Tone 2

                // 2. NO FLIP FLOP! Shift the LFSR immediately!
                bool isWhiteNoise = (Registers[6] & 0x04) != 0;
                
                // Read the bits BEFORE shifting
                int bit0 = _lfsr & 1;
                int bit3 = (_lfsr >> 3) & 1;
                
                // The Master System physically XORs bit 0 and bit 3 for White Noise.
                // For Periodic Noise, it just feeds bit 0 straight back in.
                int feedback = isWhiteNoise ? (bit0 ^ bit3) : bit0;
                
                // Shift the register right and push the feedback into the highest bit (Bit 15)
                _lfsr = (ushort)((_lfsr >> 1) | (feedback << 15));

                // The noise speaker is driven directly by the lowest bit
                _polarities[3] = (_lfsr & 1) == 1 ? 1 : -1;
            }
        }

        private void MixAndOutputSample()
        {
            if (AudioDevice == null) return;

            float sample = 0f;

            // Mix Tone Channels 0, 1, 2
            for (int i = 0; i < 3; i++)
            {
                int tone = Registers[i * 2];

                // THE FIX 1: Catch ALL ultrasonic frequencies (1 through 5) to stop Aliasing!
                if (tone >= 0 && tone <= 5)
                {
                    // THE FIX 2: Scale it by 0.5f. A fast square wave spends 50% of its time 
                    // high, so it naturally averages out to half volume. This prevents clipping!
                    sample += 0.5f * VolumeTable[Registers[(i * 2) + 1]];
                }
                else
                {
                    sample += _polarities[i] * VolumeTable[Registers[(i * 2) + 1]];
                }
            }

            // Mix Noise Channel 3
            sample += _polarities[3] * VolumeTable[Registers[7]];

            // Divide by 4 so all 4 channels together never exceed 1.0f
            sample /= 4.0f;

            // THE FIX 3: The DC Blocker (1-Pole High-Pass Filter)
            // PCM speech creates a massive DC offset (pushes the speaker cone forward and leaves it there).
            // This filter smoothly pulls the speaker cone back to center at 35Hz, removing the hum and distortion!
            float filteredSample = sample - _previousSample + 0.995f * _previousFiltered;

            _previousSample = sample;
            _previousFiltered = filteredSample;

            AudioDevice.AddSample(filteredSample);
        }
        public void WritePort7F(byte value)
        {
            if ((value & 0x80) != 0)
            {
                // --- LATCH BYTE --- (Bit 7 is 1)
                // Bits 4-6 contain the Register Index (0 to 7)
                _latchedRegister = (value >> 4) & 0x07;

                // Bits 0-3 contain the lower 4 bits of data
                int data = value & 0x0F;

                if (_latchedRegister % 2 != 0 || _latchedRegister == 6)
                {
                    // Volume registers (1, 3, 5, 7) and Noise Control (6) only hold 4 bits total.
                    // We completely overwrite them.
                    Registers[_latchedRegister] = (ushort)data;
                    if (_latchedRegister == 6) _lfsr = 0x8000;
                }
                else
                {
                    // Tone registers (0, 2, 4) hold 10 bits. 
                    // A Latch byte ONLY overwrites the bottom 4 bits and leaves the top 6 alone!
                    Registers[_latchedRegister] = (ushort)((Registers[_latchedRegister] & 0x03F0) | data);
                    if (_latchedRegister == 6) _lfsr = 0x8000;
                }
            }
            else
            {
                // --- DATA BYTE --- (Bit 7 is 0)
                // Bits 0-5 contain the upper 6 bits of data for the currently latched Tone register
                int data = value & 0x3F;

                if (_latchedRegister % 2 == 0 && _latchedRegister != 6)
                {
                    // Update the top 6 bits of the 10-bit Tone register
                    Registers[_latchedRegister] = (ushort)((Registers[_latchedRegister] & 0x000F) | (data << 4));
                }
                else
                {
                    // If a Data Byte is sent to a Volume or Noise register, it just overwrites the lower 4 bits again
                    Registers[_latchedRegister] = (ushort)(data & 0x0F);
                }
            }
        }

        public void SaveState(BinaryWriter bw)
        {
            for (int i = 0; i < 8; i++) bw.Write(Registers[i]);
            bw.Write(_latchedRegister);
            bw.Write(_sampleCycleTracker);
            bw.Write(_psgCycleTracker);
            for (int i = 0; i < 4; i++) { bw.Write(_counters[i]); bw.Write(_polarities[i]); }
            bw.Write(_lfsr);
            bw.Write(_previousSample);
            bw.Write(_previousFiltered);
        }

        public void LoadState(BinaryReader br)
        {
            for (int i = 0; i < 8; i++) Registers[i] = br.ReadUInt16();
            _latchedRegister = br.ReadInt32();
            _sampleCycleTracker = br.ReadDouble();
            _psgCycleTracker = br.ReadInt32();
            for (int i = 0; i < 4; i++) { _counters[i] = br.ReadInt32(); _polarities[i] = br.ReadInt32(); }
            _lfsr = br.ReadUInt16();
            _previousSample = br.ReadSingle();
            _previousFiltered = br.ReadSingle();
        }
    }
}
