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

   This file is part of the JUCE library.
   Copyright (c) 2020 - Raw Material Software Limited

   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 6 End-User License
   Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).

   End User License Agreement: www.juce.com/juce-6-licence
   Privacy Policy: www.juce.com/juce-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
{

/**
    This class contains a ValueTree that is used to manage an AudioProcessor's entire state.

    It has its own internal class of parameter object that is linked to values
    within its ValueTree, and which are each identified by a string ID.

    You can get access to the underlying ValueTree object via the state member variable,
    so you can add extra properties to it as necessary.

    It also provides some utility child classes for connecting parameters directly to
    GUI controls like sliders.

    The favoured constructor of this class takes a collection of RangedAudioParameters or
    AudioProcessorParameterGroups of RangedAudioParameters and adds them to the attached
    AudioProcessor directly.

    The deprecated way of using this class is as follows:

    1) Create an AudioProcessorValueTreeState, and give it some parameters using createAndAddParameter().
    2) Initialise the state member variable with a type name.

    The deprecated constructor will be removed from the API in a future version of JUCE!

    @tags{Audio}
*/
class JUCE_API AudioProcessorValueTreeState   : private Timer,
                                                private ValueTree::Listener
{
public:
    //==============================================================================
    /** A class to contain a set of RangedAudioParameters and AudioProcessorParameterGroups
        containing RangedAudioParameters.

        This class is used in the AudioProcessorValueTreeState constructor to allow
        arbitrarily grouped RangedAudioParameters to be passed to an AudioProcessor.
    */
    class JUCE_API ParameterLayout final
    {
    private:
        //==============================================================================
        template <typename It>
        using ValidIfIterator = decltype (std::next (std::declval<It>()));

    public:
        //==============================================================================
        template <typename... Items>
        ParameterLayout (std::unique_ptr<Items>... items) { add (std::move (items)...); }

        template <typename It, typename = ValidIfIterator<It>>
        ParameterLayout (It begin, It end) { add (begin, end); }

        template <typename... Items>
        void add (std::unique_ptr<Items>... items)
        {
            parameters.reserve (parameters.size() + sizeof... (items));

            // We can replace this with some nicer code once generic lambdas become available. A
            // sequential context like an array initialiser is required to ensure we get the correct
            // order from the parameter pack.
            int unused[] { (parameters.emplace_back (MakeContents() (std::move (items))), 0)... };
            ignoreUnused (unused);
        }

        template <typename It, typename = ValidIfIterator<It>>
        void add (It begin, It end)
        {
            parameters.reserve (parameters.size() + std::size_t (std::distance (begin, end)));
            std::transform (std::make_move_iterator (begin),
                            std::make_move_iterator (end),
                            std::back_inserter (parameters),
                            MakeContents());
        }

        ParameterLayout (const ParameterLayout& other) = delete;
        ParameterLayout (ParameterLayout&& other) noexcept { swap (other); }

        ParameterLayout& operator= (const ParameterLayout& other) = delete;
        ParameterLayout& operator= (ParameterLayout&& other) noexcept { swap (other); return *this; }

        void swap (ParameterLayout& other) noexcept { std::swap (other.parameters, parameters); }

    private:
        //==============================================================================
        struct Visitor
        {
            virtual ~Visitor() = default;

            // If you have a compiler error telling you that there is no matching
            // member function to call for 'visit', then you are probably attempting
            // to add a parameter that is not derived from RangedAudioParameter to
            // the AudioProcessorValueTreeState.
            virtual void visit (std::unique_ptr<RangedAudioParameter>) const = 0;
            virtual void visit (std::unique_ptr<AudioProcessorParameterGroup>) const = 0;
        };

        struct ParameterStorageBase
        {
            virtual ~ParameterStorageBase() = default;
            virtual void accept (const Visitor& visitor) = 0;
        };

        template <typename Contents>
        struct ParameterStorage : ParameterStorageBase
        {
            explicit ParameterStorage (std::unique_ptr<Contents> input) : contents (std::move (input)) {}

            void accept (const Visitor& visitor) override   { visitor.visit (std::move (contents)); }

            std::unique_ptr<Contents> contents;
        };

        struct MakeContents final
        {
            template <typename Item>
            std::unique_ptr<ParameterStorageBase> operator() (std::unique_ptr<Item> item) const
            {
                return std::unique_ptr<ParameterStorageBase> (new ParameterStorage<Item> (std::move (item)));
            }
        };

        void add() {}

        friend class AudioProcessorValueTreeState;

        std::vector<std::unique_ptr<ParameterStorageBase>> parameters;
    };

    //==============================================================================
    /** Creates a state object for a given processor, and sets up all the parameters
        that will control that processor.

        You should *not* assign a new ValueTree to the state, or call
        createAndAddParameter, after using this constructor.

        Note that each AudioProcessorValueTreeState should be attached to only one
        processor, and must have the same lifetime as the processor, as they will
        have dependencies on each other.

        The ParameterLayout parameter has a set of constructors that allow you to
        add multiple RangedAudioParameters and AudioProcessorParameterGroups containing
        RangedAudioParameters to the AudioProcessorValueTreeState inside this constructor.

        @code
        YourAudioProcessor()
            : apvts (*this, &undoManager, "PARAMETERS",
                     { std::make_unique<AudioParameterFloat> ("a", "Parameter A", NormalisableRange<float> (-100.0f, 100.0f), 0),
                       std::make_unique<AudioParameterInt> ("b", "Parameter B", 0, 5, 2) })
        @endcode

        To add parameters programatically you can call `add` repeatedly on a
        ParameterLayout instance:

        @code
        AudioProcessorValueTreeState::ParameterLayout createParameterLayout()
        {
            AudioProcessorValueTreeState::ParameterLayout layout;

            for (int i = 1; i < 9; ++i)
                layout.add (std::make_unique<AudioParameterInt> (String (i), String (i), 0, i, 0));

            return layout;
        }

        YourAudioProcessor()
            : apvts (*this, &undoManager, "PARAMETERS", createParameterLayout())
        {
        }
        @endcode

        @param processorToConnectTo     The Processor that will be managed by this object
        @param undoManagerToUse         An optional UndoManager to use; pass nullptr if no UndoManager is required
        @param valueTreeType            The identifier used to initialise the internal ValueTree
        @param parameterLayout          An object that holds all parameters and parameter groups that the
                                        AudioProcessor should use.
    */
    AudioProcessorValueTreeState (AudioProcessor& processorToConnectTo,
                                  UndoManager* undoManagerToUse,
                                  const Identifier& valueTreeType,
                                  ParameterLayout parameterLayout);

    /** This constructor is discouraged and will be deprecated in a future version of JUCE!
        Use the other constructor instead.

        Creates a state object for a given processor.

        The UndoManager is optional and can be a nullptr. After creating your state object,
        you should add parameters with the createAndAddParameter() method. Note that each
        AudioProcessorValueTreeState should be attached to only one processor, and must have
        the same lifetime as the processor, as they will have dependencies on each other.
    */
    AudioProcessorValueTreeState (AudioProcessor& processorToConnectTo, UndoManager* undoManagerToUse);

    /** Destructor. */
    ~AudioProcessorValueTreeState() override;

    //==============================================================================
    /** This function is deprecated and will be removed in a future version of JUCE!

        Previous calls to

        @code
        createAndAddParameter (paramID1, paramName1, ...);
        @endcode

        can be replaced with

        @code
        using Parameter = AudioProcessorValueTreeState::Parameter;
        createAndAddParameter (std::make_unique<Parameter> (paramID1, paramName1, ...));
        @endcode

        However, a much better approach is to use the AudioProcessorValueTreeState
        constructor directly

        @code
        using Parameter = AudioProcessorValueTreeState::Parameter;
        YourAudioProcessor()
            : apvts (*this, &undoManager, "PARAMETERS", { std::make_unique<Parameter> (paramID1, paramName1, ...),
                                                          std::make_unique<Parameter> (paramID2, paramName2, ...),
                                                          ... })
        @endcode

        @see AudioProcessorValueTreeState::AudioProcessorValueTreeState

        This function creates and returns a new parameter object for controlling a
        parameter with the given ID.

        Calling this will create and add a special type of AudioProcessorParameter to the
        AudioProcessor to which this state is attached.
    */
    JUCE_DEPRECATED (RangedAudioParameter* createAndAddParameter (const String& parameterID,
                                                                  const String& parameterName,
                                                                  const String& labelText,
                                                                  NormalisableRange<float> valueRange,
                                                                  float defaultValue,
                                                                  std::function<String (float)> valueToTextFunction,
                                                                  std::function<float (const String&)> textToValueFunction,
                                                                  bool isMetaParameter = false,
                                                                  bool isAutomatableParameter = true,
                                                                  bool isDiscrete = false,
                                                                  AudioProcessorParameter::Category parameterCategory = AudioProcessorParameter::genericParameter,
                                                                  bool isBoolean = false));

    /** This function adds a parameter to the attached AudioProcessor and that parameter will
        be managed by this AudioProcessorValueTreeState object.
    */
    RangedAudioParameter* createAndAddParameter (std::unique_ptr<RangedAudioParameter> parameter);

    //==============================================================================
    /** Returns a parameter by its ID string. */
    RangedAudioParameter* getParameter (StringRef parameterID) const noexcept;

    /** Returns a pointer to a floating point representation of a particular parameter which a realtime
        process can read to find out its current value.

        Note that calling this method from within AudioProcessorValueTreeState::Listener::parameterChanged()
        is not guaranteed to return an up-to-date value for the parameter.
    */
    std::atomic<float>* getRawParameterValue (StringRef parameterID) const noexcept;

    //==============================================================================
    /** A listener class that can be attached to an AudioProcessorValueTreeState.
        Use AudioProcessorValueTreeState::addParameterListener() to register a callback.
    */
    struct JUCE_API  Listener
    {
        virtual ~Listener() = default;

        /** This callback method is called by the AudioProcessorValueTreeState when a parameter changes.

            Within this call, retrieving the value of the parameter that has changed via the getRawParameterValue()
            or getParameter() methods is not guaranteed to return the up-to-date value. If you need this you should
            instead use the newValue parameter.
        */
        virtual void parameterChanged (const String& parameterID, float newValue) = 0;
    };

    /** Attaches a callback to one of the parameters, which will be called when the parameter changes. */
    void addParameterListener (StringRef parameterID, Listener* listener);

    /** Removes a callback that was previously added with addParameterCallback(). */
    void removeParameterListener (StringRef parameterID, Listener* listener);

    //==============================================================================
    /** Returns a Value object that can be used to control a particular parameter. */
    Value getParameterAsValue (StringRef parameterID) const;

    /** Returns the range that was set when the given parameter was created. */
    NormalisableRange<float> getParameterRange (StringRef parameterID) const noexcept;

    //==============================================================================
    /** Returns a copy of the state value tree.

        The AudioProcessorValueTreeState's ValueTree is updated internally on the
        message thread, but there may be cases when you may want to access the state
        from a different thread (getStateInformation is a good example). This method
        flushes all pending audio parameter value updates and returns a copy of the
        state in a thread safe way.

        Note: This method uses locks to synchronise thread access, so whilst it is
        thread-safe, it is not realtime-safe. Do not call this method from within
        your audio processing code!
    */
    ValueTree copyState();

    /** Replaces the state value tree.

        The AudioProcessorValueTreeState's ValueTree is updated internally on the
        message thread, but there may be cases when you may want to modify the state
        from a different thread (setStateInformation is a good example). This method
        allows you to replace the state in a thread safe way.

        Note: This method uses locks to synchronise thread access, so whilst it is
        thread-safe, it is not realtime-safe. Do not call this method from within
        your audio processing code!
    */
    void replaceState (const ValueTree& newState);

    //==============================================================================
    /** A reference to the processor with which this state is associated. */
    AudioProcessor& processor;

    /** The state of the whole processor.

        This must be initialised after all calls to createAndAddParameter().
        You can replace this with your own ValueTree object, and can add properties and
        children to the tree. This class will automatically add children for each of the
        parameter objects that are created by createAndAddParameter().
    */
    ValueTree state;

    /** Provides access to the undo manager that this object is using. */
    UndoManager* const undoManager;

private:
    //==============================================================================
    class ParameterAdapter;

public:
    //==============================================================================
    /** A parameter class that maintains backwards compatibility with deprecated
        AudioProcessorValueTreeState functionality.

        Previous calls to

        @code
        createAndAddParameter (paramID1, paramName1, ...);
        @endcode

        can be replaced with

        @code
        using Parameter = AudioProcessorValueTreeState::Parameter;
        createAndAddParameter (std::make_unique<Parameter> (paramID1, paramName1, ...));
        @endcode

        However, a much better approach is to use the AudioProcessorValueTreeState
        constructor directly

        @code
        using Parameter = AudioProcessorValueTreeState::Parameter;
        YourAudioProcessor()
            : apvts (*this, &undoManager, "PARAMETERS", { std::make_unique<Parameter> (paramID1, paramName1, ...),
                                                          std::make_unique<Parameter> (paramID2, paramName2, ...),
                                                          ... })
        @endcode
    */
    class Parameter final  : public AudioParameterFloat
    {
    public:
        Parameter (const String& parameterID,
                   const String& parameterName,
                   const String& labelText,
                   NormalisableRange<float> valueRange,
                   float defaultValue,
                   std::function<String (float)> valueToTextFunction,
                   std::function<float (const String&)> textToValueFunction,
                   bool isMetaParameter = false,
                   bool isAutomatableParameter = true,
                   bool isDiscrete = false,
                   AudioProcessorParameter::Category parameterCategory = AudioProcessorParameter::genericParameter,
                   bool isBoolean = false);

        float getDefaultValue() const override;
        int getNumSteps() const override;

        bool isMetaParameter() const override;
        bool isAutomatable() const override;
        bool isDiscrete() const override;
        bool isBoolean() const override;

    private:
        void valueChanged (float) override;

        std::function<void()> onValueChanged;

        const float unsnappedDefault;
        const bool metaParameter, automatable, discrete, boolean;
        std::atomic<float> lastValue { -1.0f };

        friend class AudioProcessorValueTreeState::ParameterAdapter;
    };

    //==============================================================================
    /** An object of this class maintains a connection between a Slider and a parameter
        in an AudioProcessorValueTreeState.

        During the lifetime of this SliderAttachment object, it keeps the two things in
        sync, making it easy to connect a slider to a parameter. When this object is
        deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState
        and Slider aren't deleted before this object!
    */
    class JUCE_API  SliderAttachment
    {
    public:
        SliderAttachment (AudioProcessorValueTreeState& stateToUse,
                          const String& parameterID,
                          Slider& slider);

    private:
        std::unique_ptr<SliderParameterAttachment> attachment;
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderAttachment)
    };

    //==============================================================================
    /** An object of this class maintains a connection between a ComboBox and a parameter
        in an AudioProcessorValueTreeState.

        Combobox items will be spaced linearly across the range of the parameter. For
        example if the range is specified by NormalisableRange<float> (-0.5f, 0.5f, 0.5f)
        and you add three items then the first will be mapped to a value of -0.5, the
        second to 0, and the third to 0.5.

        During the lifetime of this ComboBoxAttachment object, it keeps the two things in
        sync, making it easy to connect a combo box to a parameter. When this object is
        deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState
        and ComboBox aren't deleted before this object!
    */
    class JUCE_API  ComboBoxAttachment
    {
    public:
        ComboBoxAttachment (AudioProcessorValueTreeState& stateToUse,
                            const String& parameterID,
                            ComboBox& combo);

    private:
        std::unique_ptr<ComboBoxParameterAttachment> attachment;
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxAttachment)
    };

    //==============================================================================
    /** An object of this class maintains a connection between a Button and a parameter
        in an AudioProcessorValueTreeState.

        During the lifetime of this ButtonAttachment object, it keeps the two things in
        sync, making it easy to connect a button to a parameter. When this object is
        deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState
        and Button aren't deleted before this object!
    */
    class JUCE_API  ButtonAttachment
    {
    public:
        ButtonAttachment (AudioProcessorValueTreeState& stateToUse,
                          const String& parameterID,
                          Button& button);

    private:
        std::unique_ptr<ButtonParameterAttachment> attachment;
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAttachment)
    };

