#pragma once

#include "../Parameters/DeepPluckParameters.h"
#include "DeepPluckEnvelope.h"
#include "DeepPluckFilter.h"
#include "DeepPluckSound.h"
#include "DeepPluckTransientNoise.h"
#include "DeepPluckUnison.h"

#include <juce_audio_basics/juce_audio_basics.h>

namespace deeppluck
{
namespace benchmark { struct Access; }
class DeepPluckVoice final : public juce::SynthesiserVoice
{
public:
    DeepPluckVoice (const DeepPluckParameterSnapshot& parametersToRead,
                    std::uint32_t voiceIndex) noexcept;

    bool canPlaySound (juce::SynthesiserSound* sound) override;
    void startNote (int midiNoteNumber, float velocity, juce::SynthesiserSound* sound,
                    int currentPitchWheelPosition) override;
    void stopNote (float velocity, bool allowTailOff) override;
    void pitchWheelMoved (int) override {}
    void controllerMoved (int, int) override {}
    void renderNextBlock (juce::AudioBuffer<float>& outputBuffer,
                          int startSample, int numSamples) override;
    void setCurrentPlaybackSampleRate (double newRate) override;
    void setModulationBlock (const float* samples, int absoluteStartSample,
                             int numberOfSamples) noexcept;

    [[nodiscard]] double getOscillatorPhase() const noexcept
    {
        return unison.getPrimaryPhase (0);
    }
    [[nodiscard]] double getCurrentFrequencyHz() const noexcept { return smoothedFrequency.getCurrentValue(); }
    [[nodiscard]] double getSecondOscillatorPhase() const noexcept
    {
        return unison.getSecondPhase (0);
    }
    [[nodiscard]] double getCurrentSecondOscillatorFrequencyHz() const noexcept
    {
        return smoothedSecondFrequency.getCurrentValue();
    }
    [[nodiscard]] float getCurrentSecondOscillatorMix() const noexcept
    {
        return secondOscillatorMix.getCurrentValue();
    }
    [[nodiscard]] float getCurrentSecondOscillatorLevelGain() const noexcept
    {
        return secondOscillatorLevel.getCurrentValue();
    }
    [[nodiscard]] DeepPluckOscillatorWaveform getPrimaryWaveform() const noexcept
    {
        return unison.getPrimaryWaveform (0);
    }
    [[nodiscard]] DeepPluckOscillatorWaveform getSecondWaveform() const noexcept
    {
        return unison.getSecondWaveform (0);
    }
    [[nodiscard]] float getCurrentPrimaryPulseWidth() const noexcept
    {
        return unison.getPrimaryPulseWidth (0);
    }
    [[nodiscard]] float getCurrentSecondPulseWidth() const noexcept
    {
        return unison.getSecondPulseWidth (0);
    }
    [[nodiscard]] bool isEnvelopeActive() const noexcept { return envelope.isActive(); }
    [[nodiscard]] bool isFilterEnvelopeActive() const noexcept { return filterEnvelope.isActive(); }
    [[nodiscard]] bool isFilterEnvelopeReleasing() const noexcept
    {
        return filterEnvelope.isReleasing();
    }
    [[nodiscard]] float getCurrentFilterEnvelopeLevel() const noexcept
    {
        return filterEnvelope.getCurrentLevel();
    }
    [[nodiscard]] float getCurrentFilterCutoffHz() const noexcept
    {
        return filters[0].getCutoffFrequencyHz();
    }
    [[nodiscard]] float getCurrentFilterMix() const noexcept { return filterMix.getCurrentValue(); }
    [[nodiscard]] bool isTransientActive() const noexcept
    {
        return transientNoise.isTransientActive();
    }
    [[nodiscard]] bool isNoiseActive() const noexcept { return transientNoise.isNoiseActive(); }
    [[nodiscard]] float getCurrentTransientMix() const noexcept
    {
        return transientNoise.getTransientMix();
    }
    [[nodiscard]] float getCurrentNoiseMix() const noexcept
    {
        return transientNoise.getNoiseMix();
    }
    [[nodiscard]] bool isReleasing() const noexcept { return envelope.isReleasing(); }
    [[nodiscard]] float getCurrentAudibleLevel() const noexcept
    {
        return envelope.getCurrentLevel() * velocityGain;
    }
    [[nodiscard]] int getLatchedUnisonVoiceCount() const noexcept
    {
        return unison.getActiveVoiceCount();
    }
    [[nodiscard]] double getUnisonPrimaryPhase (int index) const noexcept
    {
        return unison.getPrimaryPhase (index);
    }
    [[nodiscard]] double getUnisonSecondPhase (int index) const noexcept
    {
        return unison.getSecondPhase (index);
    }
    [[nodiscard]] double getCurrentUnisonDetuneRatio (int index) const noexcept
    {
        return unison.getCurrentDetuneRatio (index);
    }
    [[nodiscard]] float getCurrentUnisonLeftGain (int index) const noexcept
    {
        return unison.getCurrentLeftGain (index);
    }
    [[nodiscard]] float getCurrentUnisonRightGain (int index) const noexcept
    {
        return unison.getCurrentRightGain (index);
    }
    [[nodiscard]] float getLastModulationValue() const noexcept
    {
        return lastModulationValue;
    }
    [[nodiscard]] double getLastEffectivePrimaryFrequencyHz() const noexcept
    {
        return lastEffectivePrimaryFrequencyHz;
    }
    [[nodiscard]] float getLastEffectiveFilterCutoffHz() const noexcept
    {
        return lastEffectiveFilterCutoffHz;
    }
    [[nodiscard]] float getLastEffectiveStereoSpread() const noexcept
    {
        return lastEffectiveStereoSpread;
    }
    [[nodiscard]] float getCurrentLfoFilterAmount() const noexcept
    {
        return lfoFilterAmount.getCurrentValue();
    }
    [[nodiscard]] float getCurrentLfoPitchAmount() const noexcept
    {
        return lfoPitchAmount.getCurrentValue();
    }
    [[nodiscard]] float getCurrentLfoSpreadAmount() const noexcept
    {
        return lfoSpreadAmount.getCurrentValue();
    }
    void discardResidualTail() noexcept
    {
        residualTailStart.fill (0.0f);
        residualTailSamplesRemaining = 0;
    }

private:
    friend struct benchmark::Access;
    [[nodiscard]] double noteFrequency (int midiNoteNumber) const noexcept;
    [[nodiscard]] double secondNoteFrequency (int midiNoteNumber) const noexcept;
    [[nodiscard]] float secondOscillatorTargetGain() const noexcept;
    [[nodiscard]] DeepPluckUnisonOscillatorParameters oscillatorParameters() const noexcept;
    [[nodiscard]] float modulationValueForSample (int absoluteSample) const noexcept;
    void resetVoice (bool preserveResidualTail = false) noexcept;

