#pragma once

#include "DeepPluckOscillator.h"

#include <juce_audio_basics/juce_audio_basics.h>

#include <array>
#include <cstdint>

namespace deeppluck
{
inline constexpr int maximumUnisonVoices = 4;

struct DeepPluckStereoSample
{
    float left = 0.0f;
    float right = 0.0f;
};

struct DeepPluckUnisonOscillatorParameters
{
    DeepPluckOscillatorWaveform primaryWaveform = DeepPluckOscillatorWaveform::saw;
    float primaryPulseWidth = 0.5f;
    DeepPluckOscillatorWaveform secondWaveform = DeepPluckOscillatorWaveform::saw;
    float secondPulseWidth = 0.5f;
};

class DeepPluckUnison final
{
public:
    explicit DeepPluckUnison (std::uint32_t voiceIndexToUse) noexcept;

    void prepare (double newSampleRate) noexcept;
    void noteOn (int midiNoteNumber, int newVoiceCount,
                 float detuneCents, float stereoSpread,
                 const DeepPluckUnisonOscillatorParameters& oscillatorParameters = {}) noexcept;
    void setTargets (float detuneCents, float stereoSpread) noexcept;
    void setDetuneTarget (float detuneCents) noexcept;
    void setStereoSpreadTarget (float stereoSpread) noexcept;
    void setOscillatorTargets (
        const DeepPluckUnisonOscillatorParameters& oscillatorParameters) noexcept;
    void reset() noexcept;

    [[nodiscard]] DeepPluckStereoSample processSample (
        double primaryFrequencyHz, double secondFrequencyHz,
        float secondOscillatorLevel, float secondOscillatorMix) noexcept;

    [[nodiscard]] int getActiveVoiceCount() const noexcept { return activeVoiceCount; }
    [[nodiscard]] float getNormalisationGain() const noexcept { return normalisationGain; }
    [[nodiscard]] double getPrimaryPhase (int index) const noexcept;
    [[nodiscard]] double getSecondPhase (int index) const noexcept;
    [[nodiscard]] double getCurrentDetuneRatio (int index) const noexcept;
    [[nodiscard]] float getCurrentLeftGain (int index) const noexcept;
    [[nodiscard]] float getCurrentRightGain (int index) const noexcept;
    [[nodiscard]] DeepPluckOscillatorWaveform getPrimaryWaveform (int index) const noexcept;
    [[nodiscard]] DeepPluckOscillatorWaveform getSecondWaveform (int index) const noexcept;
    [[nodiscard]] float getPrimaryPulseWidth (int index) const noexcept;
    [[nodiscard]] float getSecondPulseWidth (int index) const noexcept;

    [[nodiscard]] static float detuneFactor (int voiceCount, int index) noexcept;
    [[nodiscard]] static float panPosition (int voiceCount, int index) noexcept;
    [[nodiscard]] static double phaseOffset (int voiceCount, int index) noexcept;
    [[nodiscard]] static float normalisationFor (int voiceCount) noexcept;

private:
    void applyDetuneTarget (bool initialise) noexcept;
    void applyStereoSpreadTarget (bool initialise) noexcept;
    [[nodiscard]] static int boundedIndex (int index) noexcept;

    std::array<DeepPluckOscillator, maximumUnisonVoices> primaryOscillators;
    std::array<DeepPluckOscillator, maximumUnisonVoices> secondOscillators;
    std::array<juce::SmoothedValue<double, juce::ValueSmoothingTypes::Multiplicative>,
               maximumUnisonVoices> detuneRatios;
    std::array<juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear>,
               maximumUnisonVoices> leftPanGains;
    std::array<juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear>,
               maximumUnisonVoices> rightPanGains;
    double sampleRate = 44100.0;
    std::uint32_t voiceIndex = 0;
    int midiNote = 69;
    int activeVoiceCount = 1;
    float orientation = 1.0f;
    float targetDetuneCents = 0.0f;
    float targetStereoSpread = 0.0f;
    float normalisationGain = 1.0f;
    DeepPluckUnisonOscillatorParameters oscillatorTargets;
};
}
