/*
 * Copyright (C) 2026 Linux Studio Plugins Project <https://lsp-plug.in/>
 *           (C) 2026 Vladimir Sadovnikov <sadko4u@gmail.com>
 *
 * This file is part of lsp-plugins-comp-delay
 * Created on: 5 янв. 2023 г.
 *
 * lsp-plugins-comp-delay 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-plugins-comp-delay 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-plugins-comp-delay. If not, see <https://www.gnu.org/licenses/>.
 */

#ifndef LSP_PLUG_IN_PLUG_FW_WRAP_CLAP_IMPL_UI_WRAPPER_H_
#define LSP_PLUG_IN_PLUG_FW_WRAP_CLAP_IMPL_UI_WRAPPER_H_

#include <lsp-plug.in/plug-fw/version.h>

#include <clap/clap.h>
#include <lsp-plug.in/ipc/NativeExecutor.h>
#include <lsp-plug.in/ipc/Thread.h>
#include <lsp-plug.in/plug-fw/wrap/clap/wrapper.h>
#include <lsp-plug.in/runtime/system.h>
#include <lsp-plug.in/stdlib/stdio.h>
#include <lsp-plug.in/tk/tk.h>

namespace lsp
{
    namespace clap
    {
        UIWrapper::UIWrapper(ui::Module *ui, clap::Wrapper *wrapper):
            ui::IWrapper(ui, wrapper->resources())
        {
            pWrapper        = wrapper;
            pExt            = wrapper->extensions();
            fScaling        = -100.0f;
            pTransientFor   = NULL;
            nLastWidth      = -1;
            nLastHeight     = -1;

            bRequestProcess = false;
            bUIActive       = false;
            bRealizeActive  = false;
            bDestroying     = false;

        #ifdef LSP_CLAP_OWN_EVENT_LOOP
            pUIThread       = NULL;
        #endif /* LSP_CLAP_OWN_EVENT_LOOP */
        }

        UIWrapper::~UIWrapper()
        {
            do_destroy();
        }

        status_t UIWrapper::init(void *root_widget)
        {
            lsp_trace("Initializing wrapper");

            status_t res = STATUS_OK;

            // Obtain UI metadata
            const meta::plugin_t *meta = pUI->metadata();
            if (pUI->metadata() == NULL)
                return STATUS_BAD_STATE;

            // Create list of ports and sort it in ascending order by the identifier
            lsp_trace("Creating ports for %s - %s", meta->name, meta->description);
            for (const meta::port_t *port = meta->ports ; port->id != NULL; ++port)
                create_port(port, NULL);

            // Initialize parent
            if ((res = IWrapper::init(root_widget)) != STATUS_OK)
                return res;

            // Bind the display idle handler
            pDisplay->slots()->bind(tk::SLOT_IDLE, slot_display_idle, this);
            pDisplay->set_idle_interval(1000 / UI_FRAMES_PER_SECOND);

            // Initialize the UI early so gui.get_size() can report real dimensions.
            if ((res = initialize_ui()) != STATUS_OK)
                return false;

            return res;
        }

        void UIWrapper::destroy()
        {
            do_destroy();
        }

        void UIWrapper::do_destroy()
        {
            if (bDestroying)
                return;

            bDestroying     = true;

            // Update UI status
            bUIActive       = false;
            if (pWrapper != NULL)
                pWrapper->ui_visibility_changed();

            // Stop the event loop
            stop_event_loop();

            // Destroy the transient window wrapper
            if (pTransientFor != NULL)
            {
                delete pTransientFor;
                pTransientFor   = NULL;
            }

            // Stop the UI thread
            hide();

            // Destroy UI
            if (pUI != NULL)
            {
                pUI->pre_destroy();
                pUI->destroy();
                delete pUI;
                pUI = NULL;
            }

            // Call parent instance
            IWrapper::destroy();

            // Destroy the display
            if (pDisplay != NULL)
            {
                pDisplay->destroy();
                delete pDisplay;
                pDisplay        = NULL;
            }
        }

