/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

struct AudioProcessorValueTreeState::Parameter   : public AudioProcessorParameterWithID,
                                                   private ValueTree::Listener
{
    Parameter (AudioProcessorValueTreeState& s,
               const String& parameterID, const String& paramName, const String& labelText,
               NormalisableRange<float> r, float defaultVal,
               std::function<String (float)> valueToText,
               std::function<float (const String&)> textToValue,
               bool meta,
               bool automatable,
               bool discrete)
        : AudioProcessorParameterWithID (parameterID, paramName, labelText),
          owner (s), valueToTextFunction (valueToText), textToValueFunction (textToValue),
          range (r), value (defaultVal), defaultValue (defaultVal),
          listenersNeedCalling (true),
          isMetaParam (meta),
          isAutomatableParam (automatable),
          isDiscreteParam (discrete)
    {
        state.addListener (this);
        needsUpdate.set (1);
    }

    ~Parameter()
    {
        // should have detached all callbacks before destroying the parameters!
        jassert (listeners.size() <= 1);
    }

    float getValue() const override                             { return range.convertTo0to1 (value); }
    float getDefaultValue() const override                      { return range.convertTo0to1 (defaultValue); }

    float getValueForText (const String& text) const override
    {
        return range.convertTo0to1 (textToValueFunction != nullptr ? textToValueFunction (text)
                                                                   : text.getFloatValue());
    }

    String getText (float v, int length) const override
    {
        return valueToTextFunction != nullptr ? valueToTextFunction (range.convertFrom0to1 (v))
                                              : AudioProcessorParameter::getText (v, length);
    }

    int getNumSteps() const override
    {
        if (range.interval > 0)
            return (static_cast<int> ((range.end - range.start) / range.interval) + 1);

        return AudioProcessor::getDefaultNumParameterSteps();
    }

    void setValue (float newValue) override
    {
        newValue = range.snapToLegalValue (range.convertFrom0to1 (newValue));

        if (value != newValue || listenersNeedCalling)
        {
            value = newValue;

            listeners.call (&AudioProcessorValueTreeState::Listener::parameterChanged, paramID, value);
            listenersNeedCalling = false;

            needsUpdate.set (1);
        }
    }

    void setNewState (const ValueTree& v)
    {
        state = v;
        updateFromValueTree();
    }

    void setUnnormalisedValue (float newUnnormalisedValue)
    {
        if (value != newUnnormalisedValue)
        {
            const float newValue = range.convertTo0to1 (newUnnormalisedValue);
            setValueNotifyingHost (newValue);
        }
    }

    void updateFromValueTree()
    {
        setUnnormalisedValue (state.getProperty (owner.valuePropertyID, defaultValue));
    }

    void copyValueToValueTree()
    {
        if (state.isValid())
            state.setPropertyExcludingListener (this, owner.valuePropertyID, value, owner.undoManager);
    }

    void valueTreePropertyChanged (ValueTree&, const Identifier& property) override
    {
        if (property == owner.valuePropertyID)
            updateFromValueTree();
    }

    void valueTreeChildAdded (ValueTree&, ValueTree&) override {}
    void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {}
    void valueTreeChildOrderChanged (ValueTree&, int, int) override {}
    void valueTreeParentChanged (ValueTree&) override {}

    static Parameter* getParameterForID (AudioProcessor& processor, StringRef paramID) noexcept
    {
        const int numParams = processor.getParameters().size();

        for (int i = 0; i < numParams; ++i)
        {
            AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i);

            // When using this class, you must allow it to manage all the parameters in your AudioProcessor, and
            // not add any parameter objects of other types!
            jassert (dynamic_cast<Parameter*> (ap) != nullptr);

            Parameter* const p = static_cast<Parameter*> (ap);

            if (paramID == p->paramID)
                return p;
        }

        return nullptr;
    }

    bool isMetaParameter() const override      { return isMetaParam; }
    bool isAutomatable() const override        { return isAutomatableParam; }
    bool isDiscrete() const override           { return isDiscreteParam; }

    AudioProcessorValueTreeState& owner;
    ValueTree state;
    ListenerList<AudioProcessorValueTreeState::Listener> listeners;
    std::function<String (float)> valueToTextFunction;
    std::function<float (const String&)> textToValueFunction;
    NormalisableRange<float> range;
    float value, defaultValue;
    Atomic<int> needsUpdate;
    bool listenersNeedCalling;
    const bool isMetaParam, isAutomatableParam, isDiscreteParam;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Parameter)
};

