using System;

namespace FluidSim.Core
{
    public class OutdoorExhaustReverb
    {
        // ========== Early reflection delays (stereo: left/right) ==========
        private readonly DelayLine groundL, groundR;
        private readonly DelayLine wall1L, wall1R;
        private readonly DelayLine wall2L, wall2R;

        // ========== Diffuse tail FDNs (left/right each with 8 channels) ==========
        private const int FDN_CHANNELS = 8;
        private readonly DelayLine[] fdnL, fdnR;
        private readonly float[] stateL, stateR;
        private readonly OrthonormalMixer mixerL, mixerR;
        private readonly LowPassFilter[] filterL, filterR;

        public float DryMix { get; set; } = 1.0f;    // direct sound unchanged
        public float EarlyMix { get; set; } = 0.12f;   // very little early reflection (ground bounce)
        public float TailMix { get; set; } = 0.18f;   // subtle diffuse tail
        public float Feedback { get; set; } = 0.35f;   // lower feedback – outdoor doesn't ring
        public float DampingFreq { get; set; } = 2500f;   // air absorption – high frequencies die quickly

        public OutdoorExhaustReverb(int sampleRate)
        {
            // Early reflections – left/right offset by ~1‑2 ms for stereo width
            groundL = new DelayLine((int)(sampleRate * 0.008));   // 8 ms
            groundR = new DelayLine((int)(sampleRate * 0.010));   // 10 ms
            wall1L  = new DelayLine((int)(sampleRate * 0.045));
            wall1R  = new DelayLine((int)(sampleRate * 0.047));
            wall2L  = new DelayLine((int)(sampleRate * 0.080));
            wall2R  = new DelayLine((int)(sampleRate * 0.082));

            // FDN delay lengths – prime numbers, offset between L/R
            int[] lengthsL = { 3203, 4027, 5521, 7027, 8521, 10007, 11503, 13009 };
            int[] lengthsR = { 3217, 4049, 5531, 7043, 8537, 10037, 11519, 13033 };
            fdnL = new DelayLine[FDN_CHANNELS];
            fdnR = new DelayLine[FDN_CHANNELS];
            for (int i = 0; i < FDN_CHANNELS; i++)
            {
                int lenL = Math.Min(lengthsL[i], (int)(sampleRate * 0.25));
                int lenR = Math.Min(lengthsR[i], (int)(sampleRate * 0.25));
                fdnL[i] = new DelayLine(lenL);
                fdnR[i] = new DelayLine(lenR);
            }

            stateL = new float[FDN_CHANNELS];
            stateR = new float[FDN_CHANNELS];
            mixerL = new OrthonormalMixer(FDN_CHANNELS);
            mixerR = new OrthonormalMixer(FDN_CHANNELS);

            filterL = new LowPassFilter[FDN_CHANNELS];
            filterR = new LowPassFilter[FDN_CHANNELS];
            for (int i = 0; i < FDN_CHANNELS; i++)
            {
                filterL[i] = new LowPassFilter(sampleRate, DampingFreq);
                filterR[i] = new LowPassFilter(sampleRate, DampingFreq);
            }
        }

        /// <summary>Stereo reverb – returns (left, right) sample pair.</summary>
        public (float left, float right) ProcessStereo(float drySample)
        {
            // ---- Early reflections ----
            float gL = groundL.ReadWrite(drySample * 0.8f);
            float gR = groundR.ReadWrite(drySample * 0.8f);
            float w1L = wall1L.ReadWrite(drySample * 0.5f);
            float w1R = wall1R.ReadWrite(drySample * 0.5f);
            float w2L = wall2L.ReadWrite(drySample * 0.4f);
            float w2R = wall2R.ReadWrite(drySample * 0.4f);

            float earlyL = (gL + w1L + w2L) * EarlyMix;
            float earlyR = (gR + w1R + w2R) * EarlyMix;

            // ---- Read diffuse tail ----
            float[] delOutL = new float[FDN_CHANNELS];
            float[] delOutR = new float[FDN_CHANNELS];
            for (int i = 0; i < FDN_CHANNELS; i++)
            {
                delOutL[i] = fdnL[i].Read();
                delOutR[i] = fdnR[i].Read();
            }

            // Mix via orthonormal matrix
            float[] mixL = new float[FDN_CHANNELS];
            float[] mixR = new float[FDN_CHANNELS];
            mixerL.Process(delOutL, mixL);
            mixerR.Process(delOutR, mixR);

            // Feedback + air absorption
            for (int i = 0; i < FDN_CHANNELS; i++)
            {
                stateL[i] = drySample * 0.15f + Feedback * mixL[i];
                stateL[i] = filterL[i].Process(stateL[i]);
                fdnL[i].Write(stateL[i]);

                stateR[i] = drySample * 0.15f + Feedback * mixR[i];
                stateR[i] = filterR[i].Process(stateR[i]);
                fdnR[i].Write(stateR[i]);
            }

            float tailL = 0.0f, tailR = 0.0f;
            for (int i = 0; i < FDN_CHANNELS; i++)
            {
                tailL += delOutL[i];
                tailR += delOutR[i];
            }
            tailL *= TailMix;
            tailR *= TailMix;

            float left  = drySample * DryMix + earlyL + tailL;
            float right = drySample * DryMix + earlyR + tailR;
            return (left, right);
        }

        /// <summary>Mono fallback – sums left+right / 2.</summary>
        public float Process(float drySample)
        {
            var (l, r) = ProcessStereo(drySample);
            return MathF.Tanh((l + r) * 0.5f);
        }

        // ========== Helper classes ==========
        private class DelayLine
        {
            private float[] buffer;
            private int writePos;
            public DelayLine(int length)
            {
                buffer = new float[Math.Max(length, 1)];
            }
            public float Read() => buffer[writePos];
            public void Write(float value)
            {
                buffer[writePos] = value;
                writePos = (writePos + 1) % buffer.Length;
            }
            public float ReadWrite(float value)
            {
                float outVal = buffer[writePos];
                buffer[writePos] = value;
                writePos = (writePos + 1) % buffer.Length;
                return outVal;
            }
        }

        private class LowPassFilter
        {
            private float b0, a1, y1;
            private float sampleRate;
            public LowPassFilter(int sampleRate, float cutoff)
            {
                this.sampleRate = sampleRate;
                SetCutoff(cutoff);
            }
            public void SetCutoff(float cutoff)
            {
                float w = 2 * (float)Math.PI * cutoff / sampleRate;
                float a0 = 1 + w;
                b0 = w / a0;
                a1 = (1 - w) / a0;
            }
            public float Process(float x)
            {
                float y = b0 * x - a1 * y1;
                y1 = y;
                return y;
            }
        }

        private class OrthonormalMixer
        {
            private int size;
            public OrthonormalMixer(int size) => this.size = size;

            public void Process(float[] input, float[] output)
            {
                float sum = 0;
                for (int i = 0; i < size; i++) sum += input[i];
                float factor = 2.0f / size;
                for (int i = 0; i < size; i++)
                    output[i] = factor * sum - input[i];
            }
        }
    }
}