/*
 * DISTHRO Plugin Toolkit (DPT)
 * Copyright (C) 2012 Filipe Coelho <falktx@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * For a full copy of the license see the GPL.txt file
 */

#include "DistrhoDefines.h"

#if defined(DISTRHO_PLUGIN_TARGET_LV2) && DISTRHO_PLUGIN_HAS_UI

#include "DistrhoUIInternal.h"

#include "lv2-sdk/lv2.h"
//#include "lv2-sdk/atom.h"
//#include "lv2-sdk/atom-util.h"
//#include "lv2-sdk/midi.h"
#include "lv2-sdk/patch.h"
#include "lv2-sdk/programs.h"
//#include "lv2-sdk/state.h"
#include "lv2-sdk/urid.h"
#include "lv2-sdk/ui.h"

#include <cassert>
#include <cstring>
#include <thread>

#ifdef DISTRHO_UI_QT4
# include <QtCore/QTimerEvent>
#endif

#ifndef DISTRHO_PLUGIN_URI
# error DISTRHO_PLUGIN_URI undefined!
#endif

#define DISTRHO_LV2UI_USE_EXTENSION_DATA (DISTRHO_PLUGIN_WANT_PROGRAMS)

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

START_NAMESPACE_DISTRHO

#ifdef DISTRHO_UI_QT4
class UILv2 : public QObject
#else
class UILv2
#endif
{
public:
    UILv2(intptr_t parent, LV2UI_Controller controller, LV2UI_Write_Function writeFunction, LV2UI_Widget* widget, const LV2_Feature* const* features)
        : ui(this, parent, setParameterCallback, setStateCallback, uiNoteCallback, uiResizeCallback),
          lv2Controller(controller),
          lv2WriteFunction(writeFunction),
          lv2UiResize(nullptr),
#if DISTRHO_PLUGIN_WANT_STATE
          uridIdPatchMessage(0),
#endif
#ifdef DISTRHO_UI_QT4
          uiTimer(0)
#else
          threadExitNow(false),
          threadRunning(false),
          thread(uiThreadCallback, this)
#endif
    {
        // Get Features
        for (uint32_t i = 0; features[i]; i++)
        {
            if (strcmp(features[i]->URI, LV2_URID__map) == 0)
            {
#if DISTRHO_PLUGIN_WANT_STATE
                LV2_URID_Map* uridMap = (LV2_URID_Map*)features[i]->data;
                uridIdPatchMessage = uridMap->map(uridMap->handle, LV2_PATCH__Message);
#endif
            }

            else if (strcmp(features[i]->URI, LV2_UI__resize) == 0)
                lv2UiResize = (LV2UI_Resize*)features[i]->data;
        }

#ifndef DISTRHO_UI_QT4
        assert(parent);

        if (parent == 0)
            return;
#endif

        if (lv2UiResize)
            lv2UiResize->ui_resize(lv2UiResize->handle, ui.getWidth(), ui.getHeight());

#ifdef DISTRHO_UI_QT4
        uiTimer = startTimer(30);
#endif

        *widget = (void*)ui.getWindowId();
    }

    ~UILv2()
    {
#ifdef DISTRHO_UI_QT4
        if (uiTimer)
            killTimer(uiTimer);
#else
        uiThreadClose();
#endif
    }

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

    void lv2ui_port_event(uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
    {
        if (format == 0)
        {
            if (bufferSize != sizeof(float))
                return;
            if (int32_t(portIndex - DISTRHO_PLUGIN_NUM_INPUTS - DISTRHO_PLUGIN_NUM_OUTPUTS) < 0)
                return;

            float value = *(float*)buffer;
            ui.parameterChanged(portIndex - DISTRHO_PLUGIN_NUM_INPUTS - DISTRHO_PLUGIN_NUM_OUTPUTS, value);
        }
        // TODO - atom events
    }

#if DISTRHO_PLUGIN_WANT_PROGRAMS
    void lv2ui_select_program(uint32_t bank, uint32_t program)
    {
        const uint32_t index = bank * 128 + program;
        ui.programChanged(index);
    }
#endif

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

protected:
    void setParameterValue(uint32_t index, float value)
    {
        if (lv2WriteFunction)
            lv2WriteFunction(lv2Controller, DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS + index, sizeof(float), 0, &value);
    }

    void setState(const char* key, const char* value)
    {
        // TODO
        (void)key;
        (void)value;

        //if (lv2WriteFunction && uridMap)
        {

        }
    }

    void uiResize(unsigned int width, unsigned int height)
    {
        if (lv2UiResize)
            lv2UiResize->ui_resize(lv2UiResize->handle, width, height);
    }

#ifdef DISTRHO_UI_QT4
    void timerEvent(QTimerEvent* event)
    {
        if (event->timerId() == uiTimer)
            ui.idle();

        QObject::timerEvent(event);
    }
#else
    void uiThreadRun()
    {
        threadRunning = true;

        while (! threadExitNow)
        {
            ui.idle();
            d_msleep(1000 / 25); // 25 FPS
        }

        thread.detach();
        threadRunning = false;
    }

    void uiThreadClose()
    {
        threadExitNow = true;

        while (threadRunning)
            d_msleep(1000 / 25); // 25 FPS
    }
#endif

private:
    UIInternal  ui;

    // LV2 Stuff
    LV2UI_Controller     const lv2Controller;
    LV2UI_Write_Function const lv2WriteFunction;
    const LV2UI_Resize*        lv2UiResize;

#if DISTRHO_PLUGIN_WANT_STATE
    LV2_URID uridIdPatchMessage;
#endif

#ifdef DISTRHO_UI_QT4
    int uiTimer;
#else
    // UI Thread
    bool threadExitNow;
    bool threadRunning;
    std::thread thread;
#endif

    // ---------------------------------------------
    // Callbacks

    static void setParameterCallback(void* ptr, uint32_t index, float value)
    {
        UILv2* _this_ = (UILv2*)ptr;
        assert(_this_);

        _this_->setParameterValue(index, value);
    }

    static void setStateCallback(void* ptr, const char* key, const char* value)
    {
#if DISTRHO_PLUGIN_WANT_STATE
        UILv2* _this_ = (UILv2*)ptr;
        assert(_this_);

        _this_->setState(key, value);
#else
        // unused
        (void)ptr;
        (void)key;
        (void)value;
#endif
    }

    static void uiNoteCallback(void* ptr, bool onOff, uint8_t channel, uint8_t note, uint8_t velocity)
    {
#if DISTRHO_PLUGIN_IS_SYNTH
        UILv2* _this_ = (UILv2*)ptr;
        assert(_this_);

        _this_->uiNote(onOff, channel, note, velocity);
#else
        // unused
        (void)ptr;
        (void)onOff;
        (void)channel;
        (void)note;
        (void)velocity;
#endif
    }

    static void uiResizeCallback(void* ptr, unsigned int width, unsigned int height)
    {
        UILv2* _this_ = (UILv2*)ptr;
        assert(_this_);

        _this_->uiResize(width, height);
    }

#ifndef DISTRHO_UI_QT4
    static void uiThreadCallback(void* ptr)
    {
        UILv2* _this_ = (UILv2*)ptr;
        assert(_this_);

        _this_->uiThreadRun();
    }
#endif
};

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

static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, const char* uri, const char*, LV2UI_Write_Function writeFunction, LV2UI_Controller controller, LV2UI_Widget* widget, const LV2_Feature* const* features)
{
    if (strcmp(uri, DISTRHO_PLUGIN_URI) != 0)
        return nullptr;

    // Get parent
    intptr_t parent = 0;

    for (uint32_t i = 0; features[i]; i++)
    {
        if (strcmp(features[i]->URI, LV2_UI__parent) == 0)
        {
            parent = (intptr_t)features[i]->data;
            break;
        }
    }

    return new UILv2(parent, controller, writeFunction, widget, features);
}