//==============================================================================
AudioProcessorValueTreeState::AudioProcessorValueTreeState (AudioProcessor& p, UndoManager* um)
    : processor (p),
      undoManager (um),
      valueType ("PARAM"),
      valuePropertyID ("value"),
      idPropertyID ("id"),
      updatingConnections (false)
{
    startTimerHz (10);
    state.addListener (this);
}

AudioProcessorValueTreeState::~AudioProcessorValueTreeState() {}

AudioProcessorParameterWithID* AudioProcessorValueTreeState::createAndAddParameter (const String& paramID, const String& paramName,
                                                                                    const String& labelText, NormalisableRange<float> r,
                                                                                    float defaultVal, std::function<String (float)> valueToTextFunction,
                                                                                    std::function<float (const String&)> textToValueFunction,
                                                                                    bool isMetaParameter,
                                                                                    bool isAutomatableParameter,
                                                                                    bool isDiscreteParameter)
{
    // All parameters must be created before giving this manager a ValueTree state!
    jassert (! state.isValid());
   #if ! JUCE_LINUX
    jassert (MessageManager::getInstance()->isThisTheMessageThread());
   #endif

    Parameter* p = new Parameter (*this, paramID, paramName, labelText, r,
                                  defaultVal, valueToTextFunction, textToValueFunction,
                                  isMetaParameter, isAutomatableParameter,
                                  isDiscreteParameter);
    processor.addParameter (p);
    return p;
}

void AudioProcessorValueTreeState::addParameterListener (StringRef paramID, Listener* listener)
{
    if (Parameter* p = Parameter::getParameterForID (processor, paramID))
        p->listeners.add (listener);
}

void AudioProcessorValueTreeState::removeParameterListener (StringRef paramID, Listener* listener)
{
    if (Parameter* p = Parameter::getParameterForID (processor, paramID))
        p->listeners.remove (listener);
}

Value AudioProcessorValueTreeState::getParameterAsValue (StringRef paramID) const
{
    if (Parameter* p = Parameter::getParameterForID (processor, paramID))
        return p->state.getPropertyAsValue (valuePropertyID, undoManager);

    return Value();
}

NormalisableRange<float> AudioProcessorValueTreeState::getParameterRange (StringRef paramID) const noexcept
{
    if (Parameter* p = Parameter::getParameterForID (processor, paramID))
        return p->range;

    return NormalisableRange<float>();
}

AudioProcessorParameterWithID* AudioProcessorValueTreeState::getParameter (StringRef paramID) const noexcept
{
    return Parameter::getParameterForID (processor, paramID);
}

float* AudioProcessorValueTreeState::getRawParameterValue (StringRef paramID) const noexcept
{
    if (Parameter* p = Parameter::getParameterForID (processor, paramID))
        return &(p->value);

    return nullptr;
}

ValueTree AudioProcessorValueTreeState::getOrCreateChildValueTree (const String& paramID)
{
    ValueTree v (state.getChildWithProperty (idPropertyID, paramID));

    if (! v.isValid())
    {
        v = ValueTree (valueType);
        v.setProperty (idPropertyID, paramID, undoManager);
        state.addChild (v, -1, undoManager);
    }

    return v;
}

void AudioProcessorValueTreeState::updateParameterConnectionsToChildTrees()
{
    if (! updatingConnections)
    {
        ScopedValueSetter<bool> svs (updatingConnections, true, false);

        const int numParams = processor.getParameters().size();

        for (int i = 0; i < numParams; ++i)
        {
            AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i);
            jassert (dynamic_cast<Parameter*> (ap) != nullptr);

            Parameter* p = static_cast<Parameter*> (ap);
            p->setNewState (getOrCreateChildValueTree (p->paramID));
        }
    }
}

void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree& tree, const Identifier& property)
{
    if (property == idPropertyID && tree.hasType (valueType) && tree.getParent() == state)
        updateParameterConnectionsToChildTrees();
}

