/*
 * DISTRHO Plugin Toolkit (DPT)
 * Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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 Lesser General Public License for more details.
 *
 * For a full copy of the license see the LGPL.txt file
 */

#include "DistrhoPluginInternal.hpp"

#if DISTRHO_PLUGIN_HAS_UI
# include "DistrhoUIInternal.hpp"
# ifdef DISTRHO_UI_QT4
#  include <QtGui/QApplication>
# endif
#endif

#ifndef __cdecl
# define __cdecl
#endif

#define VST_FORCE_DEPRECATED 0
#include <pluginterfaces/vst2.x/aeffectx.h>

START_NAMESPACE_DISTRHO

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

#if DISTRHO_PLUGIN_HAS_UI

# ifdef DISTRHO_UI_QT4
static int    qargc = 0;
static char** qargv = nullptr;

class QStaticScopedAppInit
{
public:
    QStaticScopedAppInit()
    {
    }

    static void idle()
    {
        if (sApp != nullptr)
            sApp->processEvents();
    }

    static void addOne()
    {
        if (sCount++ != 0)
            return;

        sApp = qApp ? qApp : new QApplication(qargc, qargv, true);
        sApp->setQuitOnLastWindowClosed(false);
    }

    static void removeOne()
    {
        if (sCount-- != 1)
            return;

        assert(sApp != nullptr);

        sApp->processEvents();
        //sApp->quit();
        //sApp->processEvents();
        //delete sApp;
        //sApp = nullptr;
    }

private:
    static QApplication* sApp;
    static uint32_t      sCount;
};

QApplication* QStaticScopedAppInit::sApp   = nullptr;
uint32_t      QStaticScopedAppInit::sCount = 0;
# endif

class UIVst
{
public:
    UIVst(audioMasterCallback audioMaster, AEffect* effect, PluginInternal* plugin, intptr_t winId)
        : kAudioMaster(audioMaster),
          kEffect(effect),
          kPlugin(plugin),
          fUi(this, winId, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, uiResizeCallback),
          fParameterChecks(nullptr),
          fParameterValues(nullptr)
    {
        uint32_t paramCount = plugin->parameterCount();

        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

#if DISTRHO_PLUGIN_IS_SYNTH
        fMidiEventCount = 0;
#endif
    }

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

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

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

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

        for (uint32_t i=0, count = kPlugin->parameterCount(); i < count; i++)
        {
            if (fParameterChecks[i])
            {
                fParameterChecks[i] = false;

                fUi.parameterChanged(i, fParameterValues[i]);
            }
        }

#if DISTRHO_PLUGIN_IS_SYNTH
        // TODO - notes
#endif

        fUi.idle();
    }

    int16_t getWidth()
    {
        return fUi.width();
    }

    int16_t getHeight()
    {
        return fUi.height();
    }

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

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

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

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

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

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

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

    void setParameterValue(uint32_t index, float realValue)
    {
        const ParameterRanges& ranges = kPlugin->parameterRanges(index);
        float perValue = ranges.normalizeValue(realValue);

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

    void setState(const char* key, const char* value)
    {
#if DISTRHO_PLUGIN_WANT_STATE
        kPlugin->setState(key, value);
#endif
    }

    void sendNote(bool onOff, uint8_t channel, uint8_t note, uint8_t velocity)
    {
#if DISTRHO_PLUGIN_IS_SYNTH
        // TODO
#endif
    }

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

private:
    // Vst stuff
    audioMasterCallback const kAudioMaster;
    AEffect* const kEffect;
    PluginInternal* const kPlugin;

    // Plugin UI
    UIInternal fUi;

    // Temporary data
    bool*  fParameterChecks;
    float* fParameterValues;

#if DISTRHO_PLUGIN_WANT_PROGRAMS
    int32_t fNextProgram;
#endif
#if DISTRHO_PLUGIN_IS_SYNTH
    uint32_t  fMidiEventCount;
    MidiEvent fMidiEvents[MAX_MIDI_EVENTS];
#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, bool onOff, uint8_t channel, uint8_t note, uint8_t velocity)
    {
        handlePtr->sendNote(onOff, channel, note, velocity);
    }

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

    #undef handlePtr
};
#endif

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

class PluginVst
{
public:
    PluginVst(audioMasterCallback audioMaster, AEffect* effect)
        : kAudioMaster(audioMaster),
          kEffect(effect)
    {
#if DISTRHO_PLUGIN_HAS_UI
        fVstUi          = nullptr;
        fVstRect.top    = 0;
        fVstRect.left   = 0;
        fVstRect.bottom = 0;
        fVstRect.right  = 0;
#endif

#if DISTRHO_PLUGIN_WANT_PROGRAMS
        fCurProgram = -1;
#endif

#if DISTRHO_PLUGIN_IS_SYNTH
        fMidiEventCount = 0;
#endif
    }