        bool UIWrapper::start_event_loop()
        {
        #ifdef LSP_CLAP_OWN_EVENT_LOOP
            // Launch the main loop thread
            lsp_trace("Creating main loop thread");
            pUIThread   = new ipc::Thread(event_loop, this);
            if (pUIThread == NULL)
            {
                lsp_error("Failed to create UI main loop thread");
                return false;
            }

            if (pUIThread->start() != STATUS_OK)
            {
                lsp_error("Failed to start UI main loop thread");
                delete pUIThread;
                pUIThread = NULL;
                return false;
            }

            lsp_trace("Successfully started main loop thread");
        #endif /* LSP_CLAP_OWN_EVENT_LOOP */

            return true;
        }

        void UIWrapper::stop_event_loop()
        {
        #ifdef LSP_CLAP_OWN_EVENT_LOOP
            // Cancel the main loop thread
            if (pUIThread != NULL)
            {
                if (pDisplay != NULL)
                    pDisplay->quit_main();

                pUIThread->cancel();
                pUIThread->join();

                delete pUIThread;
                pUIThread = NULL;
            }
        #endif /* LSP_CLAP_OWN_EVENT_LOOP */
        }

    #ifdef LSP_CLAP_OWN_EVENT_LOOP
        status_t UIWrapper::event_loop(void *arg)
        {
            static constexpr size_t FRAME_PERIOD    = 1000 / UI_FRAMES_PER_SECOND;

            UIWrapper *self = static_cast<UIWrapper *>(arg);

            lsp_trace("Entering main loop");

            // Perform main loop
            system::time_millis_t ctime = system::get_time_millis();
            while (!ipc::Thread::is_cancelled())
            {
                // Measure the time of next frame to appear
                system::time_millis_t deadline = ctime + FRAME_PERIOD;

                // Perform main iteration with locked mutex
                if (self->sMutex.lock())
                {
                    lsp_finally { self->sMutex.unlock(); };
                    self->pDisplay->main_iteration();
                }

                // Wait for the next frame to appear
                system::time_millis_t ftime = system::get_time_millis();
                if (ftime < deadline)
                    self->pDisplay->wait_events(deadline - ftime);
                ctime   = ftime;
            }

            lsp_trace("Leaving main loop");

            return STATUS_OK;
        }
    #endif /* LSP_CLAP_OWN_EVENT_LOOP */

