/*
 * Carla UI bridge code
 * Copyright (C) 2011-2012 Filipe Coelho <falktx@falktx.com>
 *
 * 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 2 of the License, or
 * 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.
 *
 * For a full copy of the GNU General Public License see the COPYING file
 */

#ifdef BRIDGE_LV2

#include "carla_bridge_client.h"
#include "carla_lv2.h"
#include "carla_midi.h"
#include "rtmempool/rtmempool.h"

#include <vector>
#include <QtCore/QDir>

CARLA_BRIDGE_START_NAMESPACE

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

// fake values
uint32_t bufferSize = 512;
double   sampleRate = 44100.0;

// static max values
const unsigned int MAX_EVENT_BUFFER = 8192; // 0x2000

// feature ids
const uint32_t lv2_feature_id_bufsize_bounded  =  0;
const uint32_t lv2_feature_id_bufsize_fixed    =  1;
const uint32_t lv2_feature_id_bufsize_powerof2 =  2;
const uint32_t lv2_feature_id_event            =  3;
const uint32_t lv2_feature_id_logs             =  4;
const uint32_t lv2_feature_id_options          =  5;
const uint32_t lv2_feature_id_programs         =  6;
const uint32_t lv2_feature_id_rtmempool        =  7;
const uint32_t lv2_feature_id_state_make_path  =  8;
const uint32_t lv2_feature_id_state_map_path   =  9;
const uint32_t lv2_feature_id_strict_bounds    = 10;
const uint32_t lv2_feature_id_uri_map          = 11;
const uint32_t lv2_feature_id_urid_map         = 12;
const uint32_t lv2_feature_id_urid_unmap       = 13;
const uint32_t lv2_feature_id_ui_parent        = 14;
const uint32_t lv2_feature_id_ui_port_map      = 15;
const uint32_t lv2_feature_id_ui_resize        = 16;
const uint32_t lv2_feature_count               = 17;

// pre-set uri[d] map ids
const uint32_t CARLA_URI_MAP_ID_NULL                = 0;
const uint32_t CARLA_URI_MAP_ID_ATOM_CHUNK          = 1;
const uint32_t CARLA_URI_MAP_ID_ATOM_DOUBLE         = 2;
const uint32_t CARLA_URI_MAP_ID_ATOM_INT            = 3;
const uint32_t CARLA_URI_MAP_ID_ATOM_PATH           = 4;
const uint32_t CARLA_URI_MAP_ID_ATOM_SEQUENCE       = 5;
const uint32_t CARLA_URI_MAP_ID_ATOM_STRING         = 6;
const uint32_t CARLA_URI_MAP_ID_ATOM_WORKER         = 7;
const uint32_t CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM  = 8;
const uint32_t CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT = 9;
const uint32_t CARLA_URI_MAP_ID_BUF_MAX_LENGTH      = 10;
const uint32_t CARLA_URI_MAP_ID_BUF_MIN_LENGTH      = 11;
const uint32_t CARLA_URI_MAP_ID_BUF_SEQUENCE_SIZE   = 12;
const uint32_t CARLA_URI_MAP_ID_LOG_ERROR           = 13;
const uint32_t CARLA_URI_MAP_ID_LOG_NOTE            = 14;
const uint32_t CARLA_URI_MAP_ID_LOG_TRACE           = 15;
const uint32_t CARLA_URI_MAP_ID_LOG_WARNING         = 16;
const uint32_t CARLA_URI_MAP_ID_MIDI_EVENT          = 17;
const uint32_t CARLA_URI_MAP_ID_PARAM_SAMPLE_RATE   = 18;
const uint32_t CARLA_URI_MAP_ID_COUNT               = 19;

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

struct Lv2PluginOptions {
    uint32_t eventSize;
    uint32_t bufferSize;
    double   sampleRate;
    LV2_Options_Option oNull;
    LV2_Options_Option oMaxBlockLenth;
    LV2_Options_Option oMinBlockLenth;
    LV2_Options_Option oSequenceSize;
    LV2_Options_Option oSampleRate;

    Lv2PluginOptions()
        : eventSize(MAX_EVENT_BUFFER),
          bufferSize(0),
          sampleRate(0.0) {}
};

Lv2PluginOptions lv2Options;

