/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */

/*
    Rosegarden
    A MIDI and audio sequencer and musical notation editor.
    Copyright 2000-2026 the Rosegarden development team.

    Other copyrights also apply to some parts of this work.  Please
    see the AUTHORS file and individual file headers for details.

    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 (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#define RG_MODULE_STRING "[AudioPluginLV2GUIWindow]"
#define RG_NO_DEBUG_PRINT 1

#include "AudioPluginLV2GUIWindow.h"

#include "misc/Debug.h"
#include "AudioPluginLV2GUI.h"
#include "sound/LV2PluginInstance.h"
#include "sound/LV2URIDMapper.h"
#include "LV2Gtk2.h"
#include "gui/application/RosegardenMainWindow.h"

#include <QTimer>
#include <QWindow>
#include <QCloseEvent>
#include <QScreen>

#include <set>

#include <lilv/lilv.h>
#include <lv2/ui/ui.h>
#include <lv2/instance-access/instance-access.h>
#include <lv2/parameters/parameters.h>


namespace
{

    // some plugins call the writeFn even after a cleanup so keep a
    // track of all objects to avoid calls for deleted windows
    static std::set<Rosegarden::AudioPluginLV2GUIWindow*> activeWindows;

    /// For LV2UI_Descriptor::instantiate().
    void writeFn(LV2UI_Controller controller,
                 uint32_t port_index,
                 uint32_t buffer_size,
                 uint32_t port_protocol,
                 const void *buffer)
    {
        Rosegarden::AudioPluginLV2GUIWindow* ap =
            static_cast<Rosegarden::AudioPluginLV2GUIWindow*>(controller);
        if (activeWindows.find(ap) == activeWindows.end()) return;
        ap->portChange(port_index, buffer_size, port_protocol, buffer);
    }

    /// For m_resizeFeature.
    int LV2Resize (LV2UI_Feature_Handle handle, int width, int height )
    {
        RG_DEBUG << "resize" << width << height;
        if (width == 0 || height == 0) return 0; // refuse zero values
        // If scaling is being used the window size must be reduced
        // here because qt will scale it up again. I believe this
        // applies for all plugins whether they support the
        // scaleFactor option or not.
        float scaleFactor =
            Rosegarden::RosegardenMainWindow::self()->
            screen()->devicePixelRatio();
        RG_DEBUG << "resize adjust for scaleFactor" << scaleFactor;
        width /= scaleFactor;
        height /= scaleFactor;
        RG_DEBUG << "resize adjusted size" << width << height;
        QWidget *widget = static_cast<QWidget *>(handle);
        if (widget) {
            widget->resize(width, height);
            return 0;
        } else {
            return 1;
        }
    }

    /// For LV2_External_UI_Host.
    void ui_closed(LV2UI_Controller controller)
    {
        Rosegarden::AudioPluginLV2GUIWindow* ap =
            static_cast<Rosegarden::AudioPluginLV2GUIWindow*>(controller);
        if (activeWindows.find(ap) == activeWindows.end()) return;
        ap->uiClosed();
    }

}


namespace Rosegarden
{


AudioPluginLV2GUIWindow::AudioPluginLV2GUIWindow
(AudioPluginLV2GUI* lv2Gui,
 const QString& title,
 const LilvUI* ui,
 const LV2UI_Descriptor* uidesc,
 const QString& id,
 AudioPluginLV2GUI::UIType uiType) :
    m_lv2Gui(lv2Gui),
    m_uiType(uiType),
    m_lv2II(nullptr),
    m_containerWidget(nullptr),
    m_parentWindow(nullptr),
    m_widget(nullptr),
    m_title(title),
    m_titleStdString(title.toStdString()),
    m_shutdownRequested(false)
{
    activeWindows.insert(this);
    RG_DEBUG << "create window" <<
        id << m_uiType << m_title << activeWindows.size();
    setWindowTitle(m_title);

    // Create the idle timer for this window.
    m_timer = new QTimer(this);
    connect(m_timer, &QTimer::timeout,
            this, &AudioPluginLV2GUIWindow::slotTimeUp);
    m_timer->start(100);

    const char *ui_bundle_uri =
        lilv_node_as_uri(lilv_ui_get_bundle_uri(ui));
    // Parsed Bundle URI
    char* ubp = lilv_file_uri_parse(ui_bundle_uri, nullptr);

    // Assemble the features.

    m_idleFeature = {LV2_UI__idleInterface, nullptr};
    m_parentFeature = {LV2_UI__parent, (void*)winId()};

    const LV2PluginInstance* pluginInstance = m_lv2Gui->getPluginInstance();
    if (! pluginInstance) {
        RG_DEBUG << "no instance";
        return;
    }
    LV2_Handle handle = pluginInstance->getHandle();
    m_instanceFeature = {LV2_INSTANCE_ACCESS_URI, handle};

    const LV2_Descriptor* lv2d = pluginInstance->getLV2Descriptor();
    m_dataAccess.data_access = lv2d->extension_data;
    m_dataFeature = {LV2_DATA_ACCESS_URI, (void*)&m_dataAccess};

    m_resizeData.handle = this;
    m_resizeData.ui_resize = LV2Resize;
    m_resizeFeature.URI = LV2_UI__resize;
    m_resizeFeature.data = &m_resizeData;

    LV2_URID sampleRateUrid = LV2URIDMapper::uridMap(LV2_PARAMETERS__sampleRate);
    LV2_URID af_urid = LV2URIDMapper::uridMap(LV2_ATOM__Float);
    float sampleRate = pluginInstance->getSampleRate();
    LV2_URID titleUrid = LV2URIDMapper::uridMap(LV2_UI__windowTitle);
    LV2_URID scaleFactorUrid = LV2URIDMapper::uridMap(LV2_UI__scaleFactor);
    float scaleFactor1 =
        RosegardenMainWindow::self()->screen()->devicePixelRatio();
    RG_DEBUG << "scaleFactor" << scaleFactor1;
    LV2_URID as_urid = LV2URIDMapper::uridMap(LV2_ATOM__String);
    LV2_Options_Option opt;
    opt.context = LV2_OPTIONS_INSTANCE;
    opt.subject = 0;
    opt.key = sampleRateUrid;
    opt.size = 4;
    opt.type = af_urid;
    opt.value = &sampleRate;
    m_options.push_back(opt);
    opt.key = titleUrid;
    opt.size = m_titleStdString.size();
    opt.type = as_urid;
    opt.value = m_titleStdString.c_str();
    m_options.push_back(opt);
    opt.key = scaleFactorUrid;
    opt.size = 4;
    opt.type = af_urid;
    opt.value = &scaleFactor1;
    m_options.push_back(opt);
    opt.subject = 0;
    opt.key = 0;
    opt.size = 0;
    opt.type = 0;
    opt.value = 0;
    m_options.push_back(opt);
    m_optionsFeature = {LV2_OPTIONS__options, m_options.data()};

    m_extUiHost.ui_closed = &ui_closed;
    m_extUiHost.plugin_human_id = m_titleStdString.c_str();
    m_extHostFeature = {LV2_EXTERNAL_UI__Host, &m_extUiHost};

    m_uridMapFeature = {LV2_URID__map, LV2URIDMapper::getURIDMapFeature()};
    m_uridUnmapFeature = {LV2_URID__unmap, LV2URIDMapper::getURIDUnmapFeature()};

    // note the instance and data access features are
    // deprecated. However some plugins require them
    m_features.push_back(&m_uridMapFeature);
    m_features.push_back(&m_uridUnmapFeature);
    m_features.push_back(&m_idleFeature);
    m_features.push_back(&m_parentFeature);
    m_features.push_back(&m_resizeFeature);
    m_features.push_back(&m_instanceFeature);
    m_features.push_back(&m_dataFeature);
    m_features.push_back(&m_optionsFeature);
    m_features.push_back(&m_extHostFeature);
    m_features.push_back(nullptr);

    // Get the Idle Interface if any.
    const void* ii = nullptr;
    if (uidesc->extension_data)
        ii = uidesc->extension_data(LV2_UI__idleInterface);
    m_lv2II = (LV2UI_Idle_Interface*)ii;

    // Instantiate the UI.
    m_handle =
        uidesc->instantiate(uidesc,  // descriptor
                            id.toStdString().c_str(),  // plugin_uri
                            ubp,  // bundle_path
                            writeFn,  // write_function
                            this,  // controller
                            &m_widget,  // widget
                            m_features.data());  // features

    RG_DEBUG << "handle:" << m_handle << "widget:" << m_widget;

#ifdef HAVE_GTK2
    if (m_uiType == AudioPluginLV2GUI::GTK2) {
        LV2Gtk2* lv2gtk2 = LV2Gtk2::getInstance();
        m_gwidget = lv2gtk2->getWidget(m_widget, this);
        int width;
        int height;
        lv2gtk2->getSize(m_gwidget, width, height);
        RG_DEBUG << "gtk2 got size" << width << height;
        // If scaling is being used the window size must be reduced
        // here because qt will scale it up again. I believe this
        // applies for all plugins whether they support the
        // scaleFactor option or not.
        float scaleFactor =
            Rosegarden::RosegardenMainWindow::self()->
            screen()->devicePixelRatio();
        RG_DEBUG << "gtk2 resize adjust for scaleFactor" << scaleFactor;
        width /= scaleFactor;
        height /= scaleFactor;
        RG_DEBUG << "gtk2 resize adjusted size" << width << height;
        resize(width, height);

        const WId wid = (WId)(lv2gtk2->getWinId(m_gwidget));
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
        RG_DEBUG << "create gtk2 window from" << Qt::hex << wid;
#else
        RG_DEBUG << "create gtk2 window from" << hex << wid;
#endif
        m_parentWindow = QWindow::fromWinId(wid);
        m_parentWindow->setFlags(Qt::FramelessWindowHint);
        m_containerWidget = QWidget::createWindowContainer(m_parentWindow);
        m_containerWidget->setMinimumSize(QSize(width, height));
        m_containerWidget->setParent(this);
        RG_DEBUG << "got gtk2 window" << m_parentWindow <<
            m_parentWindow->parent();
    }
#else
    RG_DEBUG << "gtk2 UI not available";
#endif
}

AudioPluginLV2GUIWindow::~AudioPluginLV2GUIWindow()
{
    RG_DEBUG << "~AudioPluginLV2GUIWindow";
    activeWindows.erase(this);
    m_timer->stop();
    m_lv2II = nullptr;
}

void
AudioPluginLV2GUIWindow::portChange(uint32_t portIndex,
                                       uint32_t bufferSize,
                                       uint32_t portProtocol,
                                       const void *buffer)
{
    RG_DEBUG << "portChange" << portIndex <<
        bufferSize << portProtocol;
    m_lv2Gui->portChange(portIndex, bufferSize, portProtocol, buffer);
}

void AudioPluginLV2GUIWindow::showGui()
{
    if (m_uiType == AudioPluginLV2GUI::KX) {
        LV2_External_UI_Widget* euw = (LV2_External_UI_Widget*)m_widget;
        euw->show(euw);
    } else {
        show();
    }
}

LV2UI_Handle AudioPluginLV2GUIWindow::getHandle() const
{
    return m_handle;
}

void AudioPluginLV2GUIWindow::uiClosed()
{
    RG_DEBUG << "ui closed";

    // Signal slotTimeUp() that we need a close.
    // Can't do much here as this may be called from a different thread.
    m_shutdownRequested = true;
}

void AudioPluginLV2GUIWindow::setSize(int width, int height, bool isRequest)
{
    RG_DEBUG << "setSize" << width << height << isRequest;

    // Disallow shrinking of both dimensions.
    if (this->width() >= width && this->height() >= height) return;

    // If scaling is being used the window size must be reduced
    // here because qt will scale it up again. I believe this
    // applies for all plugins whether they support the
    // scaleFactor option or not.
    float scaleFactor =
        Rosegarden::RosegardenMainWindow::self()->
        screen()->devicePixelRatio();
    RG_DEBUG << "setSize adjust for scaleFactor" << scaleFactor;
    width /= scaleFactor;
    height /= scaleFactor;
    RG_DEBUG << "setSize adjusted size" << width << height;
    resize(width, height);
}

void AudioPluginLV2GUIWindow::slotTimeUp()
{
    // Handle shutdown.
    if (m_shutdownRequested) {
        RG_DEBUG << "slotTimeUp shutdown requested";
        m_timer->stop();
        // this will cuase this object to be deleted
        m_lv2Gui->closeUI();
        return;
    }

    // Call idle handler.
    if (m_lv2II) m_lv2II->idle(m_handle);

    // Update control outs.
    m_lv2Gui->updateControlOutValues();

    // For kx, call run().
    if (m_uiType == AudioPluginLV2GUI::KX) {
        LV2_External_UI_Widget* euw = (LV2_External_UI_Widget*)m_widget;
        euw->run(euw);
    }
}

void AudioPluginLV2GUIWindow::closeEvent(QCloseEvent* event)
{
    RG_DEBUG << "closeEvent";

    event->ignore();

    m_timer->stop();

    // tell the ui to tidy up
    if (m_parentWindow) m_parentWindow->setParent(nullptr);

#ifdef HAVE_GTK2
    if (m_uiType == AudioPluginLV2GUI::GTK2) {
        LV2Gtk2* lv2gtk2 = LV2Gtk2::getInstance();
        lv2gtk2->deleteWidget(m_gwidget);
    }
#endif

    // this will cause this object to be deleted
    m_lv2Gui->closeUI();
}


}