        clap::UIPort *UIWrapper::create_port(const meta::port_t *port, const char *postfix)
        {
            // Find the matching port for the backend
            clap::UIPort *cup = NULL;
            clap::Port *cp    = pWrapper->find_by_id(port->id);
            if (cp == NULL)
                return cup;

            switch (port->role)
            {
                case meta::R_AUDIO_IN:
                case meta::R_AUDIO_OUT:
                    // Stub port
                    lsp_trace("creating stub audio port %s", port->id);
                    cup = new clap::UIPort(port, cp);
                    break;

                case meta::R_AUDIO_SEND:
                    // Stub port
                    lsp_trace("creating stub audio send port %s", port->id);
                    cup = new clap::UIPort(port, cp);
                    break;

                case meta::R_AUDIO_RETURN:
                    // Stub port
                    lsp_trace("creating stub audio return port %s", port->id);
                    cup = new clap::UIPort(port, cp);
                    break;

                case meta::R_MIDI_IN:
                case meta::R_MIDI_OUT:
                    // Stub port
                    lsp_trace("creating stub midi port %s", port->id);
                    cup = new clap::UIPort(port, cp);
                    break;

                case meta::R_MESH:
                    lsp_trace("creating mesh port %s", port->id);
                    cup = new clap::UIMeshPort(cp);
                    break;

                case meta::R_STREAM:
                    lsp_trace("creating stream port %s", port->id);
                    cup = new clap::UIStreamPort(cp);
                    break;

                case meta::R_FBUFFER:
                    lsp_trace("creating fbuffer port %s", port->id);
                    cup = new clap::UIFrameBufferPort(cp);
                    break;

                case meta::R_OSC_IN:
                    lsp_trace("creating output osc port %s", port->id);
                    cup     = new clap::UIOscPortOut(port, cp);
                    break;

                case meta::R_OSC_OUT:
                    lsp_trace("creating input osc port %s", port->id);
                    cup     = new clap::UIOscPortIn(port, cp);
                    break;

                case meta::R_PATH:
                    lsp_trace("creating path port %s", port->id);
                    cup = new clap::UIPathPort(cp, this);
                    break;

                case meta::R_STRING:
                    lsp_trace("creating string port %s", port->id);
                    cup = new clap::UIStringPort(cp, this);
                    break;

                case meta::R_SEND_NAME:
                    lsp_trace("creating send name port %s", port->id);
                    cup = new clap::UIStringPort(cp, this);
                    break;

                case meta::R_RETURN_NAME:
                    lsp_trace("creating return name port %s", port->id);
                    cup = new clap::UIStringPort(cp, this);
                    break;

                case meta::R_CONTROL:
                    lsp_trace("creating parameter port %s", port->id);
                    cup     = new clap::UIParameterPort(static_cast<clap::ParameterPort *>(cp), &bRequestProcess, this);
                    break;

                case meta::R_BYPASS:
                    lsp_trace("creating bypass port %s", port->id);
                    cup     = new clap::UIParameterPort(static_cast<clap::ParameterPort *>(cp), &bRequestProcess, NULL);
                    break;

                case meta::R_METER:
                    lsp_trace("creating meter port %s", port->id);
                    cup     = new clap::UIMeterPort(cp);
                    break;

                case meta::R_PORT_SET:
                {
                    char postfix_buf[MAX_PARAM_ID_BYTES], param_name[MAX_PARAM_ID_BYTES];
                    lsp_trace("creating port group %s", port->id);
                    UIPortGroup *upg = new clap::UIPortGroup(static_cast<clap::PortGroup *>(cp), &bRequestProcess, this);

                    // Add immediately port group to list
                    vPorts.add(upg);

                    // Add nested ports
                    lsp_trace("  rows = %d", int(upg->rows()));
                    for (size_t row=0; row < upg->rows(); ++row)
                    {
                        // Generate postfix
                        snprintf(postfix_buf, sizeof(postfix_buf)-1, "%s_%d", (postfix != NULL) ? postfix : "", int(row));

                        // Obtain the related port for backend
                        for (const meta::port_t *p = port->members; p->id != NULL; ++p)
                        {
                            // Initialize port name
                            strncpy(param_name, p->id, sizeof(param_name)-1);
                            strncat(param_name, postfix_buf, sizeof(param_name)-1);
                            param_name[sizeof(param_name) - 1] = '\0';

                            // Obtain backend port and create UI port for it
                            cp    = pWrapper->find_by_id(param_name);
                            lsp_trace("find_by_id %s -> %p", param_name, cp);
                            if (cp != NULL)
                                create_port(cp->metadata(), postfix_buf);
                        }
                    }

                    break;
                }

                default:
                    break;
            }

            // Add port to the list of UI ports
            if (cup != NULL)
                vPorts.add(cup);

            return cup;
        }

        void UIWrapper::tranfet_ui_to_dsp()
        {
            if (bRequestProcess)
            {
                if ((pExt != NULL) && (pExt->host != NULL))
                    pExt->host->request_process(pExt->host);
                bRequestProcess = false;
            }
        }