    const DeepPluckParameterSnapshot& parameters;
    DeepPluckUnison unison;
    DeepPluckEnvelope envelope;
    DeepPluckEnvelope filterEnvelope { DeepPluckEnvelope::Contour::filter };
    DeepPluckTransientNoise transientNoise;
    std::array<DeepPluckFilter, 2> filters;
    juce::SmoothedValue<double, juce::ValueSmoothingTypes::Linear> smoothedFrequency;
    juce::SmoothedValue<double, juce::ValueSmoothingTypes::Multiplicative>
        smoothedSecondFrequency;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> secondOscillatorLevel;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> secondOscillatorMix;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> smoothedCutoffOctaves;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> smoothedResonance;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> smoothedKeyTracking;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> smoothedEnvAmount;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> filterMix;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> lfoFilterAmount;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> lfoPitchAmount;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> lfoSpreadAmount;
    const float* modulationSamples = nullptr;
    int modulationStartSample = 0;
    int modulationSampleCount = 0;
    int spreadControlSamplesRemaining = 0;
    int noteNumber = 69;
    float velocityGain = 0.0f;
    float lastModulationValue = 0.0f;
    double lastEffectivePrimaryFrequencyHz = 440.0;
    float lastEffectiveFilterCutoffHz = 1800.0f;
    float lastEffectiveStereoSpread = 0.35f;
    std::array<float, 2> lastRenderedSamples {};
    std::array<float, 2> residualTailStart {};
    int residualTailSamplesRemaining = 0;
    int residualTailLength = 1;
};
}
