#include "PluginProcessor.h"
#include "PluginEditor.h"

ChromaFlockProcessor::ChromaFlockProcessor()
    : AudioProcessor(BusesProperties()
          .withOutput("Output", juce::AudioChannelSet::stereo(), true)),
      apvts(*this, nullptr, "Parameters", createParameterLayout()) {
    synth.addSound(new SubtractiveSound());
    for (int i = 0; i < maxVoices; ++i)
        synth.addVoice(new SubtractiveVoice());
}

ChromaFlockProcessor::~ChromaFlockProcessor() {}

juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::createParameterLayout() {
    juce::AudioProcessorValueTreeState::ParameterLayout layout;

    auto zeroOne = juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f);

    // OSC 1
    layout.add(std::make_unique<juce::AudioParameterChoice>(
        juce::ParameterID{"osc1Wave", 1}, "OSC1 Wave",
        juce::StringArray{"Sine", "Saw", "Square", "Triangle", "Noise"}, 1));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"osc1Oct", 1}, "OSC1 Octave",
        juce::NormalisableRange<float>(-3.0f, 3.0f, 1.0f), 0.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"osc1Semi", 1}, "OSC1 Semi",
        juce::NormalisableRange<float>(-12.0f, 12.0f, 1.0f), 0.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"osc1Fine", 1}, "OSC1 Fine",
        juce::NormalisableRange<float>(-100.0f, 100.0f, 1.0f), 0.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"osc1Level", 1}, "OSC1 Level", zeroOne, 0.7f));

    // OSC 2
    layout.add(std::make_unique<juce::AudioParameterChoice>(
        juce::ParameterID{"osc2Wave", 1}, "OSC2 Wave",
        juce::StringArray{"Sine", "Saw", "Square", "Triangle", "Noise"}, 2));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"osc2Oct", 1}, "OSC2 Octave",
        juce::NormalisableRange<float>(-3.0f, 3.0f, 1.0f), -1.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"osc2Semi", 1}, "OSC2 Semi",
        juce::NormalisableRange<float>(-12.0f, 12.0f, 1.0f), 0.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"osc2Fine", 1}, "OSC2 Fine",
        juce::NormalisableRange<float>(-100.0f, 100.0f, 1.0f), 7.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"osc2Level", 1}, "OSC2 Level", zeroOne, 0.7f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"phaseOffset", 1}, "Phase Offset", zeroOne, 0.5f));

    // FILTER
    layout.add(std::make_unique<juce::AudioParameterChoice>(
        juce::ParameterID{"filterType", 1}, "Filter Type",
        juce::StringArray{"LP 12dB", "LP 24dB", "Band Pass", "High Pass", "Notch"}, 0));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"filterCutoff", 1}, "Filter Cutoff",
        juce::NormalisableRange<float>(50.0f, 20000.0f, 0.1f, 0.25f), 8000.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"filterRes", 1}, "Filter Resonance", zeroOne, 0.7f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"filterEnvAmt", 1}, "Filter Env Amount", zeroOne, 0.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"keyTrack", 1}, "Key Tracking", zeroOne, 0.5f));

    // FILTER ENVELOPE
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"fEnvAttack", 1}, "Filter Attack",
        juce::NormalisableRange<float>(0.001f, 5.0f, 0.001f, 0.3f), 0.01f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"fEnvDecay", 1}, "Filter Decay",
        juce::NormalisableRange<float>(0.001f, 5.0f, 0.001f, 0.3f), 0.3f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"fEnvSustain", 1}, "Filter Sustain", zeroOne, 0.5f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"fEnvRelease", 1}, "Filter Release",
        juce::NormalisableRange<float>(0.001f, 10.0f, 0.001f, 0.3f), 0.5f));

    // AMP ENVELOPE
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"envAttack", 1}, "Attack",
        juce::NormalisableRange<float>(0.001f, 5.0f, 0.001f, 0.3f), 0.01f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"envDecay", 1}, "Decay",
        juce::NormalisableRange<float>(0.001f, 5.0f, 0.001f, 0.3f), 0.3f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"envSustain", 1}, "Sustain", zeroOne, 0.7f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"envRelease", 1}, "Release",
        juce::NormalisableRange<float>(0.001f, 10.0f, 0.001f, 0.3f), 0.5f));

    // GLOBAL
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"pan", 1}, "Pan",
        juce::NormalisableRange<float>(-1.0f, 1.0f, 0.01f), 0.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"drive", 1}, "Drive",
        juce::NormalisableRange<float>(1.0f, 5.0f, 0.01f, 0.5f), 1.5f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"masterLevel", 1}, "Master Level", zeroOne, 0.8f));

    // PITCH BEND
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"pitchBendRange", 1}, "Pitch Bend Range",
        juce::NormalisableRange<float>(1.0f, 12.0f, 1.0f), 2.0f));

    // DISTORTION
    layout.add(std::make_unique<juce::AudioParameterChoice>(
        juce::ParameterID{"distType", 1}, "Dist Type",
        juce::StringArray{"Soft Clip", "Hard Clip", "Foldback", "Overdrive"}, 0));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"distAmount", 1}, "Dist Amount", zeroOne, 0.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"distMix", 1}, "Dist Mix", zeroOne, 0.0f));

    // COMPRESSOR
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"compThreshold", 1}, "Comp Threshold",
        juce::NormalisableRange<float>(-60.0f, 0.0f, 0.1f), -20.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"compRatio", 1}, "Comp Ratio",
        juce::NormalisableRange<float>(1.0f, 20.0f, 0.1f), 4.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"compAttack", 1}, "Comp Attack",
        juce::NormalisableRange<float>(0.1f, 100.0f, 0.1f), 10.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"compRelease", 1}, "Comp Release",
        juce::NormalisableRange<float>(10.0f, 1000.0f, 1.0f), 100.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"compMakeup", 1}, "Comp Makeup",
        juce::NormalisableRange<float>(0.0f, 24.0f, 0.1f), 0.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"compMix", 1}, "Comp Mix", zeroOne, 1.0f));

    // AUTO-PAN
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"autoPanRate", 1}, "Pan Rate",
        juce::NormalisableRange<float>(0.05f, 20.0f, 0.01f, 0.4f), 2.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"autoPanDepth", 1}, "Pan Depth", zeroOne, 0.0f));

    // LFO 1
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"lfo1Rate", 1}, "LFO1 Rate",
        juce::NormalisableRange<float>(0.05f, 20.0f, 0.01f, 0.4f), 2.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"lfo1Depth", 1}, "LFO1 Depth", zeroOne, 0.0f));
    layout.add(std::make_unique<juce::AudioParameterChoice>(
        juce::ParameterID{"lfo1Shape", 1}, "LFO1 Shape",
        juce::StringArray{"Sine", "Triangle", "Saw", "Square"}, 0));
    layout.add(std::make_unique<juce::AudioParameterChoice>(
        juce::ParameterID{"lfo1Dest", 1}, "LFO1 Dest",
        juce::StringArray{"Filter", "OSC1", "OSC2", "Both OSC"}, 0));

    // LFO 2
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"lfo2Rate", 1}, "LFO2 Rate",
        juce::NormalisableRange<float>(0.05f, 20.0f, 0.01f, 0.4f), 2.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"lfo2Depth", 1}, "LFO2 Depth", zeroOne, 0.0f));
    layout.add(std::make_unique<juce::AudioParameterChoice>(
        juce::ParameterID{"lfo2Shape", 1}, "LFO2 Shape",
        juce::StringArray{"Sine", "Triangle", "Saw", "Square"}, 0));
    layout.add(std::make_unique<juce::AudioParameterChoice>(
        juce::ParameterID{"lfo2Dest", 1}, "LFO2 Dest",
        juce::StringArray{"Filter", "OSC1", "OSC2", "Both OSC"}, 0));

    // DELAY
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"delayTime", 1}, "Delay Time",
        juce::NormalisableRange<float>(10.0f, 1000.0f, 1.0f, 0.3f), 200.0f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"delayFeedback", 1}, "Delay Feedback", zeroOne, 0.3f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"delayMix", 1}, "Delay Mix", zeroOne, 0.25f));
    layout.add(std::make_unique<juce::AudioParameterChoice>(
        juce::ParameterID{"delayPingPong", 1}, "Delay PingPong",
        juce::StringArray{"Off", "On"}, 0));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"delaySpread", 1}, "Delay Spread",
        juce::NormalisableRange<float>(0.25f, 0.75f, 0.01f), 0.5f));

    // REVERB
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"reverbSize", 1}, "Reverb Size", zeroOne, 0.5f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"reverbDamping", 1}, "Reverb Damping", zeroOne, 0.5f));
    layout.add(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID{"reverbMix", 1}, "Reverb Mix", zeroOne, 0.2f));

    return layout;
}

void ChromaFlockProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) {
    currentSampleRate = sampleRate;
    synth.setCurrentPlaybackSampleRate(sampleRate);
    distortion.prepare(sampleRate);
    compressor.prepare(sampleRate);
    delay.prepare(sampleRate);
    reverbFX.prepare(sampleRate);
    autoPanPhase = 0.0f;
    updateVoiceParameters();
}

void ChromaFlockProcessor::releaseResources() {}

void ChromaFlockProcessor::updateVoiceParameters() {
    auto getParam = [&](const juce::String& id) -> float {
        auto* p = apvts.getRawParameterValue(id);
        return p != nullptr ? p->load() : 0.0f;
    };

    for (int i = 0; i < synth.getNumVoices(); ++i) {
        if (auto* voice = dynamic_cast<SubtractiveVoice*>(synth.getVoice(i))) {
            float pbRange = getParam("pitchBendRange");
            voice->setPitchBend(getPitchBend() * pbRange);
            voice->setParameters(
                static_cast<Waveform>(static_cast<int>(getParam("osc1Wave"))),
                static_cast<Waveform>(static_cast<int>(getParam("osc2Wave"))),
                getParam("osc1Oct"),
                getParam("osc2Oct"),
                getParam("osc1Semi"),
                getParam("osc2Semi"),
                getParam("osc1Fine"),
                getParam("osc2Fine"),
                getParam("osc1Level"),
                getParam("osc2Level"),
                getParam("phaseOffset"),
                getParam("filterCutoff"),
                getParam("filterRes"),
                static_cast<FilterType>(static_cast<int>(getParam("filterType"))),
                getParam("filterEnvAmt"),
                getParam("keyTrack"),
                getParam("envAttack"),
                getParam("envDecay"),
                getParam("envSustain"),
                getParam("envRelease"),
                getParam("fEnvAttack"),
                getParam("fEnvDecay"),
                getParam("fEnvSustain"),
                getParam("fEnvRelease"),
                getParam("pan"),
                getParam("drive"),
                getParam("masterLevel"),
                getParam("lfo1Rate"),
                getParam("lfo1Depth"),
                static_cast<int>(getParam("lfo1Shape")),
                static_cast<int>(getParam("lfo1Dest")),
                getParam("lfo2Rate"),
                getParam("lfo2Depth"),
                static_cast<int>(getParam("lfo2Shape")),
                static_cast<int>(getParam("lfo2Dest"))
            );
        }
    }
}

void ChromaFlockProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) {
    juce::ScopedNoDenormals noDenormals;

    buffer.clear();
    updateVoiceParameters();

    for (const auto metadata : midiMessages)
        if (metadata.getMessage().isNoteOn())
            activeNotes[metadata.getMessage().getNoteNumber()].store(true, std::memory_order_relaxed);
        else if (metadata.getMessage().isNoteOff())
            activeNotes[metadata.getMessage().getNoteNumber()].store(false, std::memory_order_relaxed);

    synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());

    // Read FX params
    auto getParam = [&](const juce::String& id) -> float {
        auto* p = apvts.getRawParameterValue(id);
        return p != nullptr ? p->load() : 0.0f;
    };

    distortion.setParameters(
        static_cast<DistortionType>(static_cast<int>(getParam("distType"))),
        getParam("distAmount"),
        getParam("distMix"));

    compressor.setParameters(
        getParam("compThreshold"),
        getParam("compRatio"),
        getParam("compAttack"),
        getParam("compRelease"),
        getParam("compMakeup"),
        getParam("compMix"));

    float panRate = getParam("autoPanRate");
    float panDepth = getParam("autoPanDepth");

    delay.setParameters(getParam("delayTime"), getParam("delayFeedback"), getParam("delayMix"),
                         static_cast<int>(getParam("delayPingPong")) == 1, getParam("delaySpread"));
    reverbFX.setParameters(getParam("reverbSize"), getParam("reverbDamping"), 0.8f, getParam("reverbMix"));

    // FX processing: distortion → compression → auto-pan
    int numSamples = buffer.getNumSamples();
    float* leftData = buffer.getWritePointer(0);
    float* rightData = buffer.getNumChannels() > 1 ? buffer.getWritePointer(1) : leftData;

    float twoPi = 6.2831853f;
    for (int i = 0; i < numSamples; ++i) {
        float left = leftData[i];
        float right = rightData[i];

        // Distortion (stereo)
        left = distortion.process(left);
        right = distortion.process(right);

        // Compression (stereo)
        left = compressor.process(left);
        right = compressor.process(right);

        // Auto-pan
        if (panDepth > 0.001f) {
            float panLfo = std::sin(autoPanPhase * twoPi);
            float leftGain = 1.0f - panDepth * 0.5f * (1.0f + panLfo);
            float rightGain = 1.0f - panDepth * 0.5f * (1.0f - panLfo);
            left *= leftGain;
            right *= rightGain;
            autoPanPhase += panRate / static_cast<float>(currentSampleRate);
            if (autoPanPhase >= 1.0f) autoPanPhase -= 1.0f;
        }

        // Delay
        delay.process(left, right);

        leftData[i] = left;
        rightData[i] = right;
    }

    // Reverb (block-based)
    reverbFX.process(leftData, rightData, numSamples);

    // Metering
    float sumSquares = 0.0f;
    float peak = 0.0f;
    for (int i = 0; i < numSamples; ++i) {
        float mix = (leftData[i] + rightData[i]) * 0.5f;

        int writePos = scopeWritePos.load(std::memory_order_relaxed);
        scopeBuffer[writePos] = mix;
        scopeWritePos.store((writePos + 1) % scopeBufferSize, std::memory_order_release);

        int fftPos = fftWritePos.load(std::memory_order_relaxed);
        fftInput[fftPos] = mix;
        fftWritePos.store((fftPos + 1) % fftSize, std::memory_order_release);

        float absMix = std::abs(mix);
        if (absMix > peak) peak = absMix;
        sumSquares += mix * mix;
    }

    float rms = std::sqrt(sumSquares / static_cast<float>(numSamples));
    float level = juce::jmax(rms, peak);
    level = std::min(level * 5.0f, 1.0f);

    float prev = rmsLevel.load(std::memory_order_relaxed);
    if (level > prev)
        rmsLevel.store(level, std::memory_order_relaxed);
    else
        rmsLevel.store(prev * 0.95f, std::memory_order_relaxed);
}

juce::AudioProcessorEditor* ChromaFlockProcessor::createEditor() {
    return new ChromaFlockEditor(*this);
}

void ChromaFlockProcessor::getStateInformation(juce::MemoryBlock& destData) {
    auto state = apvts.copyState();
    std::unique_ptr<juce::XmlElement> xml(state.createXml());
    copyXmlToBinary(*xml, destData);
}

void ChromaFlockProcessor::setStateInformation(const void* data, int sizeInBytes) {
    std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
    if (xml && xml->hasTagName(apvts.state.getType()))
        apvts.replaceState(juce::ValueTree::fromXml(*xml));
}

juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() {
    return new ChromaFlockProcessor();
}
