/*
 * Copyright (C) 2026 Linux Studio Plugins Project <https://lsp-plug.in/>
 *           (C) 2026 Vladimir Sadovnikov <sadko4u@gmail.com>
 *
 * This file is part of lsp-plugin-fw
 * Created on: 12 нояб. 2021 г.
 *
 * lsp-plugin-fw 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, either version 3 of the License, or
 * any later version.
 *
 * lsp-plugin-fw 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.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with lsp-plugin-fw. If not, see <https://www.gnu.org/licenses/>.
 */

#ifndef LSP_PLUG_IN_PLUG_FW_WRAP_LV2_EXTENSIONS_H_
#define LSP_PLUG_IN_PLUG_FW_WRAP_LV2_EXTENSIONS_H_

#include <lsp-plug.in/common/alloc.h>
#include <lsp-plug.in/common/debug.h>
#include <lsp-plug.in/plug-fw/core/presets.h>
#include <lsp-plug.in/plug-fw/meta/types.h>
#include <lsp-plug.in/plug-fw/meta/func.h>
#include <lsp-plug.in/plug-fw/plug.h>
#include <lsp-plug.in/plug-fw/version.h>
#include <lsp-plug.in/plug-fw/wrap/lv2/lv2.h>
#include <lsp-plug.in/plug-fw/wrap/lv2/types.h>
#include <lsp-plug.in/stdlib/stdio.h>
#include <lsp-plug.in/stdlib/stdlib.h>
#include <lsp-plug.in/stdlib/string.h>

#pragma pack(push, 1)
typedef struct LV2_Atom_Midi
{
    LV2_Atom    atom;
    uint8_t     body[8];
} LV2_Atom_Midi;
#pragma pack(pop)

#define LV2PORT_MAX_BLOCK_LENGTH        8192

namespace lsp
{
    namespace lv2
    {
        struct Extensions;
        class Wrapper;

        /**
         * Serializable object
         */
        class Serializable
        {
            protected:
                lv2::Extensions        *pExt;
                LV2_URID                urid;

            public:
                explicit Serializable(Extensions *ext)
                {
                    pExt        = ext;
                    urid        = 0;
                }

                virtual ~Serializable()
                {
                }

            public:
                virtual void serialize() {};

                virtual void deserialize(const void *data) {};

            public:
                inline LV2_URID         get_urid() const        { return urid; };
        };

        /**
         * Set of bindings to LV2 extension interfaces.
         */
        struct Extensions
        {
            public:
                LV2_Atom_Forge          forge;

                // Extension interfaces
                LV2_URID_Map           *map;
                LV2_URID_Unmap         *unmap;
                LV2_Worker_Schedule    *sched;
                LV2_Inline_Display     *iDisplay;
                LV2_State_Map_Path     *mapPath;
                LV2_State_Free_Path    *freePath;
                LV2UI_Resize           *ui_resize;
                LV2UI_Touch            *pTouch;
                lv2::Wrapper           *pWrapper;

                LV2UI_Controller        ctl;
                LV2UI_Write_Function    wf;
                ssize_t                 nAtomIn;            // Atom input port identifier
                ssize_t                 nAtomOut;           // Atom output port identifier
                size_t                  nMaxBlockLength;    // Maximum size of audio block passed to plugin
                float                   fUIScaleFactor;     // UI scale factor
                uint8_t                *pBuffer;            // Atom serialization buffer
                size_t                  nBufSize;           // Atom serialization buffer size
                float                   fUIRefreshRate;     // UI refresh rate
                void                   *pParentWindow;      // Parent window handle

                // State interface
                LV2_State_Store_Function    hStore;
                LV2_State_Retrieve_Function hRetrieve;
                LV2_State_Handle            hHandle;

                // Plugin URI and URID
                const char             *uriPlugin;
                const char             *uriTypes;
                const char             *uriKvt;
                LV2_URID                uridPlugin;

                // Miscellaneous URIDs
                LV2_URID                uridAtomTransfer;
                LV2_URID                uridEventTransfer;
                LV2_URID                uridObject;
                LV2_URID                uridBlank;
                LV2_URID                uridStateChanged;
                LV2_URID                uridUINotification;
                LV2_URID                uridConnectUI;
                LV2_URID                uridDisconnectUI;
                LV2_URID                uridPlaySample;
                LV2_URID                uridPlayPositionUpdate;
                LV2_URID                uridDumpState;
                LV2_URID                uridPathType;
                LV2_URID                uridMidiEventType;
                LV2_URID                uridKvtObject;
                LV2_URID                uridKvtType;
                LV2_URID                uridKvtEntryType;
                LV2_URID                uridKvtEntryKey;
                LV2_URID                uridKvtEntryValue;
                LV2_URID                uridKvtEntryFlags;
                LV2_URID                uridKvtPropertyType;
                LV2_URID                uridKvtPropertyValue;
                LV2_URID                uridKvtPropertyFlags;
                LV2_URID                uridBlobType;
                LV2_URID                uridContentType;
                LV2_URID                uridContent;
                LV2_URID                uridTypeUInt;
                LV2_URID                uridTypeULong;

                LV2_URID                uridPatchGet;
                LV2_URID                uridPatchSet;
                LV2_URID                uridPatchMessage;
                LV2_URID                uridPatchProperty;
                LV2_URID                uridPatchValue;

                LV2_URID                uridAtomUrid;
                LV2_URID                uridChunk;
                LV2_URID                uridUpdateRate;

