// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Andrea De Murtas

#pragma once

#include <juce_dsp/juce_dsp.h>
#include "GjallaDSP.h"

namespace gjalla
{

// Bass / mid / treble, sitting between the preamp and the power section
// exactly where a real amp puts it. Modelled as three independent bands rather
// than as a passive Fender-style stack: a passive stack's controls interact,
// which is authentic and infuriating, and this amp is meant to be dialled in
// quickly.
//
//   bass    low shelf   120 Hz    +/-12 dB
//   mid     peak        700 Hz    Q 0.6, +/-12 dB
//   treble  high shelf  3.2 kHz   +/-12 dB
//
// It is a linear filter, but it lives inside the oversampling block because it
// sits between two nonlinear stages: pulling it out to the base rate would cost
// a second up/downsample pair, which is more CPU and more phase smear than
// running three biquads at 4x. Coefficients follow the rate in force.
class ToneStack
{
public:
    struct Settings
    {
        float bassDb = 0.0f;     // -12..+12
        float midDb = 0.0f;      // -12..+12
        float trebleDb = 0.0f;   // -12..+12
    };

    void prepare(int numChannels) noexcept
    {
        channels = juce::jlimit(1, 2, numChannels);
        reset();
    }

    void reset() noexcept
    {
        bass.reset();
        mid.reset();
        treble.reset();
    }

    void process(juce::dsp::AudioBlock<float>& block, const Settings& s, double sampleRate) noexcept
    {
        const auto numSamples = (int) block.getNumSamples();
        const auto nCh = juce::jmin((int) block.getNumChannels(), channels);

        bass.setCoeffs(makeLowShelf(sampleRate, 120.0f, 0.70710678f, s.bassDb));
        mid.setCoeffs(makePeak(sampleRate, 700.0f, 0.6f, s.midDb));
        treble.setCoeffs(makeHighShelf(sampleRate, 3200.0f, 0.70710678f, s.trebleDb));

        for (int ch = 0; ch < nCh; ++ch)
        {
            auto* data = block.getChannelPointer((size_t) ch);
            for (int i = 0; i < numSamples; ++i)
            {
                auto x = bass.processSample(ch, data[i]);
                x = mid.processSample(ch, x);
                data[i] = treble.processSample(ch, x);
            }
        }
    }

private:
    Biquad bass, mid, treble;
    int channels = 2;
};

// Presence: one high shelf at 5 kHz, +/-9 dB, after the power section. On a
// real solid-state head this is a feedback-loop control rather than a tone
// stack band, which is why it sits downstream of the clipping and why it gets
// its own class here. Base rate: it is the first thing after the oversampling
// block closes.
class Presence
{
public:
    void prepare(int numChannels) noexcept
    {
        channels = juce::jlimit(1, 2, numChannels);
        reset();
    }

    void reset() noexcept { shelf.reset(); }

    void process(juce::AudioBuffer<float>& buffer, float presenceDb, double sampleRate) noexcept
    {
        const auto numSamples = buffer.getNumSamples();
        const auto nCh = juce::jmin(channels, buffer.getNumChannels());

        shelf.setCoeffs(makeHighShelf(sampleRate, 5000.0f, 0.70710678f, presenceDb));

        for (int ch = 0; ch < nCh; ++ch)
        {
            auto* data = buffer.getWritePointer(ch);
            for (int i = 0; i < numSamples; ++i)
                data[i] = shelf.processSample(ch, data[i]);
        }
    }

private:
    Biquad shelf;
    int channels = 2;
};

} // namespace gjalla
