#include "DeepPluckPresets.h"

#include <cmath>

namespace deeppluck
{
namespace
{
void setParameter (juce::AudioProcessorValueTreeState& state, const char* id, float value)
{
    if (auto* parameter = state.getParameter (id))
    {
        parameter->beginChangeGesture();
        parameter->setValueNotifyingHost (parameter->convertTo0to1 (value));
        parameter->endChangeGesture();
    }
}

bool approximatelyMatches (float actual, float expected, float tolerance) noexcept
{
    return std::isfinite (actual) && std::abs (actual - expected) <= tolerance;
}
}

void applyFactoryPreset (juce::AudioProcessorValueTreeState& state, int presetIndex)
{
#if JUCE_DEBUG
    const auto* messageManager = juce::MessageManager::getInstanceWithoutCreating();
    jassert (messageManager == nullptr || messageManager->isThisTheMessageThread());
#endif

    if (presetIndex < 0 || presetIndex >= static_cast<int> (factoryPresets.size()))
        return;

    const auto& values = factoryPresets[static_cast<std::size_t> (presetIndex)].values;
    setParameter (state, ParameterIDs::outputGain, values.outputGainDb);
    setParameter (state, ParameterIDs::masterTune, values.masterTuneHz);
    setParameter (state, ParameterIDs::ampAttack, values.ampAttackSeconds);
    setParameter (state, ParameterIDs::ampDecay, values.ampDecaySeconds);
    setParameter (state, ParameterIDs::ampSustain, values.ampSustainLevel);
    setParameter (state, ParameterIDs::ampRelease, values.ampReleaseSeconds);
    setParameter (state, ParameterIDs::filterEnabled, values.filterEnabled);
    setParameter (state, ParameterIDs::filterCutoff, values.filterCutoffHz);
    setParameter (state, ParameterIDs::filterResonance, values.filterResonance);
    setParameter (state, ParameterIDs::filterDrive, values.filterDriveDb);
    setParameter (state, ParameterIDs::filterKeyTracking, values.filterKeyTracking);
    setParameter (state, ParameterIDs::filterEnvAmount, values.filterEnvAmountOctaves);
    setParameter (state, ParameterIDs::filterAttack, values.filterAttackSeconds);
    setParameter (state, ParameterIDs::filterDecay, values.filterDecaySeconds);
    setParameter (state, ParameterIDs::filterSustain, values.filterSustainLevel);
    setParameter (state, ParameterIDs::filterRelease, values.filterReleaseSeconds);
    setParameter (state, ParameterIDs::transientEnabled, values.transientEnabled);
    setParameter (state, ParameterIDs::transientLevel, values.transientLevelDb);
    setParameter (state, ParameterIDs::transientDecay, values.transientDecaySeconds);
    setParameter (state, ParameterIDs::transientTone, values.transientTone);
    setParameter (state, ParameterIDs::noiseEnabled, values.noiseEnabled);
    setParameter (state, ParameterIDs::noiseLevel, values.noiseLevelDb);
    setParameter (state, ParameterIDs::noiseDecay, values.noiseDecaySeconds);
    setParameter (state, ParameterIDs::noiseTone, values.noiseTone);
    setParameter (state, ParameterIDs::osc2Enabled, values.osc2Enabled);
    setParameter (state, ParameterIDs::osc2Level, values.osc2LevelDb);
    setParameter (state, ParameterIDs::osc2Octave, values.osc2Octave);
    setParameter (state, ParameterIDs::osc2Semitone, values.osc2Semitone);
    setParameter (state, ParameterIDs::osc2FineTune, values.osc2FineTuneCents);
    setParameter (state, ParameterIDs::unisonVoices, values.unisonVoices);
    setParameter (state, ParameterIDs::unisonDetune, values.unisonDetuneCents);
    setParameter (state, ParameterIDs::stereoSpread, values.stereoSpread);
    setParameter (state, ParameterIDs::lfoRate, values.lfoRateHz);
    setParameter (state, ParameterIDs::lfoSync, values.lfoSync);
    setParameter (state, ParameterIDs::lfoSyncDivision, values.lfoSyncDivision);
    setParameter (state, ParameterIDs::lfoWaveform, values.lfoWaveform);
    setParameter (state, ParameterIDs::lfoRestartMode, values.lfoRestartMode);
    setParameter (state, ParameterIDs::lfoFilterAmount, values.lfoFilterAmountOctaves);
    setParameter (state, ParameterIDs::lfoPitchAmount, values.lfoPitchAmountCents);
    setParameter (state, ParameterIDs::lfoSpreadAmount, values.lfoSpreadAmount);
    setParameter (state, ParameterIDs::osc1Waveform, values.osc1Waveform);
    setParameter (state, ParameterIDs::osc1PulseWidth, values.osc1PulseWidth);
    setParameter (state, ParameterIDs::osc2Waveform, values.osc2Waveform);
    setParameter (state, ParameterIDs::osc2PulseWidth, values.osc2PulseWidth);
    setParameter (state, ParameterIDs::chorusBypass, values.chorusBypass);
    setParameter (state, ParameterIDs::chorusRate, values.chorusRateHz);
    setParameter (state, ParameterIDs::chorusDepth, values.chorusDepth);
    setParameter (state, ParameterIDs::chorusWidth, values.chorusWidth);
    setParameter (state, ParameterIDs::chorusMix, values.chorusMix);
    setParameter (state, ParameterIDs::delayBypass, values.delayBypass);
    setParameter (state, ParameterIDs::delaySync, values.delaySync);
    setParameter (state, ParameterIDs::delayTime, values.delayTimeSeconds);
    setParameter (state, ParameterIDs::delaySyncDivision, values.delaySyncDivision);
    setParameter (state, ParameterIDs::delayFeedback, values.delayFeedback);
    setParameter (state, ParameterIDs::delayTone, values.delayToneHz);
    setParameter (state, ParameterIDs::delayMix, values.delayMix);
    setParameter (state, ParameterIDs::reverbBypass, values.reverbBypass);
    setParameter (state, ParameterIDs::reverbSize, values.reverbSize);
    setParameter (state, ParameterIDs::reverbPreDelay, values.reverbPreDelaySeconds);
    setParameter (state, ParameterIDs::reverbDamping, values.reverbDamping);
    setParameter (state, ParameterIDs::reverbMix, values.reverbMix);
    setParameter (state, ParameterIDs::fxOrder, values.fxOrder);
}

int findMatchingFactoryPreset (const juce::AudioProcessorValueTreeState& state) noexcept
{
    const auto read = [&state] (const char* id, float fallback) noexcept
    {
        if (const auto* value = state.getRawParameterValue (id))
            return value->load (std::memory_order_relaxed);
        return fallback;
    };

    const DeepPluckParameterSnapshot current {
        read (ParameterIDs::outputGain, 0.0f),
        read (ParameterIDs::masterTune, 0.0f),
        read (ParameterIDs::ampAttack, 0.0f),
        read (ParameterIDs::ampDecay, 0.0f),
        read (ParameterIDs::ampSustain, 0.0f),
        read (ParameterIDs::ampRelease, 0.0f),
        read (ParameterIDs::filterEnabled, 0.0f),
        read (ParameterIDs::filterCutoff, 0.0f),
        read (ParameterIDs::filterResonance, 0.0f),
        read (ParameterIDs::filterDrive, 0.0f),
        read (ParameterIDs::filterKeyTracking, 0.0f),
        read (ParameterIDs::filterEnvAmount, 0.0f),
        read (ParameterIDs::filterAttack, 0.0f),
        read (ParameterIDs::filterDecay, 0.0f),
        read (ParameterIDs::filterSustain, 0.0f),
        read (ParameterIDs::filterRelease, 0.0f),
        read (ParameterIDs::transientEnabled, 0.0f),
        read (ParameterIDs::transientLevel, 0.0f),
        read (ParameterIDs::transientDecay, 0.0f),
        read (ParameterIDs::transientTone, 0.0f),
        read (ParameterIDs::noiseEnabled, 0.0f),
        read (ParameterIDs::noiseLevel, 0.0f),
        read (ParameterIDs::noiseDecay, 0.0f),
        read (ParameterIDs::noiseTone, 0.0f),
        read (ParameterIDs::osc2Enabled, 0.0f),
        read (ParameterIDs::osc2Level, 0.0f),
        read (ParameterIDs::osc2Octave, 0.0f),
        read (ParameterIDs::osc2Semitone, 0.0f),
        read (ParameterIDs::osc2FineTune, 0.0f),
        read (ParameterIDs::unisonVoices, 0.0f),
        read (ParameterIDs::unisonDetune, 0.0f),
        read (ParameterIDs::stereoSpread, 0.0f),
        read (ParameterIDs::lfoRate, 0.0f),
        read (ParameterIDs::lfoSync, 0.0f),
        read (ParameterIDs::lfoSyncDivision, 0.0f),
        read (ParameterIDs::lfoWaveform, 0.0f),
        read (ParameterIDs::lfoRestartMode, 0.0f),
        read (ParameterIDs::lfoFilterAmount, 0.0f),
        read (ParameterIDs::lfoPitchAmount, 0.0f),
        read (ParameterIDs::lfoSpreadAmount, 0.0f),
        read (ParameterIDs::osc1Waveform, 0.0f),
        read (ParameterIDs::osc1PulseWidth, 0.0f),
        read (ParameterIDs::osc2Waveform, 0.0f),
        read (ParameterIDs::osc2PulseWidth, 0.0f),
        read (ParameterIDs::chorusBypass, 0.0f),
        read (ParameterIDs::chorusRate, 0.0f),
        read (ParameterIDs::chorusDepth, 0.0f),
        read (ParameterIDs::chorusWidth, 0.0f),
        read (ParameterIDs::chorusMix, 0.0f),
        read (ParameterIDs::delayBypass, 0.0f),
        read (ParameterIDs::delaySync, 0.0f),
        read (ParameterIDs::delayTime, 0.0f),
        read (ParameterIDs::delaySyncDivision, 0.0f),
        read (ParameterIDs::delayFeedback, 0.0f),
        read (ParameterIDs::delayTone, 0.0f),
        read (ParameterIDs::delayMix, 0.0f),
        read (ParameterIDs::reverbBypass, 0.0f),
        read (ParameterIDs::reverbSize, 0.0f),
        read (ParameterIDs::reverbPreDelay, 0.0f),
        read (ParameterIDs::reverbDamping, 0.0f),
        read (ParameterIDs::reverbMix, 0.0f),
        read (ParameterIDs::fxOrder, 0.0f)
    };

    for (auto index = 0; index < static_cast<int> (factoryPresets.size()); ++index)
    {
        const auto& expected = factoryPresets[static_cast<std::size_t> (index)].values;
        if (approximatelyMatches (current.outputGainDb, expected.outputGainDb, 0.051f)
            && approximatelyMatches (current.masterTuneHz, expected.masterTuneHz, 0.051f)
            && approximatelyMatches (current.ampAttackSeconds, expected.ampAttackSeconds, 1.0e-5f)
            && approximatelyMatches (current.ampDecaySeconds, expected.ampDecaySeconds, 1.0e-5f)
            && approximatelyMatches (current.ampSustainLevel, expected.ampSustainLevel, 5.1e-4f)
            && approximatelyMatches (current.ampReleaseSeconds, expected.ampReleaseSeconds, 1.0e-5f)
            && approximatelyMatches (current.filterEnabled, expected.filterEnabled, 0.01f)
            && approximatelyMatches (current.filterCutoffHz, expected.filterCutoffHz, 0.11f)
            && approximatelyMatches (current.filterResonance, expected.filterResonance, 5.1e-4f)
            && approximatelyMatches (current.filterDriveDb, expected.filterDriveDb, 0.051f)
            && approximatelyMatches (current.filterKeyTracking, expected.filterKeyTracking, 5.1e-4f)
            && approximatelyMatches (current.filterEnvAmountOctaves,
                                     expected.filterEnvAmountOctaves, 0.011f)
            && approximatelyMatches (current.filterAttackSeconds,
                                     expected.filterAttackSeconds, 1.0e-5f)
            && approximatelyMatches (current.filterDecaySeconds,
                                     expected.filterDecaySeconds, 1.0e-5f)
            && approximatelyMatches (current.filterSustainLevel,
                                     expected.filterSustainLevel, 5.1e-4f)
            && approximatelyMatches (current.filterReleaseSeconds,
                                     expected.filterReleaseSeconds, 1.0e-5f)
            && approximatelyMatches (current.transientEnabled,
                                     expected.transientEnabled, 0.01f)
            && approximatelyMatches (current.transientLevelDb,
                                     expected.transientLevelDb, 0.051f)
            && approximatelyMatches (current.transientDecaySeconds,
                                     expected.transientDecaySeconds, 1.0e-5f)
            && approximatelyMatches (current.transientTone,
                                     expected.transientTone, 5.1e-4f)
            && approximatelyMatches (current.noiseEnabled,
                                     expected.noiseEnabled, 0.01f)
            && approximatelyMatches (current.noiseLevelDb,
                                     expected.noiseLevelDb, 0.051f)
            && approximatelyMatches (current.noiseDecaySeconds,
                                     expected.noiseDecaySeconds, 1.0e-5f)
            && approximatelyMatches (current.noiseTone,
                                     expected.noiseTone, 5.1e-4f)
            && approximatelyMatches (current.osc2Enabled,
                                     expected.osc2Enabled, 0.01f)
            && approximatelyMatches (current.osc2LevelDb,
                                     expected.osc2LevelDb, 0.051f)
            && approximatelyMatches (current.osc2Octave,
                                     expected.osc2Octave, 0.01f)
            && approximatelyMatches (current.osc2Semitone,
                                     expected.osc2Semitone, 0.01f)
            && approximatelyMatches (current.osc2FineTuneCents,
                                     expected.osc2FineTuneCents, 0.051f)
            && approximatelyMatches (current.unisonVoices,
                                     expected.unisonVoices, 0.01f)
            && approximatelyMatches (current.unisonDetuneCents,
                                     expected.unisonDetuneCents, 0.051f)
            && approximatelyMatches (current.stereoSpread,
                                     expected.stereoSpread, 5.1e-4f)
            && approximatelyMatches (current.lfoRateHz,
                                     expected.lfoRateHz, 5.1e-4f)
            && approximatelyMatches (current.lfoSync,
                                     expected.lfoSync, 0.01f)
            && approximatelyMatches (current.lfoSyncDivision,
                                     expected.lfoSyncDivision, 0.01f)
            && approximatelyMatches (current.lfoWaveform,
                                     expected.lfoWaveform, 0.01f)
            && approximatelyMatches (current.lfoRestartMode,
                                     expected.lfoRestartMode, 0.01f)
            && approximatelyMatches (current.lfoFilterAmountOctaves,
                                     expected.lfoFilterAmountOctaves, 0.011f)
            && approximatelyMatches (current.lfoPitchAmountCents,
                                     expected.lfoPitchAmountCents, 0.051f)
            && approximatelyMatches (current.lfoSpreadAmount,
                                     expected.lfoSpreadAmount, 5.1e-4f)
            && approximatelyMatches (current.osc1Waveform,
                                     expected.osc1Waveform, 0.01f)
            && approximatelyMatches (current.osc1PulseWidth,
                                     expected.osc1PulseWidth, 5.1e-4f)
            && approximatelyMatches (current.osc2Waveform,
                                     expected.osc2Waveform, 0.01f)
            && approximatelyMatches (current.osc2PulseWidth,
                                     expected.osc2PulseWidth, 5.1e-4f)
            && approximatelyMatches (current.chorusBypass, expected.chorusBypass, 0.01f)
            && approximatelyMatches (current.chorusRateHz, expected.chorusRateHz, 1.0e-5f)
            && approximatelyMatches (current.chorusDepth, expected.chorusDepth, 5.1e-4f)
            && approximatelyMatches (current.chorusWidth, expected.chorusWidth, 5.1e-4f)
            && approximatelyMatches (current.chorusMix, expected.chorusMix, 5.1e-4f)
            && approximatelyMatches (current.delayBypass, expected.delayBypass, 0.01f)
            && approximatelyMatches (current.delaySync, expected.delaySync, 0.01f)
            && approximatelyMatches (current.delayTimeSeconds,
                                     expected.delayTimeSeconds, 1.0e-5f)
            && approximatelyMatches (current.delaySyncDivision,
                                     expected.delaySyncDivision, 0.01f)
            && approximatelyMatches (current.delayFeedback,
                                     expected.delayFeedback, 5.1e-4f)
            && approximatelyMatches (current.delayToneHz, expected.delayToneHz, 0.11f)
            && approximatelyMatches (current.delayMix, expected.delayMix, 5.1e-4f)
            && approximatelyMatches (current.reverbBypass, expected.reverbBypass, 0.01f)
            && approximatelyMatches (current.reverbSize, expected.reverbSize, 5.1e-4f)
            && approximatelyMatches (current.reverbPreDelaySeconds,
                                     expected.reverbPreDelaySeconds, 1.0e-5f)
            && approximatelyMatches (current.reverbDamping,
                                     expected.reverbDamping, 5.1e-4f)
            && approximatelyMatches (current.reverbMix, expected.reverbMix, 5.1e-4f)
            && approximatelyMatches (current.fxOrder, expected.fxOrder, 0.01f))
            return index;
    }

    return -1;
}
}