    ~PluginVst()
    {
    }

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

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

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

                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.programCount()))
            {
                std::strncpy((char*)ptr, fPlugin.programName(fCurProgram), kVstMaxProgNameLen);
                ret = 1;
            }
            break;
#endif

        case effGetParamDisplay:
            if (ptr != nullptr && index < static_cast<int32_t>(fPlugin.parameterCount()))
            {
                snprintf((char*)ptr, kVstMaxParamStrLen, "%f", fPlugin.parameterValue(index));
                ret = 1;
            }
            break;

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

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

        case effMainsChanged:
            if (value == 0)
                fPlugin.deactivate();
            else
                fPlugin.activate();
            break;

#if DISTRHO_PLUGIN_HAS_UI
        case effEditGetRect:
            createUiIfNeeded(0);
            *(ERect**)ptr = &fVstRect;
            ret = 1;
            break;

        case effEditOpen:
            createUiIfNeeded((intptr_t)ptr);
            ret = 1;
            break;

        case effEditClose:
            if (fVstUi != nullptr)
            {
                delete fVstUi;
                fVstUi = nullptr;
# ifdef DISTRHO_UI_QT4
                QStaticScopedAppInit::idle();
# endif
                ret = 1;
            }
            break;

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

#if DISTRHO_PLUGIN_WANT_STATE
        case effGetChunk:
        case effSetChunk:
            // TODO
            break;
#endif

#if DISTRHO_PLUGIN_IS_SYNTH
        case effProcessEvents:
            if (ptr)
            {
                //VstEvents* events = (VstEvents*)ptr;
                // TODO
            }
            break;
#endif

        case effCanBeAutomated:
            if (index < static_cast<int32_t>(fPlugin.parameterCount()))
            {
                uint32_t hints = fPlugin.parameterHints(index);

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

        case effCanDo:
            // TODO
            break;

        case effStartProcess:
        case effStopProcess:
            break;
        }

        return ret;
    }

    float vst_getParameter(int32_t index)
    {
        const ParameterRanges& ranges = fPlugin.parameterRanges(index);
        return ranges.normalizeValue(fPlugin.parameterValue(index));
    }

    void vst_setParameter(int32_t index, float value)
    {
        const ParameterRanges& ranges = fPlugin.parameterRanges(index);
        float realValue = ranges.unnormalizeValue(value);
        fPlugin.setParameterValue(index, realValue);

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

    void vst_processReplacing(float** inputs, float** outputs, int32_t sampleFrames)
    {
#if DISTRHO_PLUGIN_IS_SYNTH
        fPlugin.run(inputs, outputs, sampleFrames, fMidiEventCount, fMidiEvents);

        // TODO - send notes to UI
#else
        fPlugin.run(inputs, outputs, sampleFrames, 0, nullptr);
#endif
    }

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

private:
    // VST stuff
    audioMasterCallback const kAudioMaster;
    AEffect* const kEffect;

    // Plugin
    PluginInternal fPlugin;

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

#if DISTRHO_PLUGIN_WANT_PROGRAMS
    int32_t fCurProgram;
#endif
#if DISTRHO_PLUGIN_IS_SYNTH
    uint32_t  fMidiEventCount;
    MidiEvent fMidiEvents[MAX_MIDI_EVENTS];
#endif

#if DISTRHO_PLUGIN_HAS_UI
    void createUiIfNeeded(intptr_t ptr)
    {
        if (fVstUi != nullptr)
            return;

        d_lastUiSampleRate = kAudioMaster(kEffect, audioMasterGetSampleRate, 0, 0, nullptr, 0.0f);

        if (ptr == 0)
        {
            // fake temporary window, just to set ui size
            UIVst tmpUi(kAudioMaster, kEffect, &fPlugin, ptr);
            fVstRect.right  = tmpUi.getWidth();
            fVstRect.bottom = tmpUi.getHeight();
        }
        else
        {
            // real parented ui
            fVstUi = new UIVst(kAudioMaster, kEffect, &fPlugin, ptr);
            fVstRect.right  = fVstUi->getWidth();
            fVstRect.bottom = fVstUi->getHeight();

# if DISTRHO_PLUGIN_WANT_PROGRAMS
            if (fCurProgram >= 0)
                fVstUi->setProgramFromPlugin(fCurProgram);
# endif

            for (uint32_t i=0, count = fPlugin.parameterCount(); i < count; i++)
                fVstUi->setParameterValueFromPlugin(i, fPlugin.parameterValue(i));
        }
    }
#endif
};

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

