/*
 * DISTRHO Plugin Toolkit (DPT)
 * Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any purpose with
 * or without fee is hereby granted, provided that the above copyright notice and this
 * permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
 * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "DistrhoPluginInternal.hpp"

#if DISTRHO_PLUGIN_HAS_UI
# include "DistrhoUIInternal.hpp"
#endif

#ifndef __cdecl
# define __cdecl
#endif

// has some conflicts
#ifdef noexcept
# undef noexcept
#endif

#define VESTIGE_HEADER
#define VST_FORCE_DEPRECATED 0

#include <map>
#include <string>

#ifdef VESTIGE_HEADER
# include "vestige/aeffectx.h"
#define effFlagsProgramChunks (1 << 5)
#define effGetParamLabel 6
#define effGetParamDisplay 7
#define effGetChunk 23
#define effSetChunk 24
#define effCanBeAutomated 26
#define effGetProgramNameIndexed 29
#define effGetPlugCategory 35
#define effIdle 53
#define kPlugCategEffect 1
#define kPlugCategSynth 2
#define kVstVersion 2400
struct ERect {
    int16_t top, left, bottom, right;
};
#else
# include "vst/aeffectx.h"
#endif

#if DISTRHO_PLUGIN_WANT_STATE
# warning VST State still TODO (working but needs final testing)
#endif
#if DISTRHO_PLUGIN_WANT_TIMEPOS
# warning VST TimePos still TODO
#endif

typedef std::map<d_string,d_string> StringMap;

START_NAMESPACE_DISTRHO

// -----------------------------------------------------------------------

void strncpy(char* const dst, const char* const src, const size_t size)
{
    std::strncpy(dst, src, size);
    dst[size] = '\0';
}

// -----------------------------------------------------------------------

#if DISTRHO_PLUGIN_WANT_STATE
class StateHelper
{
public:
    virtual ~StateHelper() {}
    virtual void setSharedState(const char* const newKey, const char* const newValue) = 0;
};
#else
typedef void StateHelper;
#endif

// -----------------------------------------------------------------------

#if DISTRHO_PLUGIN_HAS_UI
class UIVst
{
public:
    UIVst(const audioMasterCallback audioMaster, AEffect* const effect, PluginExporter* const plugin, StateHelper* const stateHelper, const intptr_t winId)
        : fAudioMaster(audioMaster),
          fEffect(effect),
          fPlugin(plugin),
          fStateHelper(stateHelper),
          fUI(this, winId, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, uiResizeCallback),
          fParameterChecks(nullptr),
          fParameterValues(nullptr)
    {
        const uint32_t paramCount(plugin->getParameterCount());

        if (paramCount > 0)
        {
            fParameterChecks = new bool[paramCount];
            fParameterValues = new float[paramCount];

            for (uint32_t i=0; i < paramCount; ++i)
            {
                fParameterChecks[i] = false;
                fParameterValues[i] = 0.0f;
            }
        }

#if DISTRHO_PLUGIN_WANT_PROGRAMS
        fNextProgram = -1;
#endif
    }

    ~UIVst()
    {
        if (fParameterChecks != nullptr)
        {
            delete[] fParameterChecks;
            fParameterChecks = nullptr;
        }

        if (fParameterValues != nullptr)
        {
            delete[] fParameterValues;
            fParameterValues = nullptr;
        }
    }

    // -------------------------------------------------------------------

    void idle()
    {
#if DISTRHO_PLUGIN_WANT_PROGRAMS
        if (fNextProgram != -1)
        {
            fUI.programChanged(fNextProgram);
            fNextProgram = -1;
        }
#endif

        for (uint32_t i=0, count = fPlugin->getParameterCount(); i < count; ++i)
        {
            if (fParameterChecks[i])
            {
                fParameterChecks[i] = false;
                fUI.parameterChanged(i, fParameterValues[i]);
            }
        }

        fUI.idle();
    }

    int16_t getWidth() const
    {
        return fUI.getWidth();
    }

    int16_t getHeight() const
    {
        return fUI.getHeight();
    }

    // -------------------------------------------------------------------
    // functions called from the plugin side, RT no block

    void setParameterValueFromPlugin(const uint32_t index, const float perValue)
    {
        fParameterChecks[index] = true;
        fParameterValues[index] = perValue;
    }

#if DISTRHO_PLUGIN_WANT_PROGRAMS
    void setProgramFromPlugin(const uint32_t index)
    {
        fNextProgram = index;

        // set previous parameters invalid
        for (uint32_t i=0, count = fPlugin->getParameterCount(); i < count; ++i)
            fParameterChecks[i] = false;
    }
#endif

    // -------------------------------------------------------------------
    // functions called from the plugin side, block

#if DISTRHO_PLUGIN_WANT_STATE
    void setStateFromPlugin(const char* const key, const char* const value)
    {
        fUI.stateChanged(key, value);
    }
#endif

    // -------------------------------------------------------------------

protected:
    intptr_t hostCallback(const int32_t opcode, const int32_t index, const intptr_t value, void* const ptr, const float opt)
    {
        return fAudioMaster(fEffect, opcode, index, value, ptr, opt);
    }

    void editParameter(const uint32_t index, const bool started)
    {
        if (started)
            hostCallback(audioMasterBeginEdit, index, 0, nullptr, 0.0f);
        else
            hostCallback(audioMasterEndEdit, index, 0, nullptr, 0.0f);
    }

    void setParameterValue(const uint32_t index, const float realValue)
    {
        const ParameterRanges& ranges(fPlugin->getParameterRanges(index));
        const float perValue(ranges.getNormalizedValue(realValue));

        fPlugin->setParameterValue(index, realValue);
        hostCallback(audioMasterAutomate, index, 0, nullptr, perValue);
    }

    void setState(const char* const key, const char* const value)
    {
#if DISTRHO_PLUGIN_WANT_STATE
        fStateHelper->setSharedState(key, value);
#else
        return; // unused
        (void)key;
        (void)value;
#endif
    }

    void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
    {
#if 0 //DISTRHO_PLUGIN_IS_SYNTH
        // TODO
#else
        return; // unused
        (void)channel;
        (void)note;
        (void)velocity;
#endif
    }

    void uiResize(const unsigned int width, const unsigned int height)
    {
        fUI.setSize(width, height);
        hostCallback(audioMasterSizeWindow, width, height, nullptr, 0.0f);
    }

private:
    // Vst stuff
    const audioMasterCallback fAudioMaster;
    AEffect* const fEffect;
    PluginExporter* const fPlugin;
    StateHelper* const fStateHelper;

    // Plugin UI
    UIExporter fUI;

    // Temporary data
    bool*  fParameterChecks;
    float* fParameterValues;
#if DISTRHO_PLUGIN_WANT_PROGRAMS
    int32_t fNextProgram;
#endif

    // -------------------------------------------------------------------
    // Callbacks

    #define handlePtr ((UIVst*)ptr)

    static void editParameterCallback(void* ptr, uint32_t index, bool started)
    {
        handlePtr->editParameter(index, started);
    }

    static void setParameterCallback(void* ptr, uint32_t rindex, float value)
    {
        handlePtr->setParameterValue(rindex, value);
    }

    static void setStateCallback(void* ptr, const char* key, const char* value)
    {
        handlePtr->setState(key, value);
    }

    static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity)
    {
        handlePtr->sendNote(channel, note, velocity);
    }

    static void uiResizeCallback(void* ptr, unsigned int width, unsigned int height)
    {
        handlePtr->uiResize(width, height);
    }

    #undef handlePtr
};
#endif

// -----------------------------------------------------------------------

#if DISTRHO_PLUGIN_WANT_STATE
class PluginVst : public StateHelper
#else
class PluginVst
#endif
{
public:
    PluginVst(const audioMasterCallback audioMaster, AEffect* const effect)
        : fAudioMaster(audioMaster),
          fEffect(effect)
    {
#if DISTRHO_PLUGIN_WANT_PROGRAMS
        fCurProgram = -1;
#endif

#if DISTRHO_PLUGIN_IS_SYNTH
        fMidiEventCount = 0;
#endif

#if DISTRHO_PLUGIN_HAS_UI
        fVstUi          = nullptr;
        fVstRect.top    = 0;
        fVstRect.left   = 0;
        fVstRect.bottom = 0;
        fVstRect.right  = 0;
#endif

#if DISTRHO_PLUGIN_WANT_STATE
        fStateChunk = nullptr;
#endif
    }

#if DISTRHO_PLUGIN_WANT_STATE
    ~PluginVst()
    {
        if (fStateChunk != nullptr)
        {
            delete[] fStateChunk;
            fStateChunk = nullptr;
        }

        fStateMap.clear();
    }
#endif

    intptr_t vst_dispatcher(const int32_t opcode, const int32_t index, const intptr_t value, void* const ptr, const float opt)
    {
        int32_t ret = 0;

        switch (opcode)
        {
#if DISTRHO_PLUGIN_WANT_PROGRAMS
        case effSetProgram:
            if (value >= 0 && value < static_cast<intptr_t>(fPlugin.getProgramCount()))
            {
                fCurProgram = value;
                fPlugin.setProgram(fCurProgram);

#if DISTRHO_PLUGIN_HAS_UI
                if (fVstUi != nullptr)
                    fVstUi->setProgramFromPlugin(fCurProgram);
#endif

                ret = 1;
            }
            break;

        case effGetProgram:
            ret = fCurProgram;
            break;

        //case effSetProgramName:
        // unsupported
        //    break;

        case effGetProgramName:
            if (ptr != nullptr && fCurProgram >= 0 && fCurProgram < static_cast<int32_t>(fPlugin.getProgramCount()))
            {
                DISTRHO::strncpy((char*)ptr, fPlugin.getProgramName(fCurProgram), 24);
                ret = 1;
            }
            break;
#endif

        case effGetParamDisplay:
            if (ptr != nullptr && index < static_cast<int32_t>(fPlugin.getParameterCount()))
            {
                char* buf = (char*)ptr;
                std::snprintf((char*)ptr, 8, "%f", fPlugin.getParameterValue(index));
                buf[8] = '\0';
                ret = 1;
            }
            break;

        case effSetSampleRate:
            fPlugin.setSampleRate(opt, true);
            break;

        case effSetBlockSize:
            fPlugin.setBufferSize(value, true);
            break;

        case effMainsChanged:
            if (value != 0)
            {
                fPlugin.activate();
#if DISTRHO_PLUGIN_IS_SYNTH
                fMidiEventCount = 0;
#endif
            }
            else
            {
                fPlugin.deactivate();
            }
            break;

#if DISTRHO_PLUGIN_HAS_UI
        case effEditGetRect:
            if (fVstUi != nullptr)
            {
                fVstRect.right  = fVstUi->getWidth();
                fVstRect.bottom = fVstUi->getHeight();
            }
            else
            {
                d_lastUiSampleRate = fAudioMaster(fEffect, audioMasterGetSampleRate, 0, 0, nullptr, 0.0f);
                UIExporter tmpUI(nullptr, 0, nullptr, nullptr, nullptr, nullptr, nullptr);
                fVstRect.right  = tmpUI.getWidth();
                fVstRect.bottom = tmpUI.getHeight();
            }
            *(ERect**)ptr = &fVstRect;
            ret = 1;
            break;

        case effEditOpen:
            if (fVstUi == nullptr)
            {
# if DISTRHO_OS_MAC && ! defined(__LP64__)
                if ((fEffect->dispatcher(fEffect, effCanDo, 0, 0, (void*)"hasCockosViewAsConfig", 0.0f) & 0xffff0000) != 0xbeef0000)
                    return 0;
# endif

                d_lastUiSampleRate = fAudioMaster(fEffect, audioMasterGetSampleRate, 0, 0, nullptr, 0.0f);

                fVstUi = new UIVst(fAudioMaster, fEffect, &fPlugin, this, (intptr_t)ptr);

# if DISTRHO_PLUGIN_WANT_PROGRAMS
                if (fCurProgram >= 0)
                    fVstUi->setProgramFromPlugin(fCurProgram);
# endif
                for (uint32_t i=0, count = fPlugin.getParameterCount(); i < count; ++i)
                    fVstUi->setParameterValueFromPlugin(i, fPlugin.getParameterValue(i));

                fVstUi->idle();

#if DISTRHO_PLUGIN_WANT_STATE
                if (fStateMap.size() > 0)
                {
                    for (auto it = fStateMap.begin(), end = fStateMap.end(); it != end; ++it)
                    {
                        const d_string& key   = it->first;
                        const d_string& value = it->second;

                        fVstUi->setStateFromPlugin((const char*)key, (const char*)value);
                    }

                    fVstUi->idle();
                }
#endif
                ret = 1;
            }
            break;

        case effEditClose:
            if (fVstUi != nullptr)
            {
                delete fVstUi;
                fVstUi = nullptr;
                ret = 1;
            }
            break;

        case effEditIdle:
        case effIdle:
            if (fVstUi != nullptr)
                fVstUi->idle();
            break;
#endif

#if DISTRHO_PLUGIN_WANT_STATE
        case effGetChunk:
            if (ptr == nullptr)
                return 0;

            if (fStateChunk != nullptr)
                delete[] fStateChunk;

            if (fStateMap.size() == 0)
            {
                fStateChunk    = new char[1];
                fStateChunk[0] = '\0';
                ret = 1;
            }
            else
            {
                std::string tmpStr;

                for (auto it = fStateMap.begin(), end = fStateMap.end(); it != end; ++it)
                {
                    const d_string& key   = it->first;
                    const d_string& value = it->second;

                    tmpStr += (const char*)key;
                    tmpStr += "\xff";
                    tmpStr += (const char*)value;
                    tmpStr += "\xff";
                }

                const size_t size(tmpStr.size());
                fStateChunk = new char[size];
                std::memcpy(fStateChunk, tmpStr.c_str(), size*sizeof(char));

                for (size_t i=0; i < size; ++i)
                {
                    if (fStateChunk[i] == '\xff')
                        fStateChunk[i] = '\0';
                }

                ret = size;
            }

            *(void**)ptr = fStateChunk;
            break;

        case effSetChunk:
            if (value <= 0)
                return 0;
            if (value == 1)
                return 1;

            if (const char* const state = (const char*)ptr)
            {
                const size_t stateSize  = value;
                const char*  stateKey   = state;
                const char*  stateValue = nullptr;

                for (size_t i=0; i < stateSize; ++i)
                {
                    // find next null char
                    if (state[i] != '\0')
                        continue;

                    // found, set value
                    stateValue = &state[i+1];
                    setSharedState(stateKey, stateValue);

                    if (fVstUi != nullptr)
                        fVstUi->setStateFromPlugin(stateKey, stateValue);

                    // increment text position
                    i += std::strlen(stateValue) + 2;

                    // check if end of data
                    if (i >= stateSize)
                        break;

                    // get next key
                    stateKey = &state[i];
                }

                ret = 1;
            }

            break;
#endif

#if DISTRHO_PLUGIN_IS_SYNTH
        case effProcessEvents:
            if (const VstEvents* const events = (const VstEvents*)ptr)
            {
                if (events->numEvents == 0)
                    break;

                for (int i=0, count=events->numEvents; i < count; ++i)
                {
                    const VstMidiEvent* const vstMidiEvent((const VstMidiEvent*)events->events[i]);

                    if (vstMidiEvent == nullptr)
                        break;
                    if (vstMidiEvent->type != kVstMidiType)
                        continue;
                    if (fMidiEventCount >= kMaxMidiEvents)
                        break;

                    MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
                    midiEvent.frame  = vstMidiEvent->deltaFrames;
                    midiEvent.size   = 3;
                    std::memcpy(midiEvent.buf, vstMidiEvent->midiData, 3*sizeof(uint8_t));
                }
            }
            break;
#endif

        case effCanBeAutomated:
            if (index < static_cast<int32_t>(fPlugin.getParameterCount()))
            {
                const uint32_t hints(fPlugin.getParameterHints(index));

                // must be automable, and not output
                if ((hints & PARAMETER_IS_AUTOMABLE) != 0 && (hints & PARAMETER_IS_OUTPUT) == 0)
                    ret = 1;
            }
            break;

#if (DISTRHO_PLUGIN_IS_SYNTH || DISTRHO_PLUGIN_WANT_TIMEPOS)
        case effCanDo:
            if (const char* const canDo = (const char*)ptr)
            {
# if DISTRHO_PLUGIN_IS_SYNTH
                if (std::strcmp(canDo, "receiveVstEvents") == 0)
                    return 1;
                if (std::strcmp(canDo, "receiveVstMidiEvent") == 0)
                    return 1;
# endif
# if DISTRHO_PLUGIN_WANT_TIMEPOS
                if (std::strcmp(canDo, "receiveVstTimeInfo") == 0)
                    return 1;
# endif
            }
            break;
#endif

        //case effStartProcess:
        //case effStopProcess:
        // unused
        //    break;
        }

        return ret;
    }

    float vst_getParameter(const int32_t index)
    {
        const ParameterRanges& ranges(fPlugin.getParameterRanges(index));
        return ranges.getNormalizedValue(fPlugin.getParameterValue(index));
    }

    void vst_setParameter(const int32_t index, const float value)
    {
        const ParameterRanges& ranges(fPlugin.getParameterRanges(index));
        const float realValue(ranges.getUnnormalizedValue(value));
        fPlugin.setParameterValue(index, realValue);

#if DISTRHO_PLUGIN_HAS_UI
        if (fVstUi != nullptr)
            fVstUi->setParameterValueFromPlugin(index, realValue);
#endif
    }

    void vst_processReplacing(float** const inputs, float** const outputs, const int32_t sampleFrames)
    {
#if DISTRHO_PLUGIN_WANT_TIMEPOS
        if (const VstTimeInfo* const timeInfo = (const VstTimeInfo*)fEffect->dispatcher(fEffect, audioMasterGetTime, 0, kVstTempoValid, nullptr, 0.0f))
        {
            fTimePos.playing = (timeInfo->flags & kVstTransportPlaying);
            fTimePos.frame   = timeInfo->samplePos;

            // TODO: BBT
            // timeInfo->tempo etc

            fPlugin.setTimePos(fTimePos);
        }
#endif

#if DISTRHO_PLUGIN_IS_SYNTH
        fPlugin.run(inputs, outputs, sampleFrames, fMidiEvents, fMidiEventCount);
        fMidiEventCount = 0;
#else
        fPlugin.run(inputs, outputs, sampleFrames);
#endif
    }

    // -------------------------------------------------------------------

    friend class UIVst;

private:
    // VST stuff
    const audioMasterCallback fAudioMaster;
    AEffect* const fEffect;

    // Plugin
    PluginExporter fPlugin;

#if DISTRHO_PLUGIN_WANT_PROGRAMS
    // Current state
    int32_t fCurProgram;
#endif

    // Temporary data
#if DISTRHO_PLUGIN_IS_SYNTH
    uint32_t  fMidiEventCount;
    MidiEvent fMidiEvents[kMaxMidiEvents];
#endif
#if DISTRHO_PLUGIN_WANT_TIMEPOS
    TimePos fTimePos;
#endif

    // UI stuff
#if DISTRHO_PLUGIN_HAS_UI
    UIVst* fVstUi;
    ERect  fVstRect;
#endif

#if DISTRHO_PLUGIN_WANT_STATE
    char*     fStateChunk;
    StringMap fStateMap;

    void setSharedState(const char* const newKey, const char* const newValue) override
    {
        fPlugin.setState(newKey, newValue);

        // check if key already exists
        for (auto it = fStateMap.begin(), end = fStateMap.end(); it != end; ++it)
        {
            const d_string& key = it->first;

            if (key == newKey)
            {
                it->second = newValue;
                return;
            }
        }

        // add a new one then
        d_string d_key(newKey);
        fStateMap[d_key] = newValue;
    }
#endif
};

// -----------------------------------------------------------------------

#ifdef VESTIGE_HEADER
# define handlePtr   ((PluginVst*)effect->ptr2)
# define validEffect effect != nullptr && effect->ptr2 != nullptr
#else
# define handlePtr   ((PluginVst*)effect->resvd2)
# define validEffect effect != nullptr && effect->resvd2 != 0
#endif

static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt)
{
    // first internal init
    bool doInternalInit = (opcode == -1729 && index == 0xdead && value == 0xf00d);

    if (doInternalInit)
    {
        // set valid but dummy values
        d_lastBufferSize = 512;
        d_lastSampleRate = 44100.0;
    }

    // Create dummy plugin to get data from
    static PluginExporter plugin;

    if (doInternalInit)
    {
        // unset
        d_lastBufferSize = 0;
        d_lastSampleRate = 0.0;

        *(PluginExporter**)ptr = &plugin;
        return 0;
    }

    // handle base opcodes
    switch (opcode)
    {
    case effOpen:
#ifdef VESTIGE_HEADER
        if (effect != nullptr && effect->ptr3 != nullptr)
        {
            audioMasterCallback audioMaster = (audioMasterCallback)effect->ptr3;
#else
        if (effect != nullptr && effect->object != nullptr)
        {
            audioMasterCallback audioMaster = (audioMasterCallback)effect->object;
#endif
            d_lastBufferSize = audioMaster(effect, audioMasterGetBlockSize, 0, 0, nullptr, 0.0f);
            d_lastSampleRate = audioMaster(effect, audioMasterGetSampleRate, 0, 0, nullptr, 0.0f);
            PluginVst* const plugin(new PluginVst(audioMaster, effect));
#ifdef VESTIGE_HEADER
            effect->ptr2 = plugin;
#else
            effect->resvd2 = (intptr_t)plugin;
#endif
            return 1;
        }
        return 0;

    case effClose:
        if (validEffect)
        {
#ifdef VESTIGE_HEADER
            delete (PluginVst*)effect->ptr2;
            effect->ptr2 = nullptr;
            effect->ptr3 = nullptr;
#else
            delete (PluginVst*)effect->resvd2;
            effect->resvd2 = 0;
            effect->object = nullptr;
#endif
            delete effect;
            return 1;
        }
        return 0;

    case effGetParamLabel:
        if (ptr != nullptr && index < static_cast<int32_t>(plugin.getParameterCount()))
        {
            DISTRHO::strncpy((char*)ptr, plugin.getParameterUnit(index), 8);
            return 1;
        }
        return 0;

    case effGetParamName:
        if (ptr != nullptr && index < static_cast<int32_t>(plugin.getParameterCount()))
        {
            DISTRHO::strncpy((char*)ptr, plugin.getParameterName(index), 8);
            return 1;
        }
        return 0;

#if DISTRHO_PLUGIN_WANT_PROGRAMS
    case effGetProgramNameIndexed:
        if (ptr != nullptr && index < static_cast<int32_t>(plugin.getProgramCount()))
        {
            DISTRHO::strncpy((char*)ptr, plugin.getProgramName(index), 24);
            return 1;
        }
        return 0;
#endif

    case effGetPlugCategory:
#if DISTRHO_PLUGIN_IS_SYNTH
        return kPlugCategSynth;
#else
        return kPlugCategEffect;
#endif

    case effGetEffectName:
        if (ptr != nullptr)
        {
            DISTRHO::strncpy((char*)ptr, plugin.getName(), 64);
            return 1;
        }
        return 0;

    case effGetVendorString:
        if (ptr != nullptr)
        {
            DISTRHO::strncpy((char*)ptr, plugin.getMaker(), 64);
            return 1;
        }
        return 0;

    case effGetProductString:
        if (ptr != nullptr)
        {
            DISTRHO::strncpy((char*)ptr, plugin.getLabel(), 32);
            return 1;
        }
        return 0;

    case effGetVendorVersion:
        return plugin.getVersion();

    case effGetVstVersion:
        return kVstVersion;
    };

    // handle advanced opcodes
    if (validEffect)
        return handlePtr->vst_dispatcher(opcode, index, value, ptr, opt);

    return 0;
}

static float vst_getParameterCallback(AEffect* effect, int32_t index)
{
    if (validEffect)
        return handlePtr->vst_getParameter(index);
    return 0.0f;
}

static void vst_setParameterCallback(AEffect* effect, int32_t index, float value)
{
    if (validEffect)
        handlePtr->vst_setParameter(index, value);
}

static void vst_processCallback(AEffect* effect, float** inputs, float** outputs, int32_t sampleFrames)
{
    if (validEffect)
        handlePtr->vst_processReplacing(inputs, outputs, sampleFrames);
}

static void vst_processReplacingCallback(AEffect* effect, float** inputs, float** outputs, int32_t sampleFrames)
{
    if (validEffect)
        handlePtr->vst_processReplacing(inputs, outputs, sampleFrames);
}

#undef handlePtr

// -----------------------------------------------------------------------

END_NAMESPACE_DISTRHO

DISTRHO_PLUGIN_EXPORT
const AEffect* VSTPluginMain(audioMasterCallback audioMaster)
{
    USE_NAMESPACE_DISTRHO

    // old version
    if (audioMaster(nullptr, audioMasterVersion, 0, 0, nullptr, 0.0f) == 0)
        return nullptr;

    // first internal init
    PluginExporter* plugin = nullptr;
    vst_dispatcherCallback(nullptr, -1729, 0xdead, 0xf00d, &plugin, 0.0f);

    AEffect* const effect(new AEffect);
    std::memset(effect, 0, sizeof(AEffect));

    // vst fields
    effect->magic    = kEffectMagic;
    effect->uniqueID = plugin->getUniqueId();
#ifdef VESTIGE_HEADER
    *(int32_t*)&effect->unknown1 = plugin->getVersion();
#else
    effect->version  = plugin->getVersion();
#endif

    // plugin fields
    effect->numParams   = plugin->getParameterCount();
#if DISTRHO_PLUGIN_WANT_PROGRAMS
    effect->numPrograms = plugin->getProgramCount();
#endif
    effect->numInputs   = DISTRHO_PLUGIN_NUM_INPUTS;
    effect->numOutputs  = DISTRHO_PLUGIN_NUM_OUTPUTS;

    // plugin flags
    effect->flags |= effFlagsCanReplacing;
#if DISTRHO_PLUGIN_IS_SYNTH
    effect->flags |= effFlagsIsSynth;
#endif
#if DISTRHO_PLUGIN_HAS_UI
    effect->flags |= effFlagsHasEditor;
#endif
#if DISTRHO_PLUGIN_WANT_STATE
    effect->flags |= effFlagsProgramChunks;
#endif

    // static calls
    effect->dispatcher   = vst_dispatcherCallback;
    effect->process      = vst_processCallback;
    effect->getParameter = vst_getParameterCallback;
    effect->setParameter = vst_setParameterCallback;
    effect->processReplacing = vst_processReplacingCallback;

    // pointers
#ifdef VESTIGE_HEADER
    effect->ptr3   = (void*)audioMaster;
#else
    effect->object = (void*)audioMaster;
#endif

    return effect;
}