class CarlaLv2Client : public CarlaClient
{
public:
    CarlaLv2Client(CarlaToolkit* const toolkit)
        : CarlaClient(toolkit)
    {
        handle = nullptr;
        widget = nullptr;
        descriptor = nullptr;

        rdf_descriptor = nullptr;
        rdf_ui_descriptor = nullptr;

        programs = nullptr;

#ifdef BRIDGE_LV2_X11
        m_resizable = false;
#else
        m_resizable = true;
#endif

        for (uint32_t i=0; i < CARLA_URI_MAP_ID_COUNT; i++)
            customURIDs.push_back(nullptr);

        for (uint32_t i=0; i < lv2_feature_count+1; i++)
            features[i] = nullptr;

        // -----------------------------------------------------------------
        // initialize options

        lv2Options.bufferSize = bufferSize;
        lv2Options.sampleRate = sampleRate;

        lv2Options.oNull.key   = CARLA_URI_MAP_ID_NULL;
        lv2Options.oNull.size  = 0;
        lv2Options.oNull.type  = CARLA_URI_MAP_ID_NULL;
        lv2Options.oNull.value = nullptr;

        lv2Options.oMaxBlockLenth.key   = CARLA_URI_MAP_ID_BUF_MAX_LENGTH;
        lv2Options.oMaxBlockLenth.size  = sizeof(uint32_t);
        lv2Options.oMaxBlockLenth.type  = CARLA_URI_MAP_ID_ATOM_INT;
        lv2Options.oMaxBlockLenth.value = &lv2Options.bufferSize;

        lv2Options.oMinBlockLenth.key   = CARLA_URI_MAP_ID_BUF_MIN_LENGTH;
        lv2Options.oMinBlockLenth.size  = sizeof(uint32_t);
        lv2Options.oMinBlockLenth.type  = CARLA_URI_MAP_ID_ATOM_INT;
        lv2Options.oMinBlockLenth.value = &lv2Options.bufferSize;

        lv2Options.oSequenceSize.key   = CARLA_URI_MAP_ID_BUF_SEQUENCE_SIZE;
        lv2Options.oSequenceSize.size  = sizeof(uint32_t);
        lv2Options.oSequenceSize.type  = CARLA_URI_MAP_ID_ATOM_INT;
        lv2Options.oSequenceSize.value = &lv2Options.eventSize;

        lv2Options.oSampleRate.key     = CARLA_URI_MAP_ID_PARAM_SAMPLE_RATE;
        lv2Options.oSampleRate.size    = sizeof(double);
        lv2Options.oSampleRate.type    = CARLA_URI_MAP_ID_ATOM_DOUBLE;
        lv2Options.oSampleRate.value   = &lv2Options.sampleRate;

        // -----------------------------------------------------------------
        // initialize features

        LV2_Event_Feature* const eventFt = new LV2_Event_Feature;
        eventFt->callback_data           = this;
        eventFt->lv2_event_ref           = carla_lv2_event_ref;
        eventFt->lv2_event_unref         = carla_lv2_event_unref;

        LV2_Log_Log* const logFt         = new LV2_Log_Log;
        logFt->handle                    = this;
        logFt->printf                    = carla_lv2_log_printf;
        logFt->vprintf                   = carla_lv2_log_vprintf;

        LV2_Programs_Host* const programsFt        = new LV2_Programs_Host;
        programsFt->handle                         = this;
        programsFt->program_changed                = carla_lv2_program_changed;

        LV2_RtMemPool_Pool* const rtMemPoolFt      = new LV2_RtMemPool_Pool;
        rtmempool_allocator_init(rtMemPoolFt);

        LV2_State_Make_Path* const stateMakePathFt = new LV2_State_Make_Path;
        stateMakePathFt->handle                    = this;
        stateMakePathFt->path                      = carla_lv2_state_make_path;

        LV2_State_Map_Path* const stateMapPathFt   = new LV2_State_Map_Path;
        stateMapPathFt->handle                     = this;
        stateMapPathFt->abstract_path              = carla_lv2_state_map_abstract_path;
        stateMapPathFt->absolute_path              = carla_lv2_state_map_absolute_path;

        LV2_URI_Map_Feature* const uriMapFt = new LV2_URI_Map_Feature;
        uriMapFt->callback_data             = this;
        uriMapFt->uri_to_id                 = carla_lv2_uri_to_id;

        LV2_URID_Map* const uridMapFt       = new LV2_URID_Map;
        uridMapFt->handle                   = this;
        uridMapFt->map                      = carla_lv2_urid_map;

        LV2_URID_Unmap* const uridUnmapFt   = new LV2_URID_Unmap;
        uridUnmapFt->handle                 = this;
        uridUnmapFt->unmap                  = carla_lv2_urid_unmap;

        LV2UI_Port_Map* const uiPortMapFt   = new LV2UI_Port_Map;
        uiPortMapFt->handle                 = this;
        uiPortMapFt->port_index             = carla_lv2_ui_port_map;

        LV2UI_Resize* const uiResizeFt      = new LV2UI_Resize;
        uiResizeFt->handle                  = this;
        uiResizeFt->ui_resize               = carla_lv2_ui_resize;

        LV2_Options_Option* const optionsFt = new LV2_Options_Option [5];
        optionsFt[0] = lv2Options.oMaxBlockLenth;
        optionsFt[1] = lv2Options.oMinBlockLenth;
        optionsFt[2] = lv2Options.oSequenceSize;
        optionsFt[3] = lv2Options.oSampleRate;
        optionsFt[4] = lv2Options.oNull;

        features[lv2_feature_id_bufsize_bounded]        = new LV2_Feature;
        features[lv2_feature_id_bufsize_bounded]->URI   = LV2_BUF_SIZE__boundedBlockLength;
        features[lv2_feature_id_bufsize_bounded]->data  = nullptr;

        features[lv2_feature_id_bufsize_fixed]          = new LV2_Feature;
        features[lv2_feature_id_bufsize_fixed]->URI     = LV2_BUF_SIZE__fixedBlockLength;
        features[lv2_feature_id_bufsize_fixed]->data    = nullptr;

        features[lv2_feature_id_bufsize_powerof2]       = new LV2_Feature;
        features[lv2_feature_id_bufsize_powerof2]->URI  = LV2_BUF_SIZE__powerOf2BlockLength;
        features[lv2_feature_id_bufsize_powerof2]->data = nullptr;

        features[lv2_feature_id_event]          = new LV2_Feature;
        features[lv2_feature_id_event]->URI     = LV2_EVENT_URI;
        features[lv2_feature_id_event]->data    = eventFt;

        features[lv2_feature_id_logs]           = new LV2_Feature;
        features[lv2_feature_id_logs]->URI      = LV2_LOG__log;
        features[lv2_feature_id_logs]->data     = logFt;

        features[lv2_feature_id_options]        = new LV2_Feature;
        features[lv2_feature_id_options]->URI   = LV2_OPTIONS__options;
        features[lv2_feature_id_options]->data  = optionsFt;

        features[lv2_feature_id_programs]       = new LV2_Feature;
        features[lv2_feature_id_programs]->URI  = LV2_PROGRAMS__Host;
        features[lv2_feature_id_programs]->data = programsFt;

        features[lv2_feature_id_rtmempool]       = new LV2_Feature;
        features[lv2_feature_id_rtmempool]->URI  = LV2_RTSAFE_MEMORY_POOL__Pool;
        features[lv2_feature_id_rtmempool]->data = rtMemPoolFt;

        features[lv2_feature_id_state_make_path]       = new LV2_Feature;
        features[lv2_feature_id_state_make_path]->URI  = LV2_STATE__makePath;
        features[lv2_feature_id_state_make_path]->data = stateMakePathFt;

        features[lv2_feature_id_state_map_path]        = new LV2_Feature;
        features[lv2_feature_id_state_map_path]->URI   = LV2_STATE__mapPath;
        features[lv2_feature_id_state_map_path]->data  = stateMapPathFt;

        features[lv2_feature_id_strict_bounds]         = new LV2_Feature;
        features[lv2_feature_id_strict_bounds]->URI    = LV2_PORT_PROPS__supportsStrictBounds;
        features[lv2_feature_id_strict_bounds]->data   = nullptr;

        features[lv2_feature_id_uri_map]          = new LV2_Feature;
        features[lv2_feature_id_uri_map]->URI     = LV2_URI_MAP_URI;
        features[lv2_feature_id_uri_map]->data    = uriMapFt;

        features[lv2_feature_id_urid_map]         = new LV2_Feature;
        features[lv2_feature_id_urid_map]->URI    = LV2_URID__map;
        features[lv2_feature_id_urid_map]->data   = uridMapFt;

        features[lv2_feature_id_urid_unmap]       = new LV2_Feature;
        features[lv2_feature_id_urid_unmap]->URI  = LV2_URID__unmap;
        features[lv2_feature_id_urid_unmap]->data = uridUnmapFt;

        features[lv2_feature_id_ui_parent]         = new LV2_Feature;
        features[lv2_feature_id_ui_parent]->URI    = LV2_UI__parent;
#ifdef BRIDGE_LV2_X11
        features[lv2_feature_id_ui_parent]->data   = getContainerId();
#else
        features[lv2_feature_id_ui_parent]->data   = nullptr;
#endif

        features[lv2_feature_id_ui_port_map]           = new LV2_Feature;
        features[lv2_feature_id_ui_port_map]->URI      = LV2_UI__portMap;
        features[lv2_feature_id_ui_port_map]->data     = uiPortMapFt;

        features[lv2_feature_id_ui_resize]             = new LV2_Feature;
        features[lv2_feature_id_ui_resize]->URI        = LV2_UI__resize;
        features[lv2_feature_id_ui_resize]->data       = uiResizeFt;
    }