        void UIWrapper::transfer_dsp_to_ui()
        {
            // Initialize DSP state
            dsp::context_t ctx;
            dsp::start(&ctx);
            lsp_finally { dsp::finish(&ctx); };

            // Try to sync position
            IWrapper::position_updated(pWrapper->position());

            // DSP -> UI communication
            for (size_t i=0, nports=vPorts.size(); i < nports; ++i)
            {
                // Get UI port
                clap::UIPort *cup   = static_cast<clap::UIPort *>(vPorts.uget(i));
                do {
                    if (cup->sync())
                        cup->notify_all(ui::PORT_NONE);
                } while (cup->sync_again());
            } // for port_id

            // Perform KVT synchronization
            core::KVTStorage *kvt = pWrapper->kvt_lock();
            if (kvt != NULL)
            {
                // Synchronize DSP -> UI transfer
                size_t sync;
                const char *kvt_name;
                const core::kvt_param_t *kvt_value;

                do
                {
                    sync = 0;

                    core::KVTIterator *it = kvt->enum_tx_pending();
                    while (it->next() == STATUS_OK)
                    {
                        kvt_name = it->name();
                        if (kvt_name == NULL)
                            break;
                        status_t res = it->get(&kvt_value);
                        if (res != STATUS_OK)
                            break;
                        if ((res = it->commit(core::KVT_TX)) != STATUS_OK)
                            break;

                        kvt_dump_parameter("TX kvt param (DSP->UI): %s = ", kvt_value, kvt_name);
                        notify_write_to_kvt(kvt, kvt_name, kvt_value);
                        ++sync;
                    }
                } while (sync > 0);

                // Synchronize UI -> DSP transfer
                #ifdef LSP_DEBUG
                {
                    core::KVTIterator *it = kvt->enum_rx_pending();
                    while (it->next() == STATUS_OK)
                    {
                        kvt_name = it->name();
                        if (kvt_name == NULL)
                            break;
                        status_t res = it->get(&kvt_value);
                        if (res != STATUS_OK)
                            break;
                        if ((res = it->commit(core::KVT_RX)) != STATUS_OK)
                            break;

                        kvt_dump_parameter("RX kvt param (UI->DSP): %s = ", kvt_value, kvt_name);
                    }
                }
                #else
                    kvt-> commit_all(core::KVT_RX);    // Just clear all RX queue for non-debug version
                #endif /* LSP_DEBUG */

                // Call garbage collection and release KVT storage
                kvt->gc();
                kvt_release();
            }

            // Notify sample listeners if something has changed
            core::SamplePlayer *sp = pWrapper->sample_player();
            if (sp != NULL)
                notify_play_position(sp->position(), sp->sample_length());
        }

        core::KVTStorage *UIWrapper::kvt_lock()
        {
            return pWrapper->kvt_lock();
        }

        core::KVTStorage *UIWrapper::kvt_trylock()
        {
            return pWrapper->kvt_trylock();
        }

        bool UIWrapper::kvt_release()
        {
            return pWrapper->kvt_release();
        }

        void UIWrapper::dump_state_request()
        {
            pWrapper->request_state_dump();
        }

        const meta::package_t *UIWrapper::package() const
        {
            return pWrapper->package();
        }

        status_t UIWrapper::play_file(const char *file, wsize_t position, bool release)
        {
            core::SamplePlayer *p = pWrapper->sample_player();
            if (p != NULL)
            {
                // Trigger playback and force the position to become out-of-sync
                p->play_sample(file, position, release);
            }
            return STATUS_OK;
        }

        float UIWrapper::ui_scaling_factor(float scaling)
        {
            return (fScaling > 0.0f) ? fScaling : scaling;
        }

        bool UIWrapper::accept_window_size(tk::Window *wnd, size_t width, size_t height)
        {
            if (wnd != wWindow)
                return IWrapper::accept_window_size(wnd, width, height);
            if ((bDestroying) || (pExt == NULL) || (pExt->gui == NULL))
                return false;

            lsp_trace("request_resize(%d, %d)", int(width), int(height));
            nLastWidth  = width;
            nLastHeight = height;
            return pExt->gui->request_resize(pExt->host, width, height);
        }

        meta::plugin_format_t UIWrapper::plugin_format() const
        {
            return meta::PLUGIN_CLAP;
        }

        const core::ShmState *UIWrapper::shm_state()
        {
            return (pWrapper != NULL) ? pWrapper->shm_state() : NULL;
        }

        status_t UIWrapper::slot_ui_show(tk::Widget *sender, void *ptr, void *data)
        {
            lsp_trace("sender = %p, ptr = %p, data = %p", sender, ptr, data);

            return STATUS_OK;
        }

        status_t UIWrapper::slot_ui_realized(tk::Widget *sender, void *ptr, void *data)
        {
            const ws::rectangle_t *r = static_cast<ws::rectangle_t *>(data);
            UIWrapper *self     = static_cast<UIWrapper *>(ptr);
            if ((self == NULL) || (self->bDestroying) || (r == NULL))
                return STATUS_OK;
            if ((self->pExt == NULL) || (self->pExt->gui == NULL))
                return STATUS_OK;

        #ifdef LSP_TRACE
            lsp_trace("sender = %p, ptr = %p, data = %p", sender, ptr, data);
            lsp_trace("realized.w = %d, realized.h = %d", int(r->nWidth), int(r->nHeight));
        #endif /* LSP_TRACE */

            if ((self->bRealizeActive) ||
                ((self->nLastWidth == r->nWidth) && (self->nLastHeight == r->nHeight)))
                return STATUS_OK;

            lsp_finally { self->bRealizeActive = false; };
            self->bRealizeActive    = true;
            self->nLastWidth        = r->nWidth;
            self->nLastHeight       = r->nHeight;

            lsp_trace("request_resize(%d, %d)", int(r->nWidth), int(r->nHeight));
            self->pExt->gui->request_resize(self->pExt->host, r->nWidth, r->nHeight);

            return STATUS_OK;
        }

