// yabridge: a Wine VST bridge
// Copyright (C) 2020-2021 Robbert van der Helm
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

#pragma once

#include "../vst3.h"
#include "plug-view-proxy.h"

class Vst3PluginProxyImpl : public Vst3PluginProxy {
   public:
    Vst3PluginProxyImpl(Vst3PluginBridge& bridge,
                        Vst3PluginProxy::ConstructArgs&& args);

    /**
     * When the reference count reaches zero and this destructor is called,
     * we'll send a request to the Wine plugin host to destroy the corresponding
     * object.
     */
    ~Vst3PluginProxyImpl();

    /**
     * We'll override the query interface to log queries for interfaces we do
     * not (yet) support.
     */
    tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid,
                                      void** obj) override;

    /**
     * Add a context menu created by a call to
     * `IComponentHandler3::createContextMenu` to our list of registered cotnext
     * menus. This way we can refer to it later when the plugin calls a function
     * on the proxy object we'll create for it.
     */
    size_t register_context_menu(
        Steinberg::IPtr<Steinberg::Vst::IContextMenu> menu);

    /**
     * Unregister a context menu using the ID generated by a previous call to
     * `register_context_menu()`. This will release the context menu object
     * returned by the host.
     */
    bool unregister_context_menu(size_t context_menu_id);

    // From `IAudioPresentationLatency`
    tresult PLUGIN_API
    setAudioPresentationLatencySamples(Steinberg::Vst::BusDirection dir,
                                       int32 busIndex,
                                       uint32 latencyInSamples) override;

    // From `IAudioProcessor`
    tresult PLUGIN_API
    setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs,
                       int32 numIns,
                       Steinberg::Vst::SpeakerArrangement* outputs,
                       int32 numOuts) override;
    tresult PLUGIN_API
    getBusArrangement(Steinberg::Vst::BusDirection dir,
                      int32 index,
                      Steinberg::Vst::SpeakerArrangement& arr) override;
    tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override;
    uint32 PLUGIN_API getLatencySamples() override;
    tresult PLUGIN_API
    setupProcessing(Steinberg::Vst::ProcessSetup& setup) override;
    tresult PLUGIN_API setProcessing(TBool state) override;
    tresult PLUGIN_API process(Steinberg::Vst::ProcessData& data) override;
    uint32 PLUGIN_API getTailSamples() override;

    // From `IAutomationState`
    tresult PLUGIN_API setAutomationState(int32 state) override;

    // From `IComponent`
    tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override;
    tresult PLUGIN_API setIoMode(Steinberg::Vst::IoMode mode) override;
    int32 PLUGIN_API getBusCount(Steinberg::Vst::MediaType type,
                                 Steinberg::Vst::BusDirection dir) override;
    tresult PLUGIN_API
    getBusInfo(Steinberg::Vst::MediaType type,
               Steinberg::Vst::BusDirection dir,
               int32 index,
               Steinberg::Vst::BusInfo& bus /*out*/) override;
    tresult PLUGIN_API
    getRoutingInfo(Steinberg::Vst::RoutingInfo& inInfo,
                   Steinberg::Vst::RoutingInfo& outInfo /*out*/) override;
    tresult PLUGIN_API activateBus(Steinberg::Vst::MediaType type,
                                   Steinberg::Vst::BusDirection dir,
                                   int32 index,
                                   TBool state) override;
    tresult PLUGIN_API setActive(TBool state) override;
    tresult PLUGIN_API setState(Steinberg::IBStream* state) override;
    tresult PLUGIN_API getState(Steinberg::IBStream* state) override;

    // From `IConnectionPoint`
    tresult PLUGIN_API connect(IConnectionPoint* other) override;
    tresult PLUGIN_API disconnect(IConnectionPoint* other) override;
    tresult PLUGIN_API notify(Steinberg::Vst::IMessage* message) override;

    // From `IEditController`
    tresult PLUGIN_API setComponentState(Steinberg::IBStream* state) override;
    // `IEditController` also contains `getState()` and `setState()`  functions.
    // These are identical to those defiend in `IComponent` and they're thus
    // handled in in the same function.
    int32 PLUGIN_API getParameterCount() override;
    tresult PLUGIN_API
    getParameterInfo(int32 paramIndex,
                     Steinberg::Vst::ParameterInfo& info /*out*/) override;
    tresult PLUGIN_API
    getParamStringByValue(Steinberg::Vst::ParamID id,
                          Steinberg::Vst::ParamValue valueNormalized /*in*/,
                          Steinberg::Vst::String128 string /*out*/) override;
    tresult PLUGIN_API getParamValueByString(
        Steinberg::Vst::ParamID id,
        Steinberg::Vst::TChar* string /*in*/,
        Steinberg::Vst::ParamValue& valueNormalized /*out*/) override;
    Steinberg::Vst::ParamValue PLUGIN_API
    normalizedParamToPlain(Steinberg::Vst::ParamID id,
                           Steinberg::Vst::ParamValue valueNormalized) override;
    Steinberg::Vst::ParamValue PLUGIN_API
    plainParamToNormalized(Steinberg::Vst::ParamID id,
                           Steinberg::Vst::ParamValue plainValue) override;
    Steinberg::Vst::ParamValue PLUGIN_API
    getParamNormalized(Steinberg::Vst::ParamID id) override;
    tresult PLUGIN_API
    setParamNormalized(Steinberg::Vst::ParamID id,
                       Steinberg::Vst::ParamValue value) override;
    tresult PLUGIN_API
    setComponentHandler(Steinberg::Vst::IComponentHandler* handler) override;
    Steinberg::IPlugView* PLUGIN_API
    createView(Steinberg::FIDString name) override;

    // From `IEditController2`
    tresult PLUGIN_API setKnobMode(Steinberg::Vst::KnobMode mode) override;
    tresult PLUGIN_API openHelp(TBool onlyCheck) override;
    tresult PLUGIN_API openAboutBox(TBool onlyCheck) override;

    // From `IEditControllerHostEditing`
    tresult PLUGIN_API
    beginEditFromHost(Steinberg::Vst::ParamID paramID) override;
    tresult PLUGIN_API
    endEditFromHost(Steinberg::Vst::ParamID paramID) override;

    // From `IKeyswitchController`
    int32 PLUGIN_API getKeyswitchCount(int32 busIndex, int16 channel) override;
    tresult PLUGIN_API
    getKeyswitchInfo(int32 busIndex,
                     int16 channel,
                     int32 keySwitchIndex,
                     Steinberg::Vst::KeyswitchInfo& info /*out*/) override;

    // From `IMidiMapping`
    tresult PLUGIN_API
    getMidiControllerAssignment(int32 busIndex,
                                int16 channel,
                                Steinberg::Vst::CtrlNumber midiControllerNumber,
                                Steinberg::Vst::ParamID& id /*out*/) override;

    // From `INoteExpressionController`
    int32 PLUGIN_API getNoteExpressionCount(int32 busIndex,
                                            int16 channel) override;
    tresult PLUGIN_API getNoteExpressionInfo(
        int32 busIndex,
        int16 channel,
        int32 noteExpressionIndex,
        Steinberg::Vst::NoteExpressionTypeInfo& info /*out*/) override;
    tresult PLUGIN_API getNoteExpressionStringByValue(
        int32 busIndex,
        int16 channel,
        Steinberg::Vst::NoteExpressionTypeID id,
        Steinberg::Vst::NoteExpressionValue valueNormalized /*in*/,
        Steinberg::Vst::String128 string /*out*/) override;
    tresult PLUGIN_API getNoteExpressionValueByString(
        int32 busIndex,
        int16 channel,
        Steinberg::Vst::NoteExpressionTypeID id,
        const Steinberg::Vst::TChar* string /*in*/,
        Steinberg::Vst::NoteExpressionValue& valueNormalized /*out*/) override;

    // From `IPluginBase`
    tresult PLUGIN_API initialize(FUnknown* context) override;
    tresult PLUGIN_API terminate() override;

    // From `IProgramListData`
    tresult PLUGIN_API
    programDataSupported(Steinberg::Vst::ProgramListID listId) override;
    tresult PLUGIN_API getProgramData(Steinberg::Vst::ProgramListID listId,
                                      int32 programIndex,
                                      Steinberg::IBStream* data) override;
    tresult PLUGIN_API setProgramData(Steinberg::Vst::ProgramListID listId,
                                      int32 programIndex,
                                      Steinberg::IBStream* data) override;

    // From `IUnitData`
    tresult PLUGIN_API
    unitDataSupported(Steinberg::Vst::UnitID unitId) override;
    tresult PLUGIN_API getUnitData(Steinberg::Vst::UnitID unitId,
                                   Steinberg::IBStream* data) override;
    tresult PLUGIN_API setUnitData(Steinberg::Vst::UnitID unitId,
                                   Steinberg::IBStream* data) override;

    // From `IUnitInfo`
    int32 PLUGIN_API getUnitCount() override;
    tresult PLUGIN_API
    getUnitInfo(int32 unitIndex,
                Steinberg::Vst::UnitInfo& info /*out*/) override;
    int32 PLUGIN_API getProgramListCount() override;
    tresult PLUGIN_API
    getProgramListInfo(int32 listIndex,
                       Steinberg::Vst::ProgramListInfo& info /*out*/) override;
    tresult PLUGIN_API
    getProgramName(Steinberg::Vst::ProgramListID listId,
                   int32 programIndex,
                   Steinberg::Vst::String128 name /*out*/) override;
    tresult PLUGIN_API
    getProgramInfo(Steinberg::Vst::ProgramListID listId,
                   int32 programIndex,
                   Steinberg::Vst::CString attributeId /*in*/,
                   Steinberg::Vst::String128 attributeValue /*out*/) override;
    tresult PLUGIN_API
    hasProgramPitchNames(Steinberg::Vst::ProgramListID listId,
                         int32 programIndex) override;
    tresult PLUGIN_API
    getProgramPitchName(Steinberg::Vst::ProgramListID listId,
                        int32 programIndex,
                        int16 midiPitch,
                        Steinberg::Vst::String128 name /*out*/) override;
    Steinberg::Vst::UnitID PLUGIN_API getSelectedUnit() override;
    tresult PLUGIN_API selectUnit(Steinberg::Vst::UnitID unitId) override;
    tresult PLUGIN_API
    getUnitByBus(Steinberg::Vst::MediaType type,
                 Steinberg::Vst::BusDirection dir,
                 int32 busIndex,
                 int32 channel,
                 Steinberg::Vst::UnitID& unitId /*out*/) override;
    tresult PLUGIN_API setUnitProgramData(int32 listOrUnitId,
                                          int32 programIndex,
                                          Steinberg::IBStream* data) override;

    // From `IXmlRepresentationController`
    tresult PLUGIN_API
    getXmlRepresentationStream(Steinberg::Vst::RepresentationInfo& info /*in*/,
                               Steinberg::IBStream* stream /*out*/);

    /**
     * The component handler the host passed to us during
     * `IEditController::setComponentHandler()`. When the plugin makes a
     * callback on a component handler proxy object, we'll pass the call through
     * to this object.
     */
    Steinberg::IPtr<Steinberg::Vst::IComponentHandler> component_handler;

    /**
     * If the host doesn't connect two objects directly in
     * `IConnectionPoint::connect` but instead connects them through a proxy,
     * we'll store that proxy here. This way we can then route messages sent by
     * the plugin through this proxy. So far this is only needed for Ardour.
     */
    Steinberg::IPtr<Steinberg::Vst::IConnectionPoint> connection_point_proxy;

    /**
     * An unmanaged, raw pointer to the `IPlugView` instance returned in our
     * implementation of `IEditController::createView()`. We need this to handle
     * `IPlugFrame::resizeView()`, since that expects a pointer to the view that
     * gets resized.
     *
     * XXX: This approach of course won't work with multiple views, but the SDK
     *      currently only defines a single type of view so that shouldn't be an
     *      issue
     */
    Vst3PlugViewProxyImpl* last_created_plug_view = nullptr;

    /**
     * A pointer to a context menu returned by the host as a response to a call
     * to `IComponentHandler3::createContextMenu`, as well as all targets we've
     * created for it. This way we can drop both all at once.
     */
    struct ContextMenu {
        ContextMenu(Steinberg::IPtr<Steinberg::Vst::IContextMenu> menu);

        Steinberg::IPtr<Steinberg::Vst::IContextMenu> menu;

        /**
         * All targets we pass to `IContextMenu::addItem`. We'll store them per
         * item tag, so we can drop them together with the menu. We probably
         * don't have to use smart pointers for this, but the docs are missing a
         * lot of details o how this should be implemented and there's no
         * example implementation around.
         */
        std::map<int32, Steinberg::IPtr<YaContextMenuTarget>> targets;
    };

    /**
     * All context menus created by this object through
     * `IComponentHandler3::createContextMenu()`. We'll generate a unique
     * identifier for each context menu just like we do for plugin objects. When
     * the plugin drops the context menu object, we'll also remove the
     * corresponding entry for this map causing the original pointer returned by
     * the host to get dropped a well.
     *
     * @see Vst3PluginProxyImpl::register_context_menu
     * @see Vst3PluginProxyImpl::unregister_context_menu
     */
    std::map<size_t, ContextMenu> context_menus;
    std::mutex context_menus_mutex;

    // The following pointers are cast from `host_context` if
    // `IPluginBase::initialize()` has been called

    Steinberg::FUnknownPtr<Steinberg::Vst::IHostApplication> host_application;

    // The following pointers are cast from `component_handler` if
    // `IEditController::setComponentHandler()` has been called

    Steinberg::FUnknownPtr<Steinberg::Vst::IComponentHandler2>
        component_handler_2;
    Steinberg::FUnknownPtr<Steinberg::Vst::IComponentHandler3>
        component_handler_3;
    Steinberg::FUnknownPtr<Steinberg::Vst::IUnitHandler> unit_handler;
    Steinberg::FUnknownPtr<Steinberg::Vst::IUnitHandler2> unit_handler_2;

   private:
    Vst3PluginBridge& bridge;

    /**
     * An host context if we get passed one through `IPluginBase::initialize()`.
     * We'll read which interfaces it supports and we'll then create a proxy
     * object that supports those same interfaces. This should be the same for
     * all plugin instances so we should not have to store it here separately,
     * but for the sake of correctness we will.
     */
    Steinberg::IPtr<Steinberg::FUnknown> host_context;

    /**
     * Used to assign unique identifiers to context menus created by
     * `IComponentHandler3::CreateContextMenu`.
     *
     * @related Vst3PluginProxyImpl::register_context_menu
     */
    std::atomic_size_t current_context_menu_id;
};