static void lv2ui_cleanup(LV2UI_Handle instance)
{
    UILv2* ui = (UILv2*)instance;
    assert(ui);

    delete ui;
}

static void lv2ui_port_event(LV2UI_Handle instance, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
{
    UILv2* ui = (UILv2*)instance;
    assert(ui);

    ui->lv2ui_port_event(portIndex, bufferSize, format, buffer);
}

#if DISTRHO_LV2UI_USE_EXTENSION_DATA
# if DISTRHO_PLUGIN_WANT_PROGRAMS
static void lv2ui_select_program(LV2_Handle instance, uint32_t bank, uint32_t program)
{
    UILv2* ui = (UILv2*)instance;
    assert(ui);

    ui->lv2ui_select_program(bank, program);
}
# endif

static const void* lv2ui_extension_data(const char* uri)
{
# if DISTRHO_PLUGIN_WANT_PROGRAMS
    static const LV2_Programs_UI_Interface programs = { lv2ui_select_program };
    if (strcmp(uri, LV2_PROGRAMS__UIInterface) == 0)
        return &programs;
# endif

    return nullptr;
}
#endif

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

static LV2UI_Descriptor uidescriptor = {
    DISTRHO_UI_URI,
    lv2ui_instantiate,
    lv2ui_cleanup,
    lv2ui_port_event,
#if DISTRHO_LV2UI_USE_EXTENSION_DATA
    lv2ui_extension_data
#else
    /* extension_data */ nullptr
#endif
};

END_NAMESPACE_DISTRHO

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

DISTRHO_PLUGIN_EXPORT
const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index)
{
    USE_NAMESPACE_DISTRHO
    return (index == 0) ? &uidescriptor : nullptr;
}

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

#endif // DISTRHO_PLUGIN_TARGET_LV2 && DISTRHO_PLUGIN_HAS_UI
