#include "DeepPluckParameterState.h"

#include <cmath>

namespace deeppluck
{
namespace
{
float readRawValue (const juce::AudioProcessorValueTreeState& state,
                    int index,
                    bool useDefault) noexcept
{
    const auto* parameter = state.getParameter (allParameterIds[static_cast<std::size_t> (index)]);
    if (parameter == nullptr)
        return 0.0f;

    if (useDefault)
        return parameter->convertFrom0to1 (parameter->getDefaultValue());

    if (const auto* raw = state.getRawParameterValue (
            allParameterIds[static_cast<std::size_t> (index)]))
        return raw->load (std::memory_order_relaxed);

    return parameter->convertFrom0to1 (parameter->getDefaultValue());
}

float canonicalize (const juce::RangedAudioParameter& parameter, float value) noexcept
{
    if (! std::isfinite (value))
        return parameter.convertFrom0to1 (parameter.getDefaultValue());

    const auto normalized = parameter.convertTo0to1 (value);
    if (! std::isfinite (normalized))
        return parameter.convertFrom0to1 (parameter.getDefaultValue());

    return parameter.convertFrom0to1 (juce::jlimit (0.0f, 1.0f, normalized));
}
}

DeepPluckParameterValues captureParameterValues (
    const juce::AudioProcessorValueTreeState& state) noexcept
{
    DeepPluckParameterValues values {};
    for (auto index = 0; index < static_cast<int> (values.size()); ++index)
        values[static_cast<std::size_t> (index)] = readRawValue (state, index, false);
    return values;
}

DeepPluckParameterValues captureParameterDefaults (
    const juce::AudioProcessorValueTreeState& state) noexcept
{
    DeepPluckParameterValues values {};
    for (auto index = 0; index < static_cast<int> (values.size()); ++index)
        values[static_cast<std::size_t> (index)] = readRawValue (state, index, true);
    return values;
}

bool canonicalizeParameterValues (const juce::AudioProcessorValueTreeState& state,
                                  DeepPluckParameterValues& values) noexcept
{
    for (auto index = 0; index < static_cast<int> (values.size()); ++index)
    {
        const auto* parameter = state.getParameter (
            allParameterIds[static_cast<std::size_t> (index)]);
        if (parameter == nullptr || ! std::isfinite (values[static_cast<std::size_t> (index)]))
            return false;
        values[static_cast<std::size_t> (index)] = canonicalize (
            *parameter, values[static_cast<std::size_t> (index)]);
    }
    return true;
}

bool setParameterValueNotifyingHost (juce::AudioProcessorValueTreeState& state,
                                     int parameterIndex,
                                     float rawValue)
{
    if (parameterIndex < 0 || parameterIndex >= static_cast<int> (allParameterIds.size()))
        return false;

    auto* parameter = state.getParameter (
        allParameterIds[static_cast<std::size_t> (parameterIndex)]);
    if (parameter == nullptr || ! std::isfinite (rawValue))
        return false;

    const auto normalized = juce::jlimit (0.0f, 1.0f, parameter->convertTo0to1 (rawValue));
    if (! std::isfinite (normalized))
        return false;

    parameter->beginChangeGesture();
    parameter->setValueNotifyingHost (normalized);
    parameter->endChangeGesture();
    return true;
}

bool applyParameterValues (juce::AudioProcessorValueTreeState& state,
                           const DeepPluckParameterValues& suppliedValues)
{
#if JUCE_DEBUG
    const auto* messageManager = juce::MessageManager::getInstanceWithoutCreating();
    jassert (messageManager == nullptr || messageManager->isThisTheMessageThread());
#endif

    auto values = suppliedValues;
    if (! canonicalizeParameterValues (state, values))
        return false;

    for (auto index = 0; index < static_cast<int> (values.size()); ++index)
        if (! setParameterValueNotifyingHost (
                state, index, values[static_cast<std::size_t> (index)]))
            return false;
    return true;
}

bool parameterValuesMatch (const juce::AudioProcessorValueTreeState& state,
                           const DeepPluckParameterValues& expected,
                           float normalizedTolerance) noexcept
{
    const auto tolerance = juce::jmax (0.0f, normalizedTolerance);
    for (auto index = 0; index < static_cast<int> (expected.size()); ++index)
    {
        const auto* parameter = state.getParameter (
            allParameterIds[static_cast<std::size_t> (index)]);
        if (parameter == nullptr || ! std::isfinite (expected[static_cast<std::size_t> (index)]))
            return false;

        const auto actual = readRawValue (state, index, false);
        if (! std::isfinite (actual))
            return false;

        const auto actualNormalized = parameter->convertTo0to1 (actual);
        const auto expectedNormalized = parameter->convertTo0to1 (
            expected[static_cast<std::size_t> (index)]);
        if (! std::isfinite (actualNormalized) || ! std::isfinite (expectedNormalized)
            || std::abs (actualNormalized - expectedNormalized) > tolerance)
            return false;
    }
    return true;
}

int findParameterIndex (juce::StringRef parameterId) noexcept
{
    for (auto index = 0; index < static_cast<int> (allParameterIds.size()); ++index)
        if (parameterId == juce::StringRef (
                allParameterIds[static_cast<std::size_t> (index)]))
            return index;
    return -1;
}
}