void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree& tree)
{
    if (parent == state && tree.hasType (valueType))
        updateParameterConnectionsToChildTrees();
}

void AudioProcessorValueTreeState::valueTreeChildRemoved (ValueTree& parent, ValueTree& tree, int)
{
    if (parent == state && tree.hasType (valueType))
        updateParameterConnectionsToChildTrees();
}

void AudioProcessorValueTreeState::valueTreeRedirected (ValueTree& v)
{
    if (v == state)
        updateParameterConnectionsToChildTrees();
}

void AudioProcessorValueTreeState::valueTreeChildOrderChanged (ValueTree&, int, int) {}
void AudioProcessorValueTreeState::valueTreeParentChanged (ValueTree&) {}

void AudioProcessorValueTreeState::timerCallback()
{
    const int numParams = processor.getParameters().size();
    bool anythingUpdated = false;

    for (int i = 0; i < numParams; ++i)
    {
        AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i);
        jassert (dynamic_cast<Parameter*> (ap) != nullptr);

        Parameter* p = static_cast<Parameter*> (ap);

        if (p->needsUpdate.compareAndSetBool (0, 1))
        {
            p->copyValueToValueTree();
            anythingUpdated = true;
        }
    }

    startTimer (anythingUpdated ? 1000 / 50
                                : jlimit (50, 500, getTimerInterval() + 20));
}

AudioProcessorValueTreeState::Listener::Listener() {}
AudioProcessorValueTreeState::Listener::~Listener() {}

//==============================================================================
struct AttachedControlBase  : public AudioProcessorValueTreeState::Listener,
                              public AsyncUpdater
{
    AttachedControlBase (AudioProcessorValueTreeState& s, const String& p)
        : state (s), paramID (p), lastValue (0)
    {
        state.addParameterListener (paramID, this);
    }

    void removeListener()
    {
        state.removeParameterListener (paramID, this);
    }

    void setNewUnnormalisedValue (float newUnnormalisedValue)
    {
        if (AudioProcessorParameter* p = state.getParameter (paramID))
        {
            const float newValue = state.getParameterRange (paramID)
                                      .convertTo0to1 (newUnnormalisedValue);

            if (p->getValue() != newValue)
                p->setValueNotifyingHost (newValue);
        }
    }

    void sendInitialUpdate()
    {
        if (float* v = state.getRawParameterValue (paramID))
            parameterChanged (paramID, *v);
    }

    void parameterChanged (const String&, float newValue) override
    {
        lastValue = newValue;

        if (MessageManager::getInstance()->isThisTheMessageThread())
        {
            cancelPendingUpdate();
            setValue (newValue);
        }
        else
        {
            triggerAsyncUpdate();
        }
    }

    void beginParameterChange()
    {
        if (AudioProcessorParameter* p = state.getParameter (paramID))
            p->beginChangeGesture();
    }

    void endParameterChange()
    {
        if (AudioProcessorParameter* p = state.getParameter (paramID))
            p->endChangeGesture();
    }

    void handleAsyncUpdate() override
    {
        setValue (lastValue);
    }

    virtual void setValue (float) = 0;

    AudioProcessorValueTreeState& state;
    String paramID;
    float lastValue;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttachedControlBase)
};

//==============================================================================
struct AudioProcessorValueTreeState::SliderAttachment::Pimpl  : private AttachedControlBase,
                                                                private Slider::Listener
{
    Pimpl (AudioProcessorValueTreeState& s, const String& p, Slider& sl)
        : AttachedControlBase (s, p), slider (sl), ignoreCallbacks (false)
    {
        NormalisableRange<float> range (s.getParameterRange (paramID));
        slider.setRange (range.start, range.end, range.interval);
        slider.setSkewFactor (range.skew, range.symmetricSkew);

        if (AudioProcessorParameter* param = state.getParameter (paramID))
            slider.setDoubleClickReturnValue (true, range.convertFrom0to1 (param->getDefaultValue()));

        sendInitialUpdate();
        slider.addListener (this);
    }

    ~Pimpl()
    {
        slider.removeListener (this);
        removeListener();
    }

    void setValue (float newValue) override
    {
        const ScopedLock selfCallbackLock (selfCallbackMutex);

        {
            ScopedValueSetter<bool> svs (ignoreCallbacks, true);
            slider.setValue (newValue, sendNotificationSync);
        }
    }

    void sliderValueChanged (Slider* s) override
    {
        const ScopedLock selfCallbackLock (selfCallbackMutex);

        if ((! ignoreCallbacks) && (! ModifierKeys::getCurrentModifiers().isRightButtonDown()))
            setNewUnnormalisedValue ((float) s->getValue());
    }

    void sliderDragStarted (Slider*) override { beginParameterChange(); }
    void sliderDragEnded   (Slider*) override { endParameterChange();   }

    Slider& slider;
    bool ignoreCallbacks;
    CriticalSection selfCallbackMutex;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};