        status_t UIWrapper::slot_ui_close(tk::Widget *sender, void *ptr, void *data)
        {
            lsp_trace("sender = %p, ptr = %p, data = %p", sender, ptr, data);
            UIWrapper *this_ = static_cast<UIWrapper *>(ptr);
            if ((this_ == NULL) || (this_->bDestroying))
                return STATUS_OK;
            if ((this_->pExt != NULL) && (this_->pExt->gui != NULL))
                this_->pExt->gui->closed(this_->pExt->host, false);
            return STATUS_OK;
        }

        bool UIWrapper::set_scale(double scale)
        {
            // Scale is in %
            fScaling    = scale * 100.0f;

            // Trigger scaling factor to become updated
            if (!sMutex.lock())
                return false;
            lsp_finally { sMutex.unlock(); };

            host_scaling_changed();

            return true;
        }

        bool UIWrapper::get_size(uint32_t *width, uint32_t *height)
        {
            tk::Window *wnd     = window();
            if (wnd == NULL)
                return false;

            // Lock the access to the main loop
            if (!sMutex.lock())
                return false;
            lsp_finally { sMutex.unlock(); };

            // Obtain the window parameters
            if (wnd->visibility()->get())
            {
                ws::rectangle_t rr;
                rr.nLeft        = 0;
                rr.nTop         = 0;
                rr.nWidth       = 0;
                rr.nHeight      = 0;

                wnd->get_padded_rectangle(&rr);

                // Return result
                nLastWidth      = rr.nWidth;
                nLastHeight     = rr.nHeight;
            }
            else
            {
                ws::size_limit_t sr;
                wnd->get_padded_size_limits(&sr);

                // Return result
                nLastWidth      = lsp_max(sr.nMinWidth, 32);
                nLastHeight     = lsp_max(sr.nMinHeight, 32);
            }

            if (width != NULL)
                *width  = uint32_t(nLastWidth);
            if (height != NULL)
                *height = uint32_t(nLastHeight);

            lsp_trace("return width = %d, height = %d", int(nLastWidth), int(nLastHeight));

            return true;
        }

        bool UIWrapper::can_resize()
        {
            tk::Window *wnd     = window();
            if (wnd == NULL)
                return false;

            if (!sMutex.lock())
                return false;
            lsp_finally { sMutex.unlock(); };

            return wnd->border_style()->get() != ws::BS_DIALOG;
        }

        bool UIWrapper::get_resize_hints(clap_gui_resize_hints_t *hints)
        {
            if (hints == NULL)
                return false;

            tk::Window *wnd     = window();
            if (wnd == NULL)
                return false;

            if (!sMutex.lock())
                return false;
            lsp_finally { sMutex.unlock(); };

            // Obtain window size constraints
            ws::size_limit_t sr;
            wnd->get_padded_size_limits(&sr);

            hints->can_resize_horizontally  = (sr.nMaxWidth < 0) || (sr.nMaxWidth  > sr.nMinWidth);
            hints->can_resize_vertically    = (sr.nMaxHeight< 0) || (sr.nMaxHeight > sr.nMinHeight);

            hints->preserve_aspect_ratio    = false;
            hints->aspect_ratio_width       = 1;
            hints->aspect_ratio_height      = 1;

            return true;
        }