    ~CarlaLv2Client()
    {
        if (rdf_descriptor)
            delete rdf_descriptor;

        const LV2_Options_Option* const options = (const LV2_Options_Option*)features[lv2_feature_id_options]->data;
        delete[] options;

        delete (LV2_Event_Feature*)features[lv2_feature_id_event]->data;
        delete (LV2_Log_Log*)features[lv2_feature_id_logs]->data;
        delete (LV2_Programs_Host*)features[lv2_feature_id_programs]->data;
        delete (LV2_RtMemPool_Pool*)features[lv2_feature_id_rtmempool]->data;
        delete (LV2_State_Make_Path*)features[lv2_feature_id_state_make_path]->data;
        delete (LV2_State_Map_Path*)features[lv2_feature_id_state_map_path]->data;
        delete (LV2_URI_Map_Feature*)features[lv2_feature_id_uri_map]->data;
        delete (LV2_URID_Map*)features[lv2_feature_id_urid_map]->data;
        delete (LV2_URID_Unmap*)features[lv2_feature_id_urid_unmap]->data;
        delete (LV2UI_Port_Map*)features[lv2_feature_id_ui_port_map]->data;
        delete (LV2UI_Resize*)features[lv2_feature_id_ui_resize]->data;

        for (uint32_t i=0; i < lv2_feature_count; i++)
        {
            if (features[i])
                delete features[i];
        }

        for (size_t i=0; i < customURIDs.size(); i++)
        {
            if (customURIDs[i])
                free((void*)customURIDs[i]);
        }

        customURIDs.clear();
    }

    // ---------------------------------------------------------------------
    // ui initialization

    bool init(const char* pluginURI, const char* uiURI)
    {
        // -----------------------------------------------------------------
        // init

        CarlaClient::init(pluginURI, uiURI);

        // -----------------------------------------------------------------
        // get plugin from lv2_rdf (lilv)

        Lv2World.init();
        rdf_descriptor = lv2_rdf_new(pluginURI);

        if (! rdf_descriptor)
            return false;

        // -----------------------------------------------------------------
        // find requested UI

        for (uint32_t i=0; i < rdf_descriptor->UICount; i++)
        {
            if (strcmp(rdf_descriptor->UIs[i].URI, uiURI) == 0)
            {
                rdf_ui_descriptor = &rdf_descriptor->UIs[i];
                break;
            }
        }

        if (! rdf_ui_descriptor)
            return false;

        // -----------------------------------------------------------------
        // open DLL

        if (! libOpen(rdf_ui_descriptor->Binary))
            return false;

        // -----------------------------------------------------------------
        // get DLL main entry

        const LV2UI_DescriptorFunction ui_descFn = (LV2UI_DescriptorFunction)libSymbol("lv2ui_descriptor");

        if (! ui_descFn)
            return false;

        // -----------------------------------------------------------
        // get descriptor that matches URI

        uint32_t i = 0;
        while ((descriptor = ui_descFn(i++)))
        {
            if (strcmp(descriptor->URI, uiURI) == 0)
                break;
        }

        if (! descriptor)
            return false;

        // -----------------------------------------------------------
        // initialize UI

        handle = descriptor->instantiate(descriptor, pluginURI, rdf_ui_descriptor->Bundle, carla_lv2_ui_write_function, this, &widget, features);

        if (! handle)
            return false;

        // -----------------------------------------------------------
        // check if not resizable

#ifndef BRIDGE_LV2_X11
        for (uint32_t i=0; i < rdf_ui_descriptor->FeatureCount; i++)
        {
            if (strcmp(rdf_ui_descriptor->Features[i].URI, LV2_UI__fixedSize) == 0 || strcmp(rdf_ui_descriptor->Features[i].URI, LV2_UI__noUserResize) == 0)
            {
                m_resizable = false;
                break;
            }
        }
#endif

        // -----------------------------------------------------------
        // check for known extensions

        for (uint32_t i=0; descriptor->extension_data && i < rdf_ui_descriptor->ExtensionCount; i++)
        {
            if (strcmp(rdf_ui_descriptor->Extensions[i], LV2_PROGRAMS__UIInterface) == 0)
            {
                programs = (LV2_Programs_UI_Interface*)descriptor->extension_data(LV2_PROGRAMS__UIInterface);

                if (programs && ! programs->select_program)
                    // invalid
                    programs = nullptr;

                break;
            }
        }

        return true;
    }

    void close()
    {
        CarlaClient::close();

        if (handle && descriptor && descriptor->cleanup)
            descriptor->cleanup(handle);

        libClose();
    }

    // ---------------------------------------------------------------------
    // ui management

    void* getWidget() const
    {
        return widget;
    }

    bool isResizable() const
    {
        return m_resizable;
    }

    bool needsReparent() const
    {
#ifdef BRIDGE_LV2_X11
        return true;
#else
        return false;
#endif
    }

    // ---------------------------------------------------------------------
    // processing

    void setParameter(const int32_t rindex, const double value)
    {
        CARLA_ASSERT(handle && descriptor);

        if (handle && descriptor && descriptor->port_event)
        {
            float fvalue = value;
            descriptor->port_event(handle, rindex, sizeof(float), 0, &fvalue);
        }
    }

    void setProgram(const uint32_t)
    {
    }

    void setMidiProgram(const uint32_t bank, const uint32_t program)
    {
        CARLA_ASSERT(handle);

        if (handle && programs)
            programs->select_program(handle, bank, program);
    }

    void noteOn(const uint8_t channel, const uint8_t note, const uint8_t velo)
    {
        CARLA_ASSERT(handle && descriptor);

        if (handle && descriptor && descriptor->port_event)
        {
            LV2_Atom_MidiEvent midiEv;
            midiEv.event.time.frames = 0;
            midiEv.event.body.type   = CARLA_URI_MAP_ID_MIDI_EVENT;
            midiEv.event.body.size   = 3;
            midiEv.data[0] = MIDI_STATUS_NOTE_ON + channel;
            midiEv.data[1] = note;
            midiEv.data[2] = velo;

            descriptor->port_event(handle, 0, 3, CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM, &midiEv);
        }
    }

    void noteOff(const uint8_t channel, const uint8_t note)
    {
        CARLA_ASSERT(handle && descriptor);

        if (handle && descriptor && descriptor->port_event)
        {
            LV2_Atom_MidiEvent midiEv;
            midiEv.event.time.frames = 0;
            midiEv.event.body.type   = CARLA_URI_MAP_ID_MIDI_EVENT;
            midiEv.event.body.size   = 3;
            midiEv.data[0] = MIDI_STATUS_NOTE_OFF + channel;
            midiEv.data[1] = note;
            midiEv.data[2] = 0;

            descriptor->port_event(handle, 0, 3, CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM, &midiEv);
        }
    }

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

    uint32_t getCustomURID(const char* const uri)
    {
        qDebug("CarlaLv2Client::getCustomURID(%s)", uri);
        CARLA_ASSERT(uri);

        if (! uri)
            return CARLA_URI_MAP_ID_NULL;

        for (size_t i=0; i < customURIDs.size(); i++)
        {
            if (customURIDs[i] && strcmp(customURIDs[i], uri) == 0)
                return i;
        }

        customURIDs.push_back(strdup(uri));

        return customURIDs.size()-1;
    }

    const char* getCustomURIString(const LV2_URID urid) const
    {
        qDebug("CarlaLv2Client::getCustomURIString(%i)", urid);
        CARLA_ASSERT(urid > CARLA_URI_MAP_ID_NULL);

        if (urid == CARLA_URI_MAP_ID_NULL)
            return nullptr;
        if (urid < customURIDs.size())
            return customURIDs[urid];

        return nullptr;
    }

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

    void handleTransferAtom(const int32_t portIndex, const LV2_Atom* const atom)
    {
        qDebug("CarlaLv2Client::handleTransferEvent(%i, %p)", portIndex, atom);
        CARLA_ASSERT(portIndex >= 0);
        CARLA_ASSERT(atom);

        if (handle && descriptor && descriptor->port_event)
            descriptor->port_event(handle, portIndex, atom->size, CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM, atom);
    }

    void handleTransferEvent(const int32_t portIndex, const LV2_Atom* const atom)
    {
        qDebug("CarlaLv2Client::handleTransferEvent(%i, %p)", portIndex, atom);
        CARLA_ASSERT(portIndex >= 0);
        CARLA_ASSERT(atom);

        if (handle && descriptor && descriptor->port_event)
            descriptor->port_event(handle, portIndex, atom->size, CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT, atom);
#if 0
        if (handle && descriptor && descriptor->port_event)
        {
            LV2_URID_Map* const URID_Map = (LV2_URID_Map*)features[lv2_feature_id_urid_map]->data;
            const LV2_URID uridPatchSet  = getCustomURID(LV2_PATCH__Set);
            const LV2_URID uridPatchBody = getCustomURID(LV2_PATCH__body);

            Sratom*   sratom = sratom_new(URID_Map);
            SerdChunk chunk  = { nullptr, 0 };

            LV2_Atom_Forge forge;
            lv2_atom_forge_init(&forge, URID_Map);
            lv2_atom_forge_set_sink(&forge, sratom_forge_sink, sratom_forge_deref, &chunk);

            LV2_Atom_Forge_Frame refFrame, bodyFrame;
            LV2_Atom_Forge_Ref   ref = lv2_atom_forge_blank(&forge, &refFrame, 1, uridPatchSet);

            lv2_atom_forge_property_head(&forge, uridPatchBody, CARLA_URI_MAP_ID_NULL);
            lv2_atom_forge_blank(&forge, &bodyFrame, 2, CARLA_URI_MAP_ID_NULL);

            //lv2_atom_forge_property_head(&forge, getCustomURID(key), CARLA_URI_MAP_ID_NULL);

            if (strcmp(type, "string") == 0)
                lv2_atom_forge_string(&forge, value, strlen(value));
            else if (strcmp(type, "path") == 0)
                lv2_atom_forge_path(&forge, value, strlen(value));
            else if (strcmp(type, "chunk") == 0)
                lv2_atom_forge_literal(&forge, value, strlen(value), CARLA_URI_MAP_ID_ATOM_CHUNK, CARLA_URI_MAP_ID_NULL);
            //else
            //    lv2_atom_forge_literal(&forge, value, strlen(value), getCustomURID(key), CARLA_URI_MAP_ID_NULL);

            lv2_atom_forge_pop(&forge, &bodyFrame);
            lv2_atom_forge_pop(&forge, &refFrame);

            const LV2_Atom* const atom = lv2_atom_forge_deref(&forge, ref);
            descriptor->port_event(handle, 0, atom->size, CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT, atom);

            free((void*)chunk.buf);
            sratom_free(sratom);
        }
#endif
    }

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

    void handleProgramChanged(int32_t /*index*/)
    {
        sendOscConfigure("reloadprograms", "");
    }

    uint32_t handleUiPortMap(const char* const symbol)
    {
        CARLA_ASSERT(symbol);

        if (! symbol)
            return LV2UI_INVALID_PORT_INDEX;

        for (uint32_t i=0; i < rdf_descriptor->PortCount; i++)
        {
            if (strcmp(rdf_descriptor->Ports[i].Symbol, symbol) == 0)
                return i;
        }

        return LV2UI_INVALID_PORT_INDEX;
    }


    int handleUiResize(int width, int height)
    {
        CARLA_ASSERT(width > 0);
        CARLA_ASSERT(height > 0);

        if (width <= 0 || height <= 0)
            return 1;

        toolkitResize(width, height);

        return 0;
    }


    void handleUiWrite(uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
    {
        if (format == 0)
        {
            CARLA_ASSERT(buffer);
            CARLA_ASSERT(bufferSize == sizeof(float));

            if (bufferSize != sizeof(float))
                return;

            float value = *(float*)buffer;
            sendOscControl(portIndex, value);
        }
        else if (format == CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM)
        {
            CARLA_ASSERT(buffer);
            const LV2_Atom* const atom = (const LV2_Atom*)buffer;

            QByteArray chunk((const char*)buffer, bufferSize);
            sendOscLv2TransferAtom(portIndex, getCustomURIString(atom->type), chunk.toBase64().constData());
        }
        else if (format == CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT)
        {
            CARLA_ASSERT(buffer);
            const LV2_Atom* const atom = (const LV2_Atom*)buffer;

            QByteArray chunk((const char*)buffer, bufferSize);
            sendOscLv2TransferEvent(portIndex, getCustomURIString(atom->type), chunk.toBase64().constData());
        }
    }

    // ----------------- Event Feature ---------------------------------------------------

    static uint32_t carla_lv2_event_ref(const LV2_Event_Callback_Data callback_data, LV2_Event* const event)
    {
        qDebug("CarlaLv2Client::carla_lv2_event_ref(%p, %p)", callback_data, event);
        CARLA_ASSERT(callback_data);
        CARLA_ASSERT(event);

        return 0;
    }

    static uint32_t carla_lv2_event_unref(const LV2_Event_Callback_Data callback_data, LV2_Event* const event)
    {
        qDebug("CarlaLv2Client::carla_lv2_event_unref(%p, %p)", callback_data, event);
        CARLA_ASSERT(callback_data);
        CARLA_ASSERT(event);

        return 0;
    }

    // ----------------- Logs Feature ----------------------------------------------------

    static int carla_lv2_log_printf(const LV2_Log_Handle handle, const LV2_URID type, const char* const fmt, ...)
    {
        qDebug("CarlaLv2Client::carla_lv2_log_printf(%p, %i, %s, ...)", handle, type, fmt);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(type > 0);

#ifndef DEBUG
        if (type == CARLA_URI_MAP_ID_LOG_TRACE)
            return 0;
#endif

        va_list args;
        va_start(args, fmt);
        const int ret = carla_lv2_log_vprintf(handle, type, fmt, args);
        va_end(args);

        return ret;
    }

    static int carla_lv2_log_vprintf(const LV2_Log_Handle handle, const LV2_URID type, const char* const fmt, va_list ap)
    {
        qDebug("CarlaLv2Client::carla_lv2_log_vprintf(%p, %i, %s, ...)", handle, type, fmt);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(type > 0);

#ifndef DEBUG
        if (type == CARLA_URI_MAP_ID_LOG_TRACE)
            return 0;
#endif

        char buf[8196];
        vsprintf(buf, fmt, ap);

        if (*buf == 0)
            return 0;

        switch (type)
        {
        case CARLA_URI_MAP_ID_LOG_ERROR:
            qCritical("%s", buf);
            break;
        case CARLA_URI_MAP_ID_LOG_NOTE:
            printf("%s\n", buf);
            break;
        case CARLA_URI_MAP_ID_LOG_TRACE:
            qDebug("%s", buf);
            break;
        case CARLA_URI_MAP_ID_LOG_WARNING:
            qWarning("%s", buf);
            break;
        default:
            break;
        }

        return strlen(buf);
    }

    // ----------------- Programs Feature ------------------------------------------------

    static void carla_lv2_program_changed(const LV2_Programs_Handle handle, const int32_t index)
    {
        qDebug("CarlaLv2Client::carla_lv2_program_changed(%p, %i)", handle, index);
        CARLA_ASSERT(handle);

        if (! handle)
            return;

        CarlaLv2Client* const client = (CarlaLv2Client*)handle;
        client->handleProgramChanged(index);
    }

    // ----------------- State Feature ---------------------------------------------------

    static char* carla_lv2_state_make_path(const LV2_State_Make_Path_Handle handle, const char* const path)
    {
        qDebug("CarlaLv2Client::carla_lv2_state_make_path(%p, %p)", handle, path);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(path);

        if (! path)
            return nullptr;

        QDir dir;
        dir.mkpath(path);
        return strdup(path);
    }

    static char* carla_lv2_state_map_abstract_path(const LV2_State_Map_Path_Handle handle, const char* const absolute_path)
    {
        qDebug("CarlaLv2Client::carla_lv2_state_map_abstract_path(%p, %p)", handle, absolute_path);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(absolute_path);

        if (! absolute_path)
            return nullptr;

        QDir dir(absolute_path);
        return strdup(dir.canonicalPath().toUtf8().constData());
    }

    static char* carla_lv2_state_map_absolute_path(const LV2_State_Map_Path_Handle handle, const char* const abstract_path)
    {
        qDebug("CarlaLv2Client::carla_lv2_state_map_absolute_path(%p, %p)", handle, abstract_path);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(abstract_path);

        if (! abstract_path)
            return nullptr;

        QDir dir(abstract_path);
        return strdup(dir.absolutePath().toUtf8().constData());
    }

    // ----------------- URI-Map Feature ---------------------------------------

    static uint32_t carla_lv2_uri_to_id(const LV2_URI_Map_Callback_Data data, const char* const map, const char* const uri)
    {
        qDebug("CarlaLv2Client::carla_lv2_uri_to_id(%p, %s, %s)", data, map, uri);
        return carla_lv2_urid_map((LV2_URID_Map_Handle*)data, uri);
    }

    // ----------------- URID Feature ------------------------------------------

    static LV2_URID carla_lv2_urid_map(const LV2_URID_Map_Handle handle, const char* const uri)
    {
        qDebug("CarlaLv2Client::carla_lv2_urid_map(%p, %s)", handle, uri);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(uri);

        if (! uri)
            return CARLA_URI_MAP_ID_NULL;

        // Atom types
        if (strcmp(uri, LV2_ATOM__Chunk) == 0)
            return CARLA_URI_MAP_ID_ATOM_CHUNK;
        if (strcmp(uri, LV2_ATOM__Double) == 0)
            return CARLA_URI_MAP_ID_ATOM_DOUBLE;
        if (strcmp(uri, LV2_ATOM__Int) == 0)
            return CARLA_URI_MAP_ID_ATOM_INT;
        if (strcmp(uri, LV2_ATOM__Path) == 0)
            return CARLA_URI_MAP_ID_ATOM_PATH;
        if (strcmp(uri, LV2_ATOM__Sequence) == 0)
            return CARLA_URI_MAP_ID_ATOM_SEQUENCE;
        if (strcmp(uri, LV2_ATOM__String) == 0)
            return CARLA_URI_MAP_ID_ATOM_STRING;
        if (strcmp(uri, LV2_ATOM__atomTransfer) == 0)
            return CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM;
        if (strcmp(uri, LV2_ATOM__eventTransfer) == 0)
            return CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT;

        // BufSize types
        if (strcmp(uri, LV2_BUF_SIZE__maxBlockLength) == 0)
            return CARLA_URI_MAP_ID_BUF_MAX_LENGTH;
        if (strcmp(uri, LV2_BUF_SIZE__minBlockLength) == 0)
            return CARLA_URI_MAP_ID_BUF_MIN_LENGTH;
        if (strcmp(uri, LV2_BUF_SIZE__sequenceSize) == 0)
            return CARLA_URI_MAP_ID_BUF_SEQUENCE_SIZE;

        // Log types
        if (strcmp(uri, LV2_LOG__Error) == 0)
            return CARLA_URI_MAP_ID_LOG_ERROR;
        if (strcmp(uri, LV2_LOG__Note) == 0)
            return CARLA_URI_MAP_ID_LOG_NOTE;
        if (strcmp(uri, LV2_LOG__Trace) == 0)
            return CARLA_URI_MAP_ID_LOG_TRACE;
        if (strcmp(uri, LV2_LOG__Warning) == 0)
            return CARLA_URI_MAP_ID_LOG_WARNING;

        // Others
        if (strcmp(uri, LV2_MIDI__MidiEvent) == 0)
            return CARLA_URI_MAP_ID_MIDI_EVENT;
        if (strcmp(uri, LV2_PARAMETERS__sampleRate) == 0)
            return CARLA_URI_MAP_ID_PARAM_SAMPLE_RATE;

        if (! handle)
            return CARLA_URI_MAP_ID_NULL;

        // Custom types
        CarlaLv2Client* const client = (CarlaLv2Client*)handle;
        return client->getCustomURID(uri);
    }

    static const char* carla_lv2_urid_unmap(const LV2_URID_Map_Handle handle, const LV2_URID urid)
    {
        qDebug("CarlaLv2Client::carla_lv2_urid_unmap(%p, %i)", handle, urid);
        CARLA_ASSERT(handle);
        CARLA_ASSERT(urid > CARLA_URI_MAP_ID_NULL);

        if (urid == CARLA_URI_MAP_ID_NULL)
            return nullptr;

        // Atom types
        if (urid == CARLA_URI_MAP_ID_ATOM_CHUNK)
            return LV2_ATOM__Chunk;
        if (urid == CARLA_URI_MAP_ID_ATOM_DOUBLE)
            return LV2_ATOM__Double;
        if (urid == CARLA_URI_MAP_ID_ATOM_INT)
            return LV2_ATOM__Int;
        if (urid == CARLA_URI_MAP_ID_ATOM_PATH)
            return LV2_ATOM__Path;
        if (urid == CARLA_URI_MAP_ID_ATOM_SEQUENCE)
            return LV2_ATOM__Sequence;
        if (urid == CARLA_URI_MAP_ID_ATOM_STRING)
            return LV2_ATOM__String;
        if (urid == CARLA_URI_MAP_ID_ATOM_TRANSFER_ATOM)
            return LV2_ATOM__atomTransfer;
        if (urid == CARLA_URI_MAP_ID_ATOM_TRANSFER_EVENT)
            return LV2_ATOM__eventTransfer;

        // BufSize types
        if (urid == CARLA_URI_MAP_ID_BUF_MAX_LENGTH)
            return LV2_BUF_SIZE__maxBlockLength;
        if (urid == CARLA_URI_MAP_ID_BUF_MIN_LENGTH)
            return LV2_BUF_SIZE__minBlockLength;
        if (urid == CARLA_URI_MAP_ID_BUF_SEQUENCE_SIZE)
            return LV2_BUF_SIZE__sequenceSize;

        // Log types
        if (urid == CARLA_URI_MAP_ID_LOG_ERROR)
            return LV2_LOG__Error;
        if (urid == CARLA_URI_MAP_ID_LOG_NOTE)
            return LV2_LOG__Note;
        if (urid == CARLA_URI_MAP_ID_LOG_TRACE)
            return LV2_LOG__Trace;
        if (urid == CARLA_URI_MAP_ID_LOG_WARNING)
            return LV2_LOG__Warning;

        // Others
        if (urid == CARLA_URI_MAP_ID_MIDI_EVENT)
            return LV2_MIDI__MidiEvent;
        if (urid == CARLA_URI_MAP_ID_PARAM_SAMPLE_RATE)
            return LV2_PARAMETERS__sampleRate;

        if (! handle)
            return nullptr;

        // Custom types
        CarlaLv2Client* const client = (CarlaLv2Client*)handle;
        return client->getCustomURIString(urid);
    }

    // ----------------- UI Port-Map Feature ---------------------------------------------

    static uint32_t carla_lv2_ui_port_map(const LV2UI_Feature_Handle handle, const char* const symbol)
    {
        qDebug("CarlaLv2Client::carla_lv2_ui_port_map(%p, %s)", handle, symbol);
        CARLA_ASSERT(handle);

        if (! handle)
            return LV2UI_INVALID_PORT_INDEX;

        CarlaLv2Client* const client = (CarlaLv2Client*)handle;
        return client->handleUiPortMap(symbol);
    }


    // ----------------- UI Resize Feature -------------------------------------

    static int carla_lv2_ui_resize(const LV2UI_Feature_Handle handle, const int width, const int height)
    {
        qDebug("CarlaLv2Client::carla_lv2_ui_resize(%p, %i, %i)", handle, width, height);
        CARLA_ASSERT(handle);

        if (! handle)
            return 1;

        CarlaLv2Client* const client = (CarlaLv2Client*)handle;
        return client->handleUiResize(width, height);
    }

    // ----------------- UI Extension ------------------------------------------

    static void carla_lv2_ui_write_function(const LV2UI_Controller controller, const uint32_t port_index, const uint32_t buffer_size, const uint32_t format, const void* const buffer)
    {
        qDebug("CarlaLv2Client::carla_lv2_ui_write_function(%p, %i, %i, %i, %p)", controller, port_index, buffer_size, format, buffer);
        CARLA_ASSERT(controller);

        if (! controller)
            return;

        CarlaLv2Client* const client = (CarlaLv2Client*)controller;
        client->handleUiWrite(port_index, buffer_size, format, buffer);
    }

private:
    LV2UI_Handle handle;
    LV2UI_Widget widget;
    const LV2UI_Descriptor* descriptor;
    LV2_Feature* features[lv2_feature_count+1];

    const LV2_RDF_Descriptor* rdf_descriptor;
    const LV2_RDF_UI* rdf_ui_descriptor;

    const LV2_Programs_UI_Interface* programs;

    bool m_resizable;
    std::vector<const char*> customURIDs;
};

int CarlaBridgeOsc::handleMsgLv2TransferAtom(CARLA_BRIDGE_OSC_HANDLE_ARGS)
{
    qDebug("CarlaBridgeOsc::handleMsgLv2TransferAtom()");
    CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(3, "iss");

    if (! client)
        return 1;

    const int32_t portIndex   = argv[0]->i;
    const char* const typeStr = (const char*)&argv[1]->s;
    const char* const atomBuf = (const char*)&argv[2]->s;

    QByteArray chunk;
    chunk = QByteArray::fromBase64(atomBuf);

    LV2_Atom* const atom = (LV2_Atom*)chunk.constData();
    CarlaLv2Client* const lv2client = (CarlaLv2Client*)client;

    atom->type = lv2client->getCustomURID(typeStr);
    lv2client->handleTransferAtom(portIndex, atom);

    return 0;
}

int CarlaBridgeOsc::handleMsgLv2TransferEvent(CARLA_BRIDGE_OSC_HANDLE_ARGS)
{
    qDebug("CarlaBridgeOsc::handleMsgLv2TransferEvent()");
    CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(3, "iss");

    if (! client)
        return 1;

    const int32_t portIndex   = argv[0]->i;
    const char* const typeStr = (const char*)&argv[1]->s;
    const char* const atomBuf = (const char*)&argv[2]->s;

    QByteArray chunk;
    chunk = QByteArray::fromBase64(atomBuf);

    LV2_Atom* const atom = (LV2_Atom*)chunk.constData();
    CarlaLv2Client* const lv2client = (CarlaLv2Client*)client;

    atom->type = lv2client->getCustomURID(typeStr);
    lv2client->handleTransferEvent(portIndex, atom);

    return 0;
}

CARLA_BRIDGE_END_NAMESPACE

int main(int argc, char* argv[])
{
    using namespace CarlaBridge;

    if (argc != 5)
    {
        qWarning("usage: %s <osc-url|\"null\"> <plugin-uri> <ui-uri> <ui-title>", argv[0]);
        return 1;
    }

    const char* oscUrl    = argv[1];
    const char* pluginURI = argv[2];
    const char* uiURI     = argv[3];
    const char* uiTitle   = argv[4];

    const bool useOsc = strcmp(oscUrl, "null");

    // try to get sampleRate value
    const char* const sampleRateStr = getenv("CARLA_SAMPLE_RATE");

    if (sampleRateStr)
        sampleRate = atof(sampleRateStr);

    // Init toolkit
    CarlaToolkit* const toolkit = CarlaToolkit::createNew(uiTitle);
    toolkit->init();

    // Init LV2-UI
    CarlaLv2Client client(toolkit);

    // Init OSC
    if (useOsc && ! client.oscInit(oscUrl))
    {
        toolkit->quit();
        delete toolkit;
        return -1;
    }

    // Load UI
    int ret;

    if (client.init(pluginURI, uiURI))
    {
        toolkit->exec(&client, !useOsc);
        ret = 0;
    }
    else
    {
        qCritical("Failed to load LV2 UI");
        ret = 1;
    }

    // Close OSC
    if (useOsc)
    {
        client.oscClose();
    }

    // Close LV2-UI
    client.close();

    // Close toolkit
    toolkit->quit();
    delete toolkit;

    return ret;
}

#endif