private:
    //==============================================================================
    /** This method was introduced to allow you to use AudioProcessorValueTreeState parameters in
        an AudioProcessorParameterGroup, but there is now a much nicer way to achieve this.

        Code that looks like this
        @code
        auto paramA = apvts.createParameter ("a", "Parameter A", {}, { -100, 100 }, ...);
        auto paramB = apvts.createParameter ("b", "Parameter B", {}, { 0, 5 }, ...);
        addParameterGroup (std::make_unique<AudioProcessorParameterGroup> ("g1", "Group 1", " | ", std::move (paramA), std::move (paramB)));
        apvts.state = ValueTree (Identifier ("PARAMETERS"));
        @endcode
        can instead create the APVTS like this, avoiding the two-step initialization process and leveraging one of JUCE's
        pre-built parameter types (or your own custom type derived from RangedAudioParameter)
        @code
        using Parameter = AudioProcessorValueTreeState::Parameter;
        YourAudioProcessor()
            : apvts (*this, &undoManager, "PARAMETERS",
                     { std::make_unique<AudioProcessorParameterGroup> ("g1", "Group 1", " | ",
                           std::make_unique<Parameter> ("a", "Parameter A", "", NormalisableRange<float> (-100, 100), ...),
                           std::make_unique<Parameter> ("b", "Parameter B", "", NormalisableRange<float> (0, 5), ...)) })
        @endcode
    */
    JUCE_DEPRECATED (std::unique_ptr<RangedAudioParameter> createParameter (const String&, const String&, const String&, NormalisableRange<float>,
                                                                            float, std::function<String (float)>, std::function<float (const String&)>,
                                                                            bool, bool, bool, AudioProcessorParameter::Category, bool));

    //==============================================================================
   #if JUCE_UNIT_TESTS
    friend struct ParameterAdapterTests;
   #endif

    void addParameterAdapter (RangedAudioParameter&);
    ParameterAdapter* getParameterAdapter (StringRef) const;

    bool flushParameterValuesToValueTree();
    void setNewState (ValueTree);
    void timerCallback() override;

    void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
    void valueTreeChildAdded (ValueTree&, ValueTree&) override;
    void valueTreeRedirected (ValueTree&) override;
    void updateParameterConnectionsToChildTrees();

    const Identifier valueType { "PARAM" }, valuePropertyID { "value" }, idPropertyID { "id" };

    struct StringRefLessThan final
    {
        bool operator() (StringRef a, StringRef b) const noexcept { return a.text.compare (b.text) < 0; }
    };

    std::map<StringRef, std::unique_ptr<ParameterAdapter>, StringRefLessThan> adapterTable;

    CriticalSection valueTreeChanging;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorValueTreeState)
};

} // namespace juce