        bool UIWrapper::adjust_size(uint32_t *width, uint32_t *height)
        {
            if ((width == NULL) || (height == NULL))
                return false;

            tk::Window *wnd     = window();
            if (wnd == NULL)
                return false;

            if (!sMutex.lock())
                return false;
            lsp_finally { sMutex.unlock(); };

            ws::size_limit_t sr;
            wnd->get_padded_size_limits(&sr);

            ws::rectangle_t srq, drq;
            srq.nLeft    = 0;
            srq.nTop     = 0;
            srq.nWidth   = *width;
            srq.nHeight  = *height;

            tk::SizeConstraints::apply(&drq, &srq, &sr);

            lsp_trace("adjust size width = %d -> %d, height = %d -> %d",
                *width, int(drq.nWidth),
                *height, int(drq.nHeight));

            if ((drq.nWidth < 0) || (drq.nHeight < 0))
                return false;

            *width      = uint32_t(drq.nWidth);
            *height     = uint32_t(drq.nHeight);

            return true;
        }

        bool UIWrapper::set_size(uint32_t width, uint32_t height)
        {
            tk::Window *wnd     = window();
            if (wnd == NULL)
            {
                lsp_trace("no window");
                return false;
            }

            if (!sMutex.lock())
                return false;
            lsp_finally { sMutex.unlock(); };

            // Ensure that we can resize the window to the desired size
            ws::rectangle_t srq, drq;
            ws::size_limit_t sr;
            wnd->get_padded_size_limits(&sr);

            srq.nLeft    = 0;
            srq.nTop     = 0;
            srq.nWidth   = width;
            srq.nHeight  = height;

            tk::SizeConstraints::apply(&drq, &srq, &sr);
            if ((drq.nWidth != ssize_t(width)) || (drq.nHeight != ssize_t(height)))
            {
                lsp_trace("failed size check: width=%d -> %d, height=%d -> %d",
                    int(width), int(drq.nWidth), int(height), int(drq.nHeight));
                return false;
            }

            // Check that we need to resize window
            ws::rectangle_t r;
            wnd->get_padded_rectangle(&r);
            if ((r.nWidth != drq.nWidth) || (r.nHeight != drq.nHeight))
            {
                lsp_trace("width = %d -> %d, height = %d -> %d, call for resize",
                    int(r.nWidth), int(drq.nWidth), int(r.nHeight), int(drq.nHeight));
                wnd->resize_window(drq.nWidth, drq.nHeight);
            }

            return true;
        }

        void *UIWrapper::to_native_handle(const clap_window_t *window)
        {
            if (window == NULL)
                return NULL;

        #if defined(PLATFORM_WINDOWS)
            return static_cast<void *>(window->win32);
        #elif defined(PLATFORM_MACOSX)
            return static_cast<void *>(window->cocoa);
        #else
            return reinterpret_cast<void *>(window->x11);
        #endif
        }

        bool UIWrapper::set_parent(const clap_window_t *clap_window)
        {
            if (clap_window == NULL)
                return false;

            tk::Window * const wnd  = window();
            if (wnd == NULL)
                return false;

            if (!sMutex.lock())
                return false;
            lsp_finally { sMutex.unlock(); };

            status_t res = wnd->native()->set_parent(to_native_handle(clap_window));
            if (res != STATUS_OK)
                return false;

            wnd->position()->set(0, 0);
            return true;
        }

        bool UIWrapper::set_transient(const clap_window_t *window)
        {
            if (pDisplay == NULL)
                return false;

            if (pTransientFor != NULL)
                delete pTransientFor;

            void *handle    = to_native_handle(window);
            pTransientFor   = (handle != NULL) ? pDisplay->display()->wrap_window(handle) : NULL;

            return true;
        }

        void UIWrapper::suggest_title(const char *title)
        {
            tk::Window *wnd     = window();
            if (wnd == NULL)
                return;
            if (!sMutex.lock())
                return;
            lsp_finally { sMutex.unlock(); };

            // Update the window title
            wnd->title()->set_raw(title);
        }