                LV2_URID                uridTimePosition;
                LV2_URID                uridTimeFrame;
                LV2_URID                uridTimeFrameRate;
                LV2_URID                uridTimeSpeed;
                LV2_URID                uridTimeBarBeat;
                LV2_URID                uridTimeBar;
                LV2_URID                uridTimeBeatUnit;
                LV2_URID                uridTimeBeatsPerBar;
                LV2_URID                uridTimeBeatsPerMinute;

                LV2_URID                uridMaxBlockLength;
                LV2_URID                uridScaleFactor;

                // State-related URIDs
                LV2_URID                uridPortData;
                LV2_URID                uridPortDataType;

                // Preset-related URIDs
                LV2_URID                uridPresetState;
                LV2_URID                uridPresetStateType;
                LV2_URID                uridPresetName;
                LV2_URID                uridPresetFlags;
                LV2_URID                uridPresetTab;

                // OSC-related URIDs
                LV2_URID                uridOscBundle;
                LV2_URID                uridOscBundleTimetag;
                LV2_URID                uridOscBundleItems;
                LV2_URID                uridOscMessage;
                LV2_URID                uridOscMessagePath;
                LV2_URID                uridOscMessageArguments;
                LV2_URID                uridOscTimetag;
                LV2_URID                uridOscTimetagIntegral;
                LV2_URID                uridOscTimetagFraction;
                LV2_URID                uridOscNil;
                LV2_URID                uridOscImpulse;
                LV2_URID                uridOscChar;
                LV2_URID                uridOscRgba;
                LV2_URID                uridOscRawPacket;

                // LSP-related URIDs
                LV2_URID                uridMeshType;
                LV2_URID                uridMeshItems;
                LV2_URID                uridMeshDimensions;
                LV2_URID                uridMeshData;
                LV2_URID                uridFrameBufferType;
                LV2_URID                uridFrameBufferRows;        // Number of rows
                LV2_URID                uridFrameBufferCols;        // Number of cols
                LV2_URID                uridFrameBufferFirstRowID;  // First row identifier
                LV2_URID                uridFrameBufferLastRowID;   // Last row identifier
                LV2_URID                uridFrameBufferData;        // Frame buffer row data
                LV2_URID                uridStreamType;             // Stream data type
                LV2_URID                uridStreamDimensions;       // Stream dimensions
                LV2_URID                uridStreamFrame;            // Stream frame
                LV2_URID                uridStreamFrameType;        // Stream frame type
                LV2_URID                uridStreamFrameId;          // Number of frame
                LV2_URID                uridStreamFrameSize;        // Size of frame
                LV2_URID                uridStreamFrameData;        // Frame data

                LV2_URID                uridPlayRequestType;
                LV2_URID                uridPlayRequestFileName;
                LV2_URID                uridPlayRequestPosition;
                LV2_URID                uridPlayRequestRelease;
                LV2_URID                uridPlayPositionType;
                LV2_URID                uridPlayPositionPosition;
                LV2_URID                uridPlayPositionLength;

                LV2_URID                uridShmState;
                LV2_URID                uridShmStateType;
                LV2_URID                uridShmStateItems;
                LV2_URID                uridShmRecordType;
                LV2_URID                uridShmRecordId;
                LV2_URID                uridShmRecordName;
                LV2_URID                uridShmRecordIndex;
                LV2_URID                uridShmRecordMagic;