AudioProcessorValueTreeState::SliderAttachment::SliderAttachment (AudioProcessorValueTreeState& s, const String& p, Slider& sl)
    : pimpl (new Pimpl (s, p, sl))
{
}

AudioProcessorValueTreeState::SliderAttachment::~SliderAttachment() {}

//==============================================================================
struct AudioProcessorValueTreeState::ComboBoxAttachment::Pimpl  : private AttachedControlBase,
                                                                  private ComboBox::Listener
{
    Pimpl (AudioProcessorValueTreeState& s, const String& p, ComboBox& c)
        : AttachedControlBase (s, p), combo (c), ignoreCallbacks (false)
    {
        sendInitialUpdate();
        combo.addListener (this);
    }

    ~Pimpl()
    {
        combo.removeListener (this);
        removeListener();
    }

    void setValue (float newValue) override
    {
        const ScopedLock selfCallbackLock (selfCallbackMutex);

        {
            ScopedValueSetter<bool> svs (ignoreCallbacks, true);
            combo.setSelectedItemIndex (roundToInt (newValue), sendNotificationSync);
        }
    }

    void comboBoxChanged (ComboBox* comboBox) override
    {
        const ScopedLock selfCallbackLock (selfCallbackMutex);

        if (! ignoreCallbacks)
        {
            beginParameterChange();
            setNewUnnormalisedValue ((float) comboBox->getSelectedId() - 1.0f);
            endParameterChange();
        }
    }

    ComboBox& combo;
    bool ignoreCallbacks;
    CriticalSection selfCallbackMutex;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};

AudioProcessorValueTreeState::ComboBoxAttachment::ComboBoxAttachment (AudioProcessorValueTreeState& s, const String& p, ComboBox& c)
    : pimpl (new Pimpl (s, p, c))
{
}

AudioProcessorValueTreeState::ComboBoxAttachment::~ComboBoxAttachment() {}

//==============================================================================
struct AudioProcessorValueTreeState::ButtonAttachment::Pimpl  : private AttachedControlBase,
                                                                private Button::Listener
{
    Pimpl (AudioProcessorValueTreeState& s, const String& p, Button& b)
        : AttachedControlBase (s, p), button (b), ignoreCallbacks (false)
    {
        sendInitialUpdate();
        button.addListener (this);
    }

    ~Pimpl()
    {
        button.removeListener (this);
        removeListener();
    }

    void setValue (float newValue) override
    {
        const ScopedLock selfCallbackLock (selfCallbackMutex);

        {
            ScopedValueSetter<bool> svs (ignoreCallbacks, true);
            button.setToggleState (newValue >= 0.5f, sendNotificationSync);
        }
    }

    void buttonClicked (Button* b) override
    {
        const ScopedLock selfCallbackLock (selfCallbackMutex);

        if (! ignoreCallbacks)
        {
            beginParameterChange();
            setNewUnnormalisedValue (b->getToggleState() ? 1.0f : 0.0f);
            endParameterChange();
        }
    }

    Button& button;
    bool ignoreCallbacks;
    CriticalSection selfCallbackMutex;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};

AudioProcessorValueTreeState::ButtonAttachment::ButtonAttachment (AudioProcessorValueTreeState& s, const String& p, Button& b)
    : pimpl (new Pimpl (s, p, b))
{
}

AudioProcessorValueTreeState::ButtonAttachment::~ButtonAttachment() {}

} // namespace juce