        status_t UIWrapper::initialize_ui()
        {
            lsp_trace("Initializing UI contents");

            status_t res = STATUS_OK;

            // Initialize the UI
            if ((res = pUI->init(this)) != STATUS_OK)
                return res;

            const meta::plugin_t *meta = pUI->metadata();
            if (pUI->metadata() == NULL)
                return STATUS_BAD_STATE;

            if (meta->ui_resource != NULL)
            {
                if ((res = build_ui(meta->ui_resource, NULL)) != STATUS_OK)
                {
                    lsp_error("Error building UI for resource %s: code=%d", meta->ui_resource, int(res));
                    return false;
                }
            }

            // Bind resize slot
            lsp_trace("Binding slots");
            tk::Window *wnd  = window();
            if (wnd != NULL)
            {
                wnd->slots()->bind(tk::SLOT_SHOW, slot_ui_show, this);
                wnd->slots()->bind(tk::SLOT_REALIZED, slot_ui_realized, this);
                wnd->slots()->bind(tk::SLOT_CLOSE, slot_ui_close, this);
            }

            // Call the post-initialization routine
            lsp_trace("Doing post-init");
            res = pUI->post_init();
            lsp_trace("result=%d", int(res));

            return res;
        }

        bool UIWrapper::ui_active() const
        {
            return bUIActive;
        }

        void UIWrapper::main_iteration()
        {
            if (!bUIActive)
                return;

            // Perform main data transfers and state sync
            core::preset_state_t state;
            tranfet_ui_to_dsp();
            transfer_dsp_to_ui();
            if (pWrapper->fetch_preset_state(&state, false))
                receive_preset_state(&state);

            IWrapper::main_iteration();
        }

        bool UIWrapper::show()
        {
            // Process all pending events before starting the display and show the window
            tk::Display * const dpy = display();
            if (dpy == NULL)
                return false;
            tk::Window * const wnd  = window();
            if (wnd == NULL)
                return false;

            dpy->process_pending_events();
            wnd->show(pTransientFor);

            // Start the event loop
            bool res    = start_event_loop();
            if (!res)
            {
                wnd->hide();
                return res;
            }

            // Do the sync barrier
            sMutex.lock();
            lsp_finally { sMutex.unlock(); };

            // Resize window
            ws::size_limit_t sr;
            wnd->get_padded_size_limits(&sr);
            lsp_trace("min_w=%d, min_h=%d", int(sr.nMinWidth), int(sr.nMinHeight));
            if ((sr.nMinWidth >= 0) && (sr.nMinHeight >= 0))
            {
                lsp_trace("request_resize(%d, %d)", int(sr.nMinWidth), int(sr.nMinHeight));
                nLastWidth  = sr.nMinWidth;
                nLastHeight = sr.nMinHeight;
                if ((pExt != NULL) && (pExt->gui != NULL))
                    pExt->gui->request_resize(pExt->host, sr.nMinWidth, sr.nMinHeight);
            }

            // Fetch preset state
            core::preset_state_t state;
            if (pWrapper->fetch_preset_state(&state, true))
                receive_preset_state(&state);

            // Update UI status
            bUIActive       = true;
            if (pWrapper != NULL)
                pWrapper->ui_visibility_changed();

            return true;
        }

        bool UIWrapper::hide()
        {
            // Update UI status
            bUIActive       = false;
            if (pWrapper != NULL)
                pWrapper->ui_visibility_changed();

            // Hide the window
            tk::Window * const wnd = window();
            if (wnd != NULL)
            {
                sMutex.lock();
                lsp_finally { sMutex.unlock(); };
                wnd->hide();
            }

            // Stop the event loop
            stop_event_loop();

            // Process pending events
            tk::Display * const dpy = display();
            if (dpy != NULL)
                dpy->process_pending_events();

            return true;
        }

        status_t UIWrapper::slot_display_idle(tk::Widget *sender, void *ptr, void *data)
        {
            UIWrapper *self = static_cast<UIWrapper *>(ptr);
            if ((self != NULL) && (!self->bDestroying))
                self->main_iteration();

            return STATUS_OK;
        }

        void UIWrapper::send_preset_state(const core::preset_state_t *state)
        {
            if (pWrapper != NULL)
                pWrapper->set_preset_state(state, clap::Wrapper::PT_STATE);
        }

        void UIWrapper::host_scaling_changed()
        {
            ctl::PluginWindow *wnd = ctl::ctl_cast<ctl::PluginWindow>(pWindow);
            if (wnd != NULL)
                wnd->host_scaling_changed();
        }

    } /* namespace clap */
} /* namespace lsp */

#endif /* LSP_PLUG_IN_PLUG_FW_WRAP_CLAP_IMPL_UI_WRAPPER_H_ */