            public:
                inline Extensions(
                    const LV2_Feature* const* feat,
                    const char *plugin_uri,
                    const char *types_uri,
                    const char *kvt_uri,
                    LV2UI_Controller lv2_ctl,
                    LV2UI_Write_Function lv2_write)
                {
                    map                 = NULL;
                    unmap               = NULL;
                    sched               = NULL;
                    iDisplay            = NULL;
                    mapPath             = NULL;
                    freePath            = NULL;
                    ui_resize           = NULL;
                    pTouch              = NULL;
                    pWrapper            = NULL;

                    ctl                 = lv2_ctl;
                    wf                  = lv2_write;
                    nAtomIn             = -1;
                    nAtomOut            = -1;
                    nMaxBlockLength     = LV2PORT_MAX_BLOCK_LENGTH;
                    fUIScaleFactor      = 1.0f;
                    pBuffer             = NULL;
                    nBufSize            = 0;
                    fUIRefreshRate      = UI_FRAMES_PER_SECOND;
                    pParentWindow       = NULL;

                    // State interface
                    hStore              = NULL;
                    hRetrieve           = NULL;
                    hHandle             = NULL;

                    uriPlugin           = plugin_uri;
                    uriTypes            = types_uri;
                    uriKvt              = kvt_uri;

                    const LV2_Options_Option *opts = NULL;

                    // Scan features
                    if (feat != NULL)
                    {
                        for (size_t i=0; feat[i]; ++i)
                        {
                            const LV2_Feature *f = feat[i];

                            lsp_trace("Host reported extension uri=%sp", f->URI);

                            if (!strcmp(f->URI, LV2_URID__map))
                                map             = reinterpret_cast<LV2_URID_Map *>(f->data);
                            else if (!strcmp(f->URI, LV2_URID__unmap))
                                unmap           = reinterpret_cast<LV2_URID_Unmap *>(f->data);
                            else if (!strcmp(f->URI, LV2_WORKER__schedule))
                                sched           = reinterpret_cast<LV2_Worker_Schedule *>(f->data);
                            else if (!strcmp(f->URI, LV2_UI__parent))
                                pParentWindow   = f->data;
                            else if (!strcmp(f->URI, LV2_UI__resize))
                                ui_resize       = reinterpret_cast<LV2UI_Resize *>(f->data);
                            else if (!strcmp(f->URI, LV2_INLINEDISPLAY__queue_draw))
                                iDisplay        = reinterpret_cast<LV2_Inline_Display *>(f->data);
                            else if (!strcmp(f->URI, LV2_UI__touch))
                                pTouch          = reinterpret_cast<LV2UI_Touch *>(f->data);
                        #if !LSP_LV2_NO_INSTANCE_ACCESS
                            else if (!strcmp(f->URI, LV2_INSTANCE_ACCESS_URI))
                                pWrapper = reinterpret_cast<Wrapper *>(f->data);
                        #endif
                            else if (!strcmp(f->URI, LV2_OPTIONS__options))
                            {
                                lsp_trace("Received options from host");
                                opts = reinterpret_cast<const LV2_Options_Option *>(f->data);
                            }
                        }

                        lsp_trace("Plugin instance wrapper pointer: %p", pWrapper);
                    }

                    // Initialize basic URIDs
                    uridPlugin                  = (map != NULL) ? map->map(map->handle, uriPlugin) : -1;

                    if (map != NULL)
                        lv2_atom_forge_init(&forge, map);
                    else
                        bzero(&forge, sizeof(forge));

                    uridAtomTransfer            = map_uri(LV2_ATOM__atomTransfer);
                    uridEventTransfer           = map_uri(LV2_ATOM__eventTransfer);
                    uridObject                  = forge.Object;
                    uridBlank                   = map_uri(LV2_ATOM__Blank);
                    uridStateChanged            = map_uri(LV2_STATE__StateChanged);
                    uridUINotification          = map_type_legacy("UINotification");
                    uridConnectUI               = map_primitive("ui_connect");
                    uridDisconnectUI            = map_primitive("ui_disconnect");
                    uridPlaySample              = map_primitive("play_sample");
                    uridPlayPositionUpdate      = map_primitive("play_position_update");
                    uridDumpState               = map_primitive("dumpState");
                    uridPathType                = forge.Path;
                    uridMidiEventType           = map_uri(LV2_MIDI__MidiEvent);
                    uridKvtObject               = map_primitive("KVT");
                    uridKvtType                 = map_type_legacy("KVT");
                    uridKvtEntryType            = map_type("KVTEntry");
                    uridKvtEntryKey             = map_field("KVTEntry", "key");
                    uridKvtEntryValue           = map_field("KVTEntry", "value");
                    uridKvtEntryFlags           = map_field("KVTEntry", "flags");
                    uridKvtPropertyType         = map_type_legacy("KVTProperty");
                    uridKvtPropertyValue        = map_field("KVTProperty", "value");
                    uridKvtPropertyFlags        = map_field("KVTProperty", "flags");
                    uridBlobType                = map_type_legacy("Blob");
                    uridContentType             = map_field("Blob", "ContentType");
                    uridContent                 = map_field("Blob", "Content");

                    uridTypeUInt                = map_uri(LV2_ATOM_PREFIX "UInt" );
                    uridTypeULong               = map_uri(LV2_ATOM_PREFIX "ULong" );

                    uridPatchGet                = map_uri(LV2_PATCH__Get);
                    uridPatchSet                = map_uri(LV2_PATCH__Set);
                    uridPatchMessage            = map_uri(LV2_PATCH__Message);
                    uridPatchProperty           = map_uri(LV2_PATCH__property);
                    uridPatchValue              = map_uri(LV2_PATCH__value);
                    uridAtomUrid                = forge.URID;
                    uridChunk                   = forge.Chunk;
                    uridUpdateRate              = map_uri(LV2_UI__updateRate);

                    uridTimePosition            = map_uri(LV2_TIME__Position);
                    uridTimeFrame               = map_uri(LV2_TIME__frame);
                    uridTimeFrameRate           = map_uri(LV2_TIME__framesPerSecond);
                    uridTimeSpeed               = map_uri(LV2_TIME__speed);
                    uridTimeBarBeat             = map_uri(LV2_TIME__barBeat);
                    uridTimeBar                 = map_uri(LV2_TIME__bar);
                    uridTimeBeatUnit            = map_uri(LV2_TIME__beatUnit);
                    uridTimeBeatsPerBar         = map_uri(LV2_TIME__beatsPerBar);
                    uridTimeBeatsPerMinute      = map_uri(LV2_TIME__beatsPerMinute);

                    uridMaxBlockLength          = map_uri(LV2_BUF_SIZE__maxBlockLength);
                    uridScaleFactor             = map_uri(LV2_UI__scaleFactor);

                    // State-related URIDs
                    uridPortData                = map_primitive("port_data");
                    uridPortDataType            = map_type("PortData");

                    // Preset-related URIDs
                    uridPresetState             = map_primitive("preset_state");
                    uridPresetStateType         = map_type("PresetState");
                    uridPresetName              = map_field("PresetState", "name");
                    uridPresetFlags             = map_field("PresetState", "flags");
                    uridPresetTab               = map_field("PresetState", "tab");

                    // OSC-related URIDs
                    uridOscBundle               = map_uri(LV2_OSC__Bundle);
                    uridOscBundleTimetag        = map_uri(LV2_OSC__bundleTimetag);
                    uridOscBundleItems          = map_uri(LV2_OSC__bundleItems);
                    uridOscMessage              = map_uri(LV2_OSC__Message);
                    uridOscMessagePath          = map_uri(LV2_OSC__messagePath);
                    uridOscMessageArguments     = map_uri(LV2_OSC__messageArguments);
                    uridOscTimetag              = map_uri(LV2_OSC__Timetag);
                    uridOscTimetagIntegral      = map_uri(LV2_OSC__timetagIntegral);
                    uridOscTimetagFraction      = map_uri(LV2_OSC__timetagFraction);
                    uridOscNil                  = map_uri(LV2_OSC__Nil);
                    uridOscImpulse              = map_uri(LV2_OSC__Impulse);
                    uridOscChar                 = map_uri(LV2_OSC__Char);
                    uridOscRgba                 = map_uri(LV2_OSC__RGBA);
                    uridOscRawPacket            = map_uri(LV2_OSC__RawPacket);

                    // LSP-related URIDs
                    uridMeshType                = map_type_legacy("Mesh");
                    uridMeshItems               = map_field("Mesh", "items");
                    uridMeshDimensions          = map_field("Mesh", "dimensions");
                    uridMeshData                = map_field("Mesh", "data");

                    uridFrameBufferType         = map_type_legacy("FrameBuffer");
                    uridFrameBufferRows         = map_field("FrameBuffer", "rows");
                    uridFrameBufferCols         = map_field("FrameBuffer", "columns");
                    uridFrameBufferFirstRowID   = map_field("FrameBuffer", "firstRowID");
                    uridFrameBufferLastRowID    = map_field("FrameBuffer", "lastRowID");
                    uridFrameBufferData         = map_field("FrameBuffer", "data");

                    uridStreamType              = map_type_legacy("Stream");
                    uridStreamDimensions        = map_field("Stream", "dimensions");
                    uridStreamFrame             = map_field("Stream", "frame");
                    uridStreamFrameType         = map_type_legacy("StreamFrame");
                    uridStreamFrameId           = map_field("StreamFrame", "id");
                    uridStreamFrameSize         = map_field("StreamFrame", "size");
                    uridStreamFrameData         = map_field("StreamFrame", "data");

                    uridPlayRequestType         = map_type("PlayRequest");
                    uridPlayRequestFileName     = map_field("PlayRequest", "fileName");
                    uridPlayRequestPosition     = map_field("PlayRequest", "position");
                    uridPlayRequestRelease      = map_field("PlayRequest", "release");
                    uridPlayPositionType        = map_type("PlayPosition");
                    uridPlayPositionPosition    = map_field("PlayPosition", "position");
                    uridPlayPositionLength      = map_field("PlayPosition", "length");

                    uridShmState                = map_primitive("shm_state");
                    uridShmStateType            = map_type("ShmState");
                    uridShmStateItems           = map_field("ShmState", "items");
                    uridShmRecordType           = map_type("ShmRecord");
                    uridShmRecordId             = map_field("ShmRecord", "id");
                    uridShmRecordName           = map_field("ShmRecord", "name");
                    uridShmRecordIndex          = map_field("ShmRecord", "index");
                    uridShmRecordMagic          = map_field("ShmRecord", "magic");

                    // Decode passed options if they are present
                    if (opts != NULL)
                    {
                        lsp_trace("Decoding passed options");
                        while ((opts->key != 0) && (opts->value != NULL))
                        {
                            lsp_trace("context = %d (%s), subject=%d, key = %d (%s), size = %d, type=%d (%s), value=%p",
                                    int(opts->context),
                                    (opts->context == LV2_OPTIONS_INSTANCE) ? "instance" :
                                    (opts->context == LV2_OPTIONS_RESOURCE) ? "resource" :
                                    (opts->context == LV2_OPTIONS_BLANK)    ? "blank" :
                                    (opts->context == LV2_OPTIONS_PORT)     ? "port" : "unknown",
                                    int(opts->subject),
                                    int(opts->key), unmap_urid(opts->key),
                                    int(opts->size),
                                    int(opts->type), unmap_urid(opts->type),
                                    opts->value
                            );

                            // Update Rate for the UI
                            if ((opts->context == LV2_OPTIONS_INSTANCE) &&
                                (opts->key == uridUpdateRate))
                            {
                                if ((opts->type == forge.Float) && (opts->size == sizeof(float)))
                                    fUIRefreshRate  = *reinterpret_cast<const float *>(opts->value);
                                else if ((opts->type == forge.Double) && (opts->size == sizeof(double)))
                                    fUIRefreshRate  = *reinterpret_cast<const double *>(opts->value);
                                else if ((opts->type == forge.Int) && (opts->size == sizeof(int32_t)))
                                    fUIRefreshRate  = *reinterpret_cast<const int32_t *>(opts->value);
                                else if ((opts->type == forge.Long) && (opts->size == sizeof(int64_t)))
                                    fUIRefreshRate  = *reinterpret_cast<const int64_t *>(opts->value);
                                if (fUIRefreshRate < 0)
                                    fUIRefreshRate = UI_FRAMES_PER_SECOND;
                                lsp_trace("UI refresh rate has been set to %f", fUIRefreshRate);
                            }

                            if (opts->context == LV2_OPTIONS_INSTANCE)
                            {
                                if (opts->key == uridMaxBlockLength)
                                {
                                    ssize_t blk_len = read_option_int(opts, uridMaxBlockLength, nMaxBlockLength);
                                    if (blk_len > 0)
                                        nMaxBlockLength = blk_len;
                                    lsp_trace("MaxBlockLength has been set to %d", int(nMaxBlockLength));
                                }
                                else if (opts->key == uridScaleFactor)
                                {
                                    float scale = read_option_float(opts, uridScaleFactor, 1.0f);
                                    if (scale > 0.0f)
                                        fUIScaleFactor  = scale;
                                    lsp_trace("UIScaleFactor has been set to %f", fUIScaleFactor);
                                }
                            }

                            if ((opts->context == LV2_OPTIONS_INSTANCE) &&
                                (opts->key == uridMaxBlockLength))
                            {
                                ssize_t blk_len = nMaxBlockLength;
                                if ((opts->type == forge.Int) && (opts->size == sizeof(int32_t)))
                                {
                                    blk_len = *reinterpret_cast<const int32_t *>(opts->value);
                                    lsp_trace("blk_len = %d", int(blk_len));
                                }
                                else if ((opts->type == forge.Long) && (opts->size == sizeof(int64_t)))
                                {
                                    blk_len = *reinterpret_cast<const int64_t *>(opts->value);
                                    lsp_trace("blk_len = %d", int(blk_len));
                                }
                                if (blk_len > 0)
                                    nMaxBlockLength = blk_len;

                            }

                            opts++;
                        }
                    }
                }

                ~Extensions()
                {
                    lsp_trace("destroy");

                    // Drop atom buffer
                    if (pBuffer != NULL)
                    {
                        delete [] pBuffer;
                        pBuffer     = NULL;
                    }
                }

            public:
                inline float ui_refresh_rate() const
                {
                    return fUIRefreshRate;
                }

                inline void *parent_window()
                {
                    return pParentWindow;
                }

                inline bool atom_supported() const
                {
                    return map != NULL;
                }

                inline const char *unmap_urid(LV2_URID urid)
                {
                    return (unmap != NULL) ? unmap->unmap(unmap->handle, urid) : NULL;
                }

                inline lv2::Wrapper *wrapper()
                {
                    return pWrapper;
                }

                inline ssize_t read_option_int(const LV2_Options_Option *opts, LV2_URID urid, ssize_t dfl)
                {
                    if ((opts->type == forge.Int) && (opts->size == sizeof(int32_t)))
                        return *static_cast<const int32_t *>(opts->value);
                    else if ((opts->type == forge.Long) && (opts->size == sizeof(int64_t)))
                        return *static_cast<const int64_t *>(opts->value);
                    return dfl;
                }

                inline float read_option_float(const LV2_Options_Option *opts, LV2_URID urid, float dfl)
                {
                    if ((opts->type == forge.Float) && (opts->size == sizeof(float)))
                        return *static_cast<const float *>(opts->value);
                    else if ((opts->type == forge.Double) && (opts->size == sizeof(double)))
                        return *static_cast<const double *>(opts->value);
                    return dfl;
                }

                inline void write_data(
                        uint32_t         port_index,
                        uint32_t         buffer_size,
                        uint32_t         port_protocol,
                        const void*      buffer)
                {
                    if ((ctl == NULL) || (wf == NULL))
                    {
                        lsp_error("ctl=%p, wf=%p", ctl, wf);
                        return;
                    }
                    wf(ctl, port_index, buffer_size, port_protocol, buffer);
                }

                inline LV2_Atom *forge_object(LV2_Atom_Forge_Frame* frame, LV2_URID id, LV2_URID otype)
                {
                    const LV2_Atom_Object a = {
                        { sizeof(LV2_Atom_Object_Body), uridObject },
                        { id, otype }
                    };
                    return reinterpret_cast<LV2_Atom *>(
                        lv2_atom_forge_push(&forge, frame, lv2_atom_forge_write(&forge, &a, sizeof(a)))
                    );
                }

                inline LV2_Atom_Vector *forge_vector(
                        uint32_t        child_size,
                        uint32_t        child_type,
                        uint32_t        n_elems,
                        const void*     elems)
                {
                    return reinterpret_cast<LV2_Atom_Vector *>(
                        lv2_atom_forge_vector(&forge, child_size, child_type, n_elems, elems)
                    );
                }

                inline LV2_Atom_Forge_Ref forge_sequence_head(LV2_Atom_Forge_Frame* frame, uint32_t unit)
                {
                    return lv2_atom_forge_sequence_head(&forge, frame, unit);
                }

                inline LV2_Atom_Forge_Ref forge_key(LV2_URID key)
                {
                    const uint32_t body[] = { key, 0 };
                    return lv2_atom_forge_write(&forge, body, sizeof(body));
                }

                inline LV2_Atom_Forge_Ref forge_urid(LV2_URID urid)
                {
                    const LV2_Atom_URID a = { { sizeof(LV2_URID), forge.URID }, urid };
                    return lv2_atom_forge_write(&forge, &a, sizeof(LV2_Atom_URID));
                }

                inline LV2_Atom_Forge_Ref forge_path(const char *str)
                {
                    size_t len = ::strlen(str);
                    return lv2_atom_forge_typed_string(&forge, forge.Path, str, len);
                }

                inline LV2_Atom_Forge_Ref forge_string(const char *str)
                {
                    size_t len = ::strlen(str);
                    return lv2_atom_forge_typed_string(&forge, forge.String, str, len);
                }

                inline LV2_Atom_Forge_Ref forge_tuple(LV2_Atom_Forge_Frame *frame)
                {
                    return lv2_atom_forge_tuple(&forge, frame);
                }

                inline void forge_pop(LV2_Atom_Forge_Frame* frame)
                {
                    lv2_atom_forge_pop(&forge, frame);
                }

                inline LV2_Atom_Forge_Ref forge_float(float val)
                {
                    const LV2_Atom_Float a = { { sizeof(float), forge.Float }, val };
                    return lv2_atom_forge_primitive(&forge, &a.atom);
                }

                inline LV2_Atom_Forge_Ref forge_int(int32_t val)
                {
                    const LV2_Atom_Int a    = { { sizeof(int32_t), forge.Int }, val };
                    return lv2_atom_forge_primitive(&forge, &a.atom);
                }

                inline LV2_Atom_Forge_Ref forge_long(int64_t val)
                {
                    const LV2_Atom_Long a = { { sizeof(val), forge.Long }, val };
                    return lv2_atom_forge_primitive(&forge, &a.atom);
                }

                inline LV2_Atom_Forge_Ref forge_bool(int32_t val)
                {
                    const LV2_Atom_Bool a = {{sizeof(int32_t), forge.Bool}, val ? 1 : 0};
                    return lv2_atom_forge_primitive(&forge, &a.atom);
                }

                inline void forge_pad(size_t size)
                {
                    lv2_atom_forge_pad(&forge, size);
                }

                inline LV2_Atom_Forge_Ref forge_raw(const void* data, size_t size)
                {
                    return lv2_atom_forge_raw(&forge, data, size);
                }

                inline LV2_Atom_Forge_Ref forge_primitive(const LV2_Atom *atom)
                {
                    return lv2_atom_forge_primitive(&forge, atom);
                }

                inline void forge_set_buffer(void* buf, size_t size)
                {
                    lv2_atom_forge_set_buffer(&forge, reinterpret_cast<uint8_t *>(buf), size);
                }

                inline LV2_Atom_Forge_Ref forge_frame_time(int64_t frames)
                {
                    return lv2_atom_forge_write(&forge, &frames, sizeof(frames));
                }

                inline void resize_ui(ssize_t width, ssize_t height)
                {
                    if (ui_resize != NULL)
                    {
                        lsp_trace("ui_resize->ui_resize(ui_resize->handle, %d, %d)", int(width), int(height));
                        ui_resize->ui_resize(ui_resize->handle, width, height);
                    }
                    else
                        lsp_trace("ui_resize == NULL");
                }

                inline void touch_ui(uint32_t index, bool grabbed)
                {
                    if (pTouch != NULL)
                    {
                        lsp_trace("UI touch index=%d, grabbed=%s", int(index), (grabbed) ? "true" : "false");
                        pTouch->touch(pTouch->handle, index, grabbed);
                    }
                    else
                        lsp_trace("pTouch == NULL");
                }

                inline LV2_URID map_uri(const char *fmt...) const
                {
                    if (map == NULL)
                        return -1;

                    va_list vl;
                    char tmpbuf[2048];

                    va_start(vl, fmt);
                    vsnprintf(tmpbuf, sizeof(tmpbuf), fmt, vl);
                    va_end(vl);

                    LV2_URID res = map->map(map->handle, tmpbuf);
                    lsp_trace("URID for <%s> is %d (0x%x)", tmpbuf, int(res), int(res));
                    return res;
                }

                inline LV2_URID map_port(const char *id) const
                {
                    return map_uri("%s/ports#%s", uriPlugin, id);
                }

                inline LV2_URID map_type_legacy(const char *type) const
                {
                    return map_uri("%s/types#%s", uriTypes, type);
                }

                inline LV2_URID map_type(const char *type) const
                {
                    return map_uri("%s/%s", uriTypes, type);
                }

                inline LV2_URID map_field(const char *type, const char *id) const
                {
                    return map_uri("%s/%s#%s", uriTypes, type, id);
                }

                inline LV2_URID map_kvt(const char *id) const
                {
                    return map_uri("%s/%s", uriKvt, id);
                }

                inline LV2_URID map_primitive(const char *id) const
                {
                    return map_uri("%s/%s", uriPlugin, id);
                }

                inline void init_state_context(
                    LV2_State_Store_Function    store,
                    LV2_State_Retrieve_Function retrieve,
                    LV2_State_Handle            handle,
                    uint32_t                    flags,
                    const LV2_Feature *const *  features
                )
                {
                    hStore          = store;
                    hRetrieve       = retrieve;
                    hHandle         = handle;

                    for (size_t i=0; features[i]; ++i)
                    {
                        const LV2_Feature *f = features[i];
                        lsp_trace("Host reported state extension uri=%s, data=%p", f->URI, f->data);

                        if (!::strcmp(f->URI, LV2_STATE__mapPath))
                            mapPath = reinterpret_cast<LV2_State_Map_Path *>(f->data);
                        if (!::strcmp(f->URI, LV2_STATE__freePath))
                            freePath = reinterpret_cast<LV2_State_Free_Path *>(f->data);
                    }
                }

                inline void store_value(LV2_URID urid, LV2_URID type, const void *data, size_t size)
                {
                    if ((hStore == NULL) || (hHandle == NULL))
                        return;
                    hStore(hHandle, urid, data, size, type, LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
                }

                inline const void *retrieve_value(LV2_URID urid, uint32_t *type, size_t *size)
                {
                    if ((hRetrieve == NULL) || (hHandle == NULL))
                        return NULL;

                    uint32_t t_flags = 0;
                    *type = 0;
                    *size = 0;

                    lsp_trace("retrieve %d (%s)", urid, unmap_urid(urid));
                    const void *ptr   = hRetrieve(hHandle, urid, size, type, &t_flags);
                    lsp_trace("retrieved ptr = %p, size=%d, type=%d, flags=0x%x", ptr, int(*size), int(*type), int(t_flags));

                    return ptr;
                }

                inline const void *restore_value(LV2_URID urid, LV2_URID type, size_t *size)
                {
                    uint32_t t_type;
                    size_t t_size;
                    const void *ptr = retrieve_value(urid, &t_type, &t_size);
                    if (t_type != type)
                        return NULL;

                    lsp_trace("retrieved type = %d (%s)", int(t_type), unmap_urid(t_type));
                    *size    = t_size;
                    return ptr;
                }

                inline void reset_state_context()
                {
                    hStore          = NULL;
                    hRetrieve       = NULL;
                    hHandle         = NULL;
                    mapPath         = NULL;
                    freePath        = NULL;
                }

                inline bool ui_create_atom_transport(size_t port, size_t buf_size)
                {
                    // Remember IDs of atom ports
                    nAtomOut    = port++;
                    nAtomIn     = port;

                    // Allocate buffer
                    nBufSize    = buf_size;
                    pBuffer     = new uint8_t[nBufSize];
                    if (pBuffer == NULL)
                        return false;

                    lsp_trace("Atom rx_id=%d, tx_id=%d, buf_size=%d, buffer=%p", int(nAtomIn), int(nAtomOut), int(nBufSize), pBuffer);
                    return true;
                }

                inline bool ui_connect_to_plugin()
                {
                    if (map == NULL)
                        return false;
                    if (pWrapper != NULL)
                        return true;

                    // Prepare forge for transfer
                    LV2_Atom_Forge_Frame    frame;
                    forge_set_buffer(pBuffer, nBufSize);

                    // Send CONNECT UI message
                    lsp_trace("Sending CONNECT UI message");
                    LV2_Atom *msg = forge_object(&frame, uridConnectUI, uridUINotification);
                    forge_pop(&frame);
                    write_data(nAtomOut, lv2_atom_total_size(msg), uridEventTransfer, msg);

                    return true;
                }

                inline bool request_state_dump()
                {
                    if (map == NULL)
                        return false;

                    // Prepare forge for transfer
                    LV2_Atom_Forge_Frame    frame;
                    forge_set_buffer(pBuffer, nBufSize);

                    // Send DUMP STATE message
                    lsp_trace("Sending DUMP STATE message");
                    LV2_Atom *msg = forge_object(&frame, uridDumpState, uridUINotification);
                    forge_pop(&frame);
                    write_data(nAtomOut, lv2_atom_total_size(msg), uridEventTransfer, msg);

                    return true;
                }

                inline void ui_disconnect_from_plugin()
                {
                    if (map == NULL)
                        return;
                    if (pWrapper != NULL)
                        return;

                    // Prepare forge for transfer
                    LV2_Atom_Forge_Frame    frame;
                    forge_set_buffer(pBuffer, nBufSize);

                    // Send DISCONNECT UI message
                    lsp_trace("Sending DISCONNECT UI message");
                    LV2_Atom *msg = forge_object(&frame, uridDisconnectUI, uridUINotification);
                    forge_pop(&frame);
                    write_data(nAtomOut, lv2_atom_total_size(msg), uridEventTransfer, msg);
                }

                bool ui_write_patch(lv2::Serializable *p)
                {
                    if ((map == NULL) || (p->get_urid() <= 0))
                        return false;

                    // Forge PATCH SET message
                    LV2_Atom_Forge_Frame    frame;
                    forge_set_buffer(pBuffer, nBufSize);

                    forge_frame_time(0);
                    LV2_Atom *msg = forge_object(&frame, uridChunk, uridPatchSet);
                    forge_key(uridPatchProperty);
                    forge_urid(p->get_urid());
                    forge_key(uridPatchValue);
                    p->serialize();
                    forge_pop(&frame);

                    write_data(nAtomOut, lv2_atom_total_size(msg), uridEventTransfer, msg);
                    return true;
                }

                bool ui_write_port_change(lv2::Serializable *p)
                {
                    if ((map == NULL) || (p->get_urid() <= 0))
                        return false;

                    // Forge PATCH SET message
                    LV2_Atom_Forge_Frame    frame;
                    forge_set_buffer(pBuffer, nBufSize);

                    forge_frame_time(0);
                    LV2_Atom *msg = forge_object(&frame, uridPortData, uridPortDataType);
                    forge_key(p->get_urid());
                    p->serialize();
                    forge_pop(&frame);

                    write_data(nAtomOut, lv2_atom_total_size(msg), uridEventTransfer, msg);
                    return true;
                }

                bool ui_play_sample(const char *name, wsize_t position, bool release)
                {
                    if (map == NULL)
                        return false;

                    // Forge PATCH SET message
                    LV2_Atom_Forge_Frame    frame;
                    forge_set_buffer(pBuffer, nBufSize);

                    forge_frame_time(0);
                    LV2_Atom *msg = forge_object(&frame, uridPlaySample, uridPlayRequestType);
                    forge_key(uridPlayRequestFileName);
                    forge_path(name);
                    forge_key(uridPlayRequestPosition);
                    forge_long(position);
                    forge_key(uridPlayRequestRelease);
                    forge_bool(release);
                    forge_pop(&frame);

                    write_data(nAtomOut, lv2_atom_total_size(msg), uridEventTransfer, msg);
                    return true;
                }

                LV2_Atom *ui_serialize_preset_state(LV2_Atom_Forge_Frame *frame, const core::preset_state_t *state)
                {
                    LV2_Atom *obj = forge_object(frame, uridPresetState, uridPresetStateType);
                    {
                        forge_key(uridPresetName);
                        forge_string(state->name);
                        forge_key(uridPresetFlags);
                        forge_int(int32_t(state->flags));
                        forge_key(uridPresetTab);
                        forge_int(int32_t(state->tab));
                    }
                    forge_pop(frame);

                    return obj;
                }

                bool deserialize_preset_state(core::preset_state_t *state, const LV2_Atom_Object_Body *obody, uint32_t p_type, size_t p_size)
                {
                    if ((p_type != forge.Object) && (p_type != uridBlank))
                    {
                        lsp_warn("Invalid preset atom type: %d = %s", int(p_type), unmap_urid(p_type));
                        return false;
                    }

                    if (obody->otype != uridPresetStateType)
                    {
                        lsp_warn("Unsupported preset state object type: %d = %s", int(obody->otype), unmap_urid(obody->otype));
                        return false;
                    }

                    state->name[0]    = '\0';
                    state->flags      = core::PRESET_FLAG_NONE;
                    state->tab        = 0;

                    // Parse preset object
                    for (
                        LV2_Atom_Property_Body *item = lv2_atom_object_begin(obody) ;
                        !lv2_atom_object_is_end(obody, p_size, item) ;
                        item = lv2_atom_object_next(item)
                    )
                    {
                        lsp_trace("item->key (%d) = %s", int(item->key), unmap_urid(item->key));
                        lsp_trace("item->value.type (%d) = %s", int(item->value.type), unmap_urid(item->value.type));

                        // Analyze type of value
                        if ((item->key == uridPresetName) && (item->value.type == forge.String))
                        {
                            const LV2_Atom_String *value = reinterpret_cast<const LV2_Atom_String *>(&item->value);
                            const char *name    = reinterpret_cast<const char *>(&value[1]);
                            const size_t len    = lsp_min(strnlen(name, value->atom.size), core::PRESET_NAME_BYTES - 1);
                            memcpy(state->name, name, len);
                            state->name[len]    = '\0';
                        }
                        else if ((item->key == uridPresetFlags) && (item->value.type == forge.Int))
                            state->flags        = reinterpret_cast<const LV2_Atom_Int *>(&item->value)->body;
                        else if ((item->key == uridPresetTab) && (item->value.type == forge.Int))
                            state->tab          = reinterpret_cast<const LV2_Atom_Int *>(&item->value)->body;
                        else
                            lsp_warn("Unknown preset state property: %d (%s)", item->key, unmap_urid(item->key));
                    } // for (object body)

                    return true;
                }

                bool ui_send_preset_state(const core::preset_state_t *state)
                {
                    if (map == NULL)
                        return false;

                    // Forge PATCH SET message
                    LV2_Atom_Forge_Frame    frame;
                    forge_set_buffer(pBuffer, nBufSize);

                    forge_frame_time(0);
                    LV2_Atom *msg = ui_serialize_preset_state(&frame, state);
                    write_data(nAtomOut, lv2_atom_total_size(msg), uridEventTransfer, msg);
                    return true;
                }
        };

        #define PATCH_OVERHEAD  (sizeof(LV2_Atom_Property) + sizeof(LV2_Atom_URID) + sizeof(LV2_Atom) + 0x20)

        inline long lv2_all_port_sizes(const meta::port_t *ports, bool in, bool out)
        {
            long size           = 0;

            for (const meta::port_t *p = ports; p->id != NULL; ++p)
            {
                switch (p->role)
                {
                    case meta::R_CONTROL:
                    case meta::R_METER:
                        size            += PATCH_OVERHEAD + sizeof(LV2_Atom_Float);
                        break;
                    case meta::R_MESH:
                        if (meta::is_out_port(p) && (!out))
                            break;
                        else if (meta::is_in_port(p) && (!in))
                            break;
                        size            += lv2_mesh_t::size_of_port(p);
                        break;
                    case meta::R_STREAM:
                    {
                        if (meta::is_out_port(p) && (!out))
                            break;
                        else if (meta::is_in_port(p) && (!in))
                            break;

                        size_t vector_len   = sizeof(LV2_Atom_Vector) + 4 * sizeof(LV2_Atom_Int) + sizeof(float) * STREAM_MAX_FRAME_SIZE;
                        size_t frm_size     = sizeof(LV2_Atom_Object) + 8 * sizeof(LV2_Atom_Int) + size_t(p->min) * vector_len;
                        size_t data_size    = sizeof(LV2_Atom_Object) + 8 * sizeof(LV2_Atom_Int) + STREAM_BULK_MAX * frm_size;
                        size               += data_size;
                        break;
                    }
                    case meta::R_FBUFFER:
                        if (meta::is_out_port(p) && (!out))
                            break;
                        else if (meta::is_in_port(p) && (!in))
                            break;
                        size           += (4 * sizeof(LV2_Atom_Int) + 0x100) + // Headers
                                            size_t(p->step) * FRAMEBUFFER_BULK_MAX * sizeof(float);
                        break;
                    case meta::R_OSC_IN:
                    case meta::R_OSC_OUT:
                        size           += OSC_BUFFER_MAX;
                        break;
                    case meta::R_MIDI_IN:
                        if (!in)
                            break;
                        size            += (sizeof(LV2_Atom_Event) + 0x10) * MIDI_EVENTS_MAX; // Size of atom event + pad for MIDI data
                        break;
                    case meta::R_MIDI_OUT:
                        if (!out)
                            break;
                        size            += (sizeof(LV2_Atom_Event) + 0x10) * MIDI_EVENTS_MAX; // Size of atom event + pad for MIDI data
                        break;
                    case meta::R_PATH: // Both sizes: IN and OUT
                        size            += PATCH_OVERHEAD + PATH_MAX;
                        break;
                    case meta::R_STRING:
                        size            += PATCH_OVERHEAD + p->max * 4;
                        break;
                    case meta::R_PORT_SET:
                        if ((p->members != NULL) && (p->items != NULL))
                        {
                            size_t items        = list_size(p->items);
                            size               += items * lv2_all_port_sizes(p->members, in, out); // Add some overhead
                            size               += sizeof(LV2_Atom_Int) + 0x10;
                        }
                        break;
                    default:
                        break;
                }
            }

            // Update state size
            return LSP_LV2_SIZE_PAD(size); // Add some extra bytes for
        }

        #undef PATCH_OVERHEAD
    } /* namespace lv2 */
} /* namespace lsp */

#endif /* LSP_PLUG_IN_PLUG_FW_WRAP_LV2_EXTENSIONS_H_ */