#define handlePtr ((PluginVst*)effect->object)

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 == -1 && 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 PluginInternal plugin;

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

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

    // handle base opcodes
    switch (opcode)
    {
    case effOpen:
        if (effect != nullptr && effect->object == nullptr)
        {
            audioMasterCallback audioMaster = (audioMasterCallback)effect->user;
            d_lastBufferSize = audioMaster(effect, audioMasterGetBlockSize, 0, 0, nullptr, 0.0f);
            d_lastSampleRate = audioMaster(effect, audioMasterGetSampleRate, 0, 0, nullptr, 0.0f);
            effect->object   = new PluginVst(audioMaster, effect);
#ifdef DISTRHO_UI_QT4
            QStaticScopedAppInit::addOne();
#endif
            return 1;
        }
        return 0;

    case effClose:
        if (effect != nullptr && effect->object != nullptr)
        {
            delete (PluginVst*)effect->object;
            effect->object = nullptr;
            delete effect;
#ifdef DISTRHO_UI_QT4
            QStaticScopedAppInit::removeOne();
#endif
            return 1;
        }
        return 0;

    case effGetParamLabel:
        if (ptr != nullptr && index < static_cast<int32_t>(plugin.parameterCount()))
        {
            std::strncpy((char*)ptr, plugin.parameterUnit(index), kVstMaxParamStrLen);
            return 1;
        }
        return 0;

    case effGetParamName:
        if (ptr != nullptr && index < static_cast<int32_t>(plugin.parameterCount()))
        {
            std::strncpy((char*)ptr, plugin.parameterName(index), kVstMaxParamStrLen);
            return 1;
        }
        return 0;

#if DISTRHO_PLUGIN_WANT_PROGRAMS
    case effGetProgramNameIndexed:
        if (ptr != nullptr && index < static_cast<int32_t>(plugin.parameterCount()))
        {
            std::strncpy((char*)ptr, plugin.programName(index), kVstMaxProgNameLen);
            return 1;
        }
        return 0;
#endif

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

    case effGetEffectName:
        if (ptr != nullptr)
        {
            std::strncpy((char*)ptr, plugin.name(), kVstMaxProductStrLen);
            return 1;
        }
        return 0;

    case effGetVendorString:
        if (ptr != nullptr)
        {
            std::strncpy((char*)ptr, plugin.maker(), kVstMaxVendorStrLen);
            return 1;
        }
        return 0;

    case effGetProductString:
        if (ptr != nullptr)
        {
            std::strncpy((char*)ptr, plugin.label(), kVstMaxEffectNameLen);
            return 1;
        }
        return 0;

    case effGetVendorVersion:
        return plugin.version();

    case effGetVstVersion:
        return kVstVersion;
    };

    // handle object opcodes
    if (effect != nullptr && effect->object != nullptr)
        return handlePtr->vst_dispatcher(opcode, index, value, ptr, opt);

    return 0;
}

static float vst_getParameterCallback(AEffect* effect, int32_t index)
{
    if (effect != nullptr && effect->object != nullptr)
        return handlePtr->vst_getParameter(index);
    return 0.0f;
}

static void vst_setParameterCallback(AEffect* effect, int32_t index, float value)
{
    if (effect != nullptr && effect->object != nullptr)
        handlePtr->vst_setParameter(index, value);
}

static void vst_processCallback(AEffect* effect, float** inputs, float** outputs, int32_t sampleFrames)
{
    if (effect != nullptr && effect->object != nullptr)
        handlePtr->vst_processReplacing(inputs, outputs, sampleFrames);
}

static void vst_processReplacingCallback(AEffect* effect, float** inputs, float** outputs, int32_t sampleFrames)
{
    if (effect != nullptr && effect->object != nullptr)
        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;

    PluginInternal* plugin = nullptr;
    vst_dispatcherCallback(nullptr, -1, 0xdead, 0xf00d, &plugin, 0.0f);

    AEffect* effect = new AEffect;
    memset(effect, 0, sizeof(AEffect));

    // vst fields
    effect->magic    = kEffectMagic;
    effect->uniqueID = plugin->uniqueId();
    effect->version  = plugin->version();

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

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

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

    // pointers
    effect->object = nullptr;
    effect->user   = (void*)audioMaster;

    return effect;
}

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