#include "core/audio/audio_effects.hpp"

#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <vector>

#include <iostream>

#include "core/audio/jail_audio.hpp"

namespace AudioEffects {

    namespace {

        // --- Caps de cua ---
        constexpr float ECHO_TAIL_MS = 800.0F;
        constexpr float REVERB_TAIL_MS = 1500.0F;

        // --- Constants Freeverb ---
        // Delays de comb i allpass tunats para 44.1 kHz; los reescalem per
        // freqüència real de la font.
        constexpr int COMB_REFERENCE_RATE = 44100;
        constexpr std::array<int, 8> COMB_DELAYS_L = {1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617};
        constexpr std::array<int, 4> ALLPASS_DELAYS_L = {556, 441, 341, 225};
        constexpr int STEREO_SPREAD = 23;

        // Mapeig de Schroeder/Dattorro/Freeverb estàndard.
        constexpr float FIXED_GAIN = 0.015F;
        constexpr float SCALE_ROOM = 0.28F;
        constexpr float OFFSET_ROOM = 0.7F;
        constexpr float SCALE_DAMP = 0.4F;

        // --- Decodificació a float -1..1 ---
        // Suporta U8/S16, mono/estèreo. Mono es duplica a L i R (la cadena
        // d'efectes treballa siempre con dos canals per simplicitat).
        auto decodeToStereoFloat(const Ja::Sound& src, std::vector<float>& left, std::vector<float>& right) -> bool {
            const auto& spec = src.spec;
            const Uint8* buf = src.buffer.get();
            if (buf == nullptr || src.length == 0) { return false; }

            int bytes_per_sample = 0;
            if (spec.format == SDL_AUDIO_S16) {
                bytes_per_sample = 2;
            } else if (spec.format == SDL_AUDIO_U8) {
                bytes_per_sample = 1;
            } else {
                std::cerr << "[AudioEffects] formato de sonido no soportado (solo U8 o S16)\n";
                return false;
            }
            if (spec.channels < 1 || spec.channels > 2) {
                std::cerr << "[AudioEffects] el sonido debe ser mono o estéreo\n";
                return false;
            }

            const std::size_t TOTAL_FRAMES = src.length / static_cast<std::size_t>(bytes_per_sample * spec.channels);
            left.resize(TOTAL_FRAMES);
            right.resize(TOTAL_FRAMES);

            for (std::size_t i = 0; i < TOTAL_FRAMES; ++i) {
                float sample_l = 0.0F;
                float sample_r = 0.0F;
                if (spec.format == SDL_AUDIO_S16) {
                    const auto* p = reinterpret_cast<const std::int16_t*>(buf + (i * spec.channels * 2));
                    sample_l = static_cast<float>(p[0]) / 32768.0F;
                    sample_r = (spec.channels == 2) ? static_cast<float>(p[1]) / 32768.0F : sample_l;
                } else {  // U8
                    const Uint8* p = buf + (i * spec.channels);
                    sample_l = (static_cast<float>(p[0]) - 128.0F) / 128.0F;
                    sample_r = (spec.channels == 2) ? (static_cast<float>(p[1]) - 128.0F) / 128.0F : sample_l;
                }
                left[i] = sample_l;
                right[i] = sample_r;
            }
            return true;
        }

        // Empaqueta dos canals float (-1..1) a S16 entrellaçat.
        void encodeStereoS16(const std::vector<float>& left, const std::vector<float>& right, std::vector<std::uint8_t>& out) {
            const std::size_t LEN = left.size();
            out.resize(LEN * 2 * sizeof(std::int16_t));
            auto* dst = reinterpret_cast<std::int16_t*>(out.data());
            for (std::size_t i = 0; i < LEN; ++i) {
                const float L = std::clamp(left[i], -1.0F, 1.0F);
                const float R = std::clamp(right[i], -1.0F, 1.0F);
                dst[(i * 2) + 0] = static_cast<std::int16_t>(std::lround(L * 32767.0F));
                dst[(i * 2) + 1] = static_cast<std::int16_t>(std::lround(R * 32767.0F));
            }
        }

        // Reescala un delay de la taula de Freeverb para la freqüència real.
        auto scaledDelay(int reference_delay, int rate) -> int {
            const long SCALED = std::lround(static_cast<double>(reference_delay) * static_cast<double>(rate) / static_cast<double>(COMB_REFERENCE_RATE));
            return std::max(1, static_cast<int>(SCALED));
        }

        // --- Filtres bàsics ---
        struct Comb {
            std::vector<float> buf;
            std::size_t idx{0};
            float feedback{0.0F};
            float damp1{0.0F};
            float damp2{1.0F};
            float store{0.0F};

            void init(int delay, float fb, float damping) {
                buf.assign(static_cast<std::size_t>(delay), 0.0F);
                idx = 0;
                feedback = fb;
                damp1 = damping;
                damp2 = 1.0F - damping;
                store = 0.0F;
            }
            auto tick(float in) -> float {
                const float OUT = buf[idx];
                store = (OUT * damp2) + (store * damp1);
                buf[idx] = in + (store * feedback);
                idx = (idx + 1) % buf.size();
                return OUT;
            }
        };

        struct Allpass {
            std::vector<float> buf;
            std::size_t idx{0};

            void init(int delay) {
                buf.assign(static_cast<std::size_t>(delay), 0.0F);
                idx = 0;
            }
            auto tick(float in) -> float {
                const float BUFOUT = buf[idx];
                const float OUT = -in + BUFOUT;
                buf[idx] = in + (BUFOUT * 0.5F);
                idx = (idx + 1) % buf.size();
                return OUT;
            }
        };

    }  // namespace

    auto applyEcho(const Ja::Sound& src, const Ja::EchoParams& params) -> std::optional<ProcessedSound> {
        std::vector<float> left;
        std::vector<float> right;
        if (!decodeToStereoFloat(src, left, right)) { return std::nullopt; }

        const int RATE = src.spec.freq;
        const int DELAY_SAMPLES = std::max(1, static_cast<int>(std::lround(params.delay_ms * 0.001F * static_cast<float>(RATE))));
        const auto TAIL_SAMPLES = static_cast<std::size_t>(std::lround(ECHO_TAIL_MS * 0.001F * static_cast<float>(RATE)));

        const float FEEDBACK = std::clamp(params.feedback, 0.0F, 0.95F);
        const float WET = std::clamp(params.wet, 0.0F, 1.0F);
        const float DRY = 1.0F - WET;

        const std::size_t INPUT_LEN = left.size();
        const std::size_t TOTAL_LEN = INPUT_LEN + TAIL_SAMPLES;

        std::vector<float> ring_l(static_cast<std::size_t>(DELAY_SAMPLES), 0.0F);
        std::vector<float> ring_r(static_cast<std::size_t>(DELAY_SAMPLES), 0.0F);
        std::size_t cursor = 0;

        std::vector<float> out_l(TOTAL_LEN);
        std::vector<float> out_r(TOTAL_LEN);

        for (std::size_t i = 0; i < TOTAL_LEN; ++i) {
            const float IN_L = (i < INPUT_LEN) ? left[i] : 0.0F;
            const float IN_R = (i < INPUT_LEN) ? right[i] : 0.0F;

            const float DELAYED_L = ring_l[cursor];
            const float DELAYED_R = ring_r[cursor];

            out_l[i] = (DRY * IN_L) + (WET * DELAYED_L);
            out_r[i] = (DRY * IN_R) + (WET * DELAYED_R);

            ring_l[cursor] = IN_L + (DELAYED_L * FEEDBACK);
            ring_r[cursor] = IN_R + (DELAYED_R * FEEDBACK);
            cursor = (cursor + 1) % static_cast<std::size_t>(DELAY_SAMPLES);
        }

        ProcessedSound result;
        result.spec = SDL_AudioSpec{.format = SDL_AUDIO_S16, .channels = 2, .freq = RATE};
        encodeStereoS16(out_l, out_r, result.bytes);
        return result;
    }

    auto applyReverb(const Ja::Sound& src, const Ja::ReverbParams& params) -> std::optional<ProcessedSound> {
        std::vector<float> left;
        std::vector<float> right;
        if (!decodeToStereoFloat(src, left, right)) { return std::nullopt; }

        const int RATE = src.spec.freq;
        const auto TAIL_SAMPLES = static_cast<std::size_t>(std::lround(REVERB_TAIL_MS * 0.001F * static_cast<float>(RATE)));

        const float ROOM_SIZE = std::clamp(params.room_size, 0.0F, 1.0F);
        const float DAMPING = std::clamp(params.damping, 0.0F, 1.0F);
        const float WET = std::clamp(params.wet, 0.0F, 1.0F);
        const float DRY = 1.0F - WET;

        const float FEEDBACK = (ROOM_SIZE * SCALE_ROOM) + OFFSET_ROOM;  // 0.7..0.98
        const float DAMP1 = DAMPING * SCALE_DAMP;                       // 0..0.4

        // Inicialitza los 8 comb filters per cada canal i los 4 allpass.
        std::array<Comb, 8> comb_l;
        std::array<Comb, 8> comb_r;
        for (std::size_t i = 0; i < COMB_DELAYS_L.size(); ++i) {
            comb_l[i].init(scaledDelay(COMB_DELAYS_L[i], RATE), FEEDBACK, DAMP1);
            comb_r[i].init(scaledDelay(COMB_DELAYS_L[i] + STEREO_SPREAD, RATE), FEEDBACK, DAMP1);
        }
        std::array<Allpass, 4> allpass_l;
        std::array<Allpass, 4> allpass_r;
        for (std::size_t i = 0; i < ALLPASS_DELAYS_L.size(); ++i) {
            allpass_l[i].init(scaledDelay(ALLPASS_DELAYS_L[i], RATE));
            allpass_r[i].init(scaledDelay(ALLPASS_DELAYS_L[i] + STEREO_SPREAD, RATE));
        }

        const std::size_t INPUT_LEN = left.size();
        const std::size_t TOTAL_LEN = INPUT_LEN + TAIL_SAMPLES;
        std::vector<float> out_l(TOTAL_LEN);
        std::vector<float> out_r(TOTAL_LEN);

        for (std::size_t i = 0; i < TOTAL_LEN; ++i) {
            const float IN_L = (i < INPUT_LEN) ? left[i] : 0.0F;
            const float IN_R = (i < INPUT_LEN) ? right[i] : 0.0F;
            const float MONO_INPUT = (IN_L + IN_R) * FIXED_GAIN;

            // 8 comb filters en paral·lel, sumats.
            float wet_l = 0.0F;
            float wet_r = 0.0F;
            for (std::size_t k = 0; k < comb_l.size(); ++k) {
                wet_l += comb_l[k].tick(MONO_INPUT);
                wet_r += comb_r[k].tick(MONO_INPUT);
            }
            // 4 allpass en sèrie.
            for (std::size_t k = 0; k < allpass_l.size(); ++k) {
                wet_l = allpass_l[k].tick(wet_l);
                wet_r = allpass_r[k].tick(wet_r);
            }

            out_l[i] = (DRY * IN_L) + (WET * wet_l);
            out_r[i] = (DRY * IN_R) + (WET * wet_r);
        }

        ProcessedSound result;
        result.spec = SDL_AudioSpec{.format = SDL_AUDIO_S16, .channels = 2, .freq = RATE};
        encodeStereoS16(out_l, out_r, result.bytes);
        return result;
    }

}  // namespace AudioEffects
