#include "DeepPluckChorus.h"
#include "DeepPluckEffectMix.h"

#include <cmath>

namespace deeppluck
{
void DeepPluckChorus::prepare (double sampleRate, int maximumBlockSize)
{
    const auto validRate = std::isfinite (sampleRate) && sampleRate > 0.0 ? sampleRate : 44100.0;
    const auto validBlock = juce::jmax (1, maximumBlockSize);
    const juce::dsp::ProcessSpec monoSpec {
        validRate, static_cast<juce::uint32> (validBlock), 1
    };

    leftChorus.prepare (monoSpec);
    rightChorus.prepare (monoSpec);
    leftChorus.setCentreDelay (6.5f);
    rightChorus.setCentreDelay (9.5f);
    leftChorus.setFeedback (0.0f);
    rightChorus.setFeedback (0.0f);
    leftChorus.setMix (1.0f);
    rightChorus.setMix (1.0f);
    wetBuffer.setSize (2, validBlock, false, true, false);
    mix.reset (validRate, smoothingSeconds);
    width.reset (validRate, smoothingSeconds);
    reset();
}

void DeepPluckChorus::reset() noexcept
{
    leftChorus.reset();
    rightChorus.reset();
    wetBuffer.clear();
    mix.setCurrentAndTargetValue (0.0f);
    width.setCurrentAndTargetValue (0.65f);
    active = false;
}

void DeepPluckChorus::setParameters (const DeepPluckParameterSnapshot& snapshot) noexcept
{
    const auto targetMix = snapshot.chorusBypass >= 0.5f
                               ? 0.0f : juce::jlimit (0.0f, 1.0f, snapshot.chorusMix);
    if (! active && targetMix > 0.0f)
    {
        leftChorus.reset();
        rightChorus.reset();
        mix.setCurrentAndTargetValue (0.0f);
        active = true;
    }

    leftChorus.setRate (juce::jlimit (0.05f, 4.0f, snapshot.chorusRateHz));
    rightChorus.setRate (juce::jlimit (0.05f, 4.0f, snapshot.chorusRateHz));
    leftChorus.setDepth (juce::jlimit (0.0f, 1.0f, snapshot.chorusDepth));
    rightChorus.setDepth (juce::jlimit (0.0f, 1.0f, snapshot.chorusDepth));
    width.setTargetValue (juce::jlimit (0.0f, 1.0f, snapshot.chorusWidth));
    mix.setTargetValue (targetMix);
}

void DeepPluckChorus::process (juce::AudioBuffer<float>& buffer,
                               int startSample,
                               int numSamples) noexcept
{
    if (! active || numSamples <= 0)
        return;

    jassert (buffer.getNumChannels() == 2);
    jassert (numSamples <= wetBuffer.getNumSamples());
    wetBuffer.copyFrom (0, 0, buffer, 0, startSample, numSamples);
    wetBuffer.copyFrom (1, 0, buffer, 1, startSample, numSamples);

    auto wetBlock = juce::dsp::AudioBlock<float> (wetBuffer).getSubBlock (
        0, static_cast<std::size_t> (numSamples));
    auto leftBlock = wetBlock.getSingleChannelBlock (0);
    auto rightBlock = wetBlock.getSingleChannelBlock (1);
    juce::dsp::ProcessContextReplacing<float> leftContext (leftBlock);
    juce::dsp::ProcessContextReplacing<float> rightContext (rightBlock);
    leftChorus.process (leftContext);
    rightChorus.process (rightContext);

    for (auto sample = 0; sample < numSamples; ++sample)
    {
        const auto wetLeft = wetBuffer.getSample (0, sample);
        const auto wetRight = wetBuffer.getSample (1, sample);
        const auto mid = 0.5f * (wetLeft + wetRight);
        const auto side = 0.5f * (wetLeft - wetRight) * width.getNextValue();
        const auto amount = juce::jlimit (0.0f, 1.0f, mix.getNextValue());
        const auto gains = calculateEffectMixGains (amount, wetCalibration);
        const auto index = startSample + sample;
        const auto dryLeft = buffer.getSample (0, index);
        const auto dryRight = buffer.getSample (1, index);
        buffer.setSample (0, index, dryLeft * gains.dry + (mid + side) * gains.wet);
        buffer.setSample (1, index, dryRight * gains.dry + (mid - side) * gains.wet);
    }

    if (mix.getTargetValue() <= 0.0f && ! mix.isSmoothing() && mix.getCurrentValue() <= 0.0f)
    {
        leftChorus.reset();
        rightChorus.reset();
        active = false;
    }
}

bool DeepPluckChorus::isIdle() const noexcept
{
    return ! active;
}
}
