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

#ifndef __CARLA_PLUGIN_INTERNAL_HPP__
#define __CARLA_PLUGIN_INTERNAL_HPP__

#include "CarlaBackendUtils.hpp"
#include "CarlaPluginThread.hpp"
#include "CarlaPlugin.hpp"
#include "CarlaEngine.hpp"
#include "CarlaOscUtils.hpp"
#include "CarlaStateUtils.hpp"
#include "CarlaMutex.hpp"
#include "CarlaMIDI.h"

#include "RtList.hpp"
#include "dgl/Window.hpp"

#define CARLA_DECLARE_NON_COPY_STRUCT(structName) \
    structName(structName&) = delete;             \
    structName(const structName&) = delete;

#define CARLA_DECLARE_NON_COPY_STRUCT_WITH_LEAK_DETECTOR(structName) \
    CARLA_DECLARE_NON_COPY_STRUCT(structName)                        \
    CARLA_LEAK_DETECTOR(structName)

#define CARLA_PROCESS_CONTINUE_CHECK if (! fEnabled) { kData->engine->callback(CALLBACK_DEBUG, fId, 0, 0, 0.0, nullptr); return; }

CARLA_BACKEND_START_NAMESPACE

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

const unsigned short MAX_RT_EVENTS   = 128;
const unsigned short MAX_MIDI_EVENTS = 512;

const unsigned int PLUGIN_HINT_HAS_MIDI_IN  = 0x1;
const unsigned int PLUGIN_HINT_HAS_MIDI_OUT = 0x2;
const unsigned int PLUGIN_HINT_CAN_RUN_RACK = 0x4;

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

struct PluginAudioPort {
    uint32_t rindex;
    CarlaEngineAudioPort* port;

    PluginAudioPort()
        : rindex(0),
          port(nullptr) {}

    ~PluginAudioPort()
    {
        CARLA_ASSERT(port == nullptr);
    }

    CARLA_DECLARE_NON_COPY_STRUCT_WITH_LEAK_DETECTOR(PluginAudioPort)
};

struct PluginAudioData {
    uint32_t count;
    PluginAudioPort* ports;

    PluginAudioData()
        : count(0),
          ports(nullptr) {}

    ~PluginAudioData()
    {
        CARLA_ASSERT_INT(count == 0, count);
        CARLA_ASSERT(ports == nullptr);
    }

    void createNew(const uint32_t newCount)
    {
        CARLA_ASSERT_INT(count == 0, count);
        CARLA_ASSERT(ports == nullptr);
        CARLA_ASSERT_INT(newCount > 0, newCount);

        if (ports != nullptr || newCount == 0)
            return;

        ports = new PluginAudioPort[newCount];
        count = newCount;
    }

    void clear()
    {
        if (ports != nullptr)
        {
            for (uint32_t i=0; i < count; i++)
            {
                if (ports[i].port != nullptr)
                {
                    delete ports[i].port;
                    ports[i].port = nullptr;
                }
            }

            delete[] ports;
            ports = nullptr;
        }

        count = 0;
    }

    void initBuffers(CarlaEngine* const engine)
    {
        for (uint32_t i=0; i < count; i++)
        {
            if (ports[i].port != nullptr)
                ports[i].port->initBuffer(engine);
        }
    }

    CARLA_DECLARE_NON_COPY_STRUCT_WITH_LEAK_DETECTOR(PluginAudioData)
};

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

struct PluginEventData {
    CarlaEngineEventPort* portIn;
    CarlaEngineEventPort* portOut;

    PluginEventData()
        : portIn(nullptr),
          portOut(nullptr) {}

    ~PluginEventData()
    {
        CARLA_ASSERT(portIn == nullptr);
        CARLA_ASSERT(portOut == nullptr);
    }

    void clear()
    {
        if (portIn != nullptr)
        {
            delete portIn;
            portIn = nullptr;
        }

        if (portOut != nullptr)
        {
            delete portOut;
            portOut = nullptr;
        }
    }

    void initBuffers(CarlaEngine* const engine)
    {
        if (portIn != nullptr)
            portIn->initBuffer(engine);

        if (portOut != nullptr)
            portOut->initBuffer(engine);
    }

    CARLA_DECLARE_NON_COPY_STRUCT_WITH_LEAK_DETECTOR(PluginEventData)
};

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

struct PluginParameterData {
    uint32_t count;
    ParameterData*   data;
    ParameterRanges* ranges;

    PluginParameterData()
        : count(0),
          data(nullptr),
          ranges(nullptr) {}

    ~PluginParameterData()
    {
        CARLA_ASSERT_INT(count == 0, count);
        CARLA_ASSERT(data == nullptr);
        CARLA_ASSERT(ranges == nullptr);
    }

    void createNew(const uint32_t newCount)
    {
        CARLA_ASSERT_INT(count == 0, count);
        CARLA_ASSERT(data == nullptr);
        CARLA_ASSERT(ranges == nullptr);
        CARLA_ASSERT_INT(newCount > 0, newCount);

        if (data != nullptr || ranges != nullptr || newCount == 0)
            return;

        data   = new ParameterData[newCount];
        ranges = new ParameterRanges[newCount];
        count  = newCount;
    }

    void clear()
    {
        if (data != nullptr)
        {
            delete[] data;
            data = nullptr;
        }

        if (ranges != nullptr)
        {
            delete[] ranges;
            ranges = nullptr;
        }

        count = 0;
    }

    float fixValue(const uint32_t parameterId, const float& value)
    {
        CARLA_ASSERT(parameterId < count);
        return ranges[parameterId].fixValue(value);
    }

    CARLA_DECLARE_NON_COPY_STRUCT_WITH_LEAK_DETECTOR(PluginParameterData)
};

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

typedef const char* ProgramName;

struct PluginProgramData {
    uint32_t count;
    int32_t  current;
    ProgramName* names;

    PluginProgramData()
        : count(0),
          current(-1),
          names(nullptr) {}

    ~PluginProgramData()
    {
        CARLA_ASSERT_INT(count == 0, count);
        CARLA_ASSERT_INT(current == -1, current);
        CARLA_ASSERT(names == nullptr);
    }

    void createNew(const uint32_t newCount)
    {
        CARLA_ASSERT_INT(count == 0, count);
        CARLA_ASSERT_INT(current == -1, current);
        CARLA_ASSERT(names == nullptr);
        CARLA_ASSERT_INT(newCount > 0, newCount);

        if (names != nullptr || newCount == 0)
            return;

        names = new ProgramName[newCount];
        count = newCount;

        for (uint32_t i=0; i < newCount; i++)
            names[i] = nullptr;
    }

    void clear()
    {
        if (names != nullptr)
        {
            for (uint32_t i=0; i < count; i++)
            {
                if (names[i] != nullptr)
                    delete[] names[i];
            }

            delete[] names;
            names = nullptr;
        }

        count = 0;
        current = -1;
    }

    CARLA_DECLARE_NON_COPY_STRUCT_WITH_LEAK_DETECTOR(PluginProgramData)
};

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

struct PluginMidiProgramData {
    uint32_t count;
    int32_t  current;
    MidiProgramData* data;

    PluginMidiProgramData()
        : count(0),
          current(-1),
          data(nullptr) {}

    ~PluginMidiProgramData()
    {
        CARLA_ASSERT_INT(count == 0, count);
        CARLA_ASSERT_INT(current == -1, current);
        CARLA_ASSERT(data == nullptr);
    }

    void createNew(const uint32_t newCount)
    {
        CARLA_ASSERT_INT(count == 0, count);
        CARLA_ASSERT_INT(current == -1, current);
        CARLA_ASSERT(data == nullptr);
        CARLA_ASSERT_INT(newCount > 0, newCount);

        if (data != nullptr || newCount == 0)
            return;

        data  = new MidiProgramData[newCount];
        count = newCount;
    }

    void clear()
    {
        if (data != nullptr)
        {
            delete[] data;
            data = nullptr;
        }

        count = 0;
        current = -1;
    }

    const MidiProgramData& getCurrent() const
    {
        CARLA_ASSERT(current >= 0 && current < static_cast<int32_t>(count));
        return data[current];
    }

    CARLA_DECLARE_NON_COPY_STRUCT_WITH_LEAK_DETECTOR(PluginMidiProgramData)
};

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

struct PluginPostRtEvent {
    PluginPostRtEventType type;
    int32_t value1;
    int32_t value2;
    float   value3;

    PluginPostRtEvent()
        : type(kPluginPostRtEventNull),
          value1(-1),
          value2(-1),
          value3(0.0f) {}

#if 1//def DEBUG
    CARLA_DECLARE_NON_COPY_STRUCT_WITH_LEAK_DETECTOR(PluginPostRtEvent)
#else
    CARLA_DECLARE_NON_COPY_STRUCT(PluginPostRtEvent)
#endif
};

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

struct ExternalMidiNote {
    int8_t  channel; // invalid = -1
    uint8_t note;
    uint8_t velo;

    ExternalMidiNote()
        : channel(-1),
          note(0),
          velo(0) {}

#if 1//def DEBUG
    CARLA_DECLARE_NON_COPY_STRUCT_WITH_LEAK_DETECTOR(ExternalMidiNote)
#else
    CARLA_DECLARE_NON_COPY_STRUCT(ExternalMidiNote)
#endif
};

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

class CarlaPluginGUI : public DGL::Window
{
public:
    class Callback
    {
    public:
        virtual ~Callback() {}
        virtual void guiClosedCallback() = 0;
    };

    CarlaPluginGUI(DGL::App* const app, Callback* const callback);
    ~CarlaPluginGUI();

//protected:
    //void closeEvent(QCloseEvent* const event);

private:
    Callback* const kCallback;

    CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginGUI)
};

// -----------------------------------------------------------------------
// Engine Helpers, defined in CarlaEngine.cpp

extern DGL::App* getEngineApp(CarlaEngine* const engine);

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

struct CarlaPluginProtectedData {
    CarlaEngine* const engine;
    CarlaEngineClient* client;
    CarlaPluginGUI* gui;

    bool active;
    bool activeBefore;
    bool needsReset;
    void* lib;

    // misc
    int8_t       ctrlChannel;
    unsigned int extraHints;

    // latency
    uint32_t latency;
    float**  latencyBuffers;

    // data
    PluginAudioData audioIn;
    PluginAudioData audioOut;
    PluginEventData event;
    PluginParameterData param;
    PluginProgramData prog;
    PluginMidiProgramData midiprog;
    NonRtList<CustomData> custom;

    CarlaMutex masterMutex; // global master lock
    CarlaMutex singleMutex; // small lock used only in processSingle()

    struct ExternalNotes {
        CarlaMutex mutex;
        RtList<ExternalMidiNote>::Pool dataPool;
        RtList<ExternalMidiNote> data;

        ExternalNotes()
            : dataPool(32, 152),
              data(&dataPool) {}

        ~ExternalNotes()
        {
            mutex.lock();
            data.clear();
            mutex.unlock();
        }

        void append(const ExternalMidiNote& note)
        {
            mutex.lock();
            data.append_sleepy(note);
            mutex.unlock();
        }

        ExternalNotes(ExternalNotes&) = delete;
        ExternalNotes(const ExternalNotes&) = delete;

    } extNotes;

    struct PostRtEvents {
        CarlaMutex mutex;
        RtList<PluginPostRtEvent>::Pool dataPool;
        RtList<PluginPostRtEvent> data;
        RtList<PluginPostRtEvent> dataPendingRT;

        PostRtEvents()
            : dataPool(MAX_RT_EVENTS, MAX_RT_EVENTS),
              data(&dataPool),
              dataPendingRT(&dataPool) {}

        ~PostRtEvents()
        {
            clear();
        }

        void appendRT(const PluginPostRtEvent& event)
        {
            dataPendingRT.append(event);
        }

        void trySplice()
        {
            if (mutex.tryLock())
            {
                dataPendingRT.spliceAppend(data, true);
                mutex.unlock();
            }
        }

        void clear()
        {
            mutex.lock();
            data.clear();
            dataPendingRT.clear();
            mutex.unlock();
        }

        PostRtEvents(PostRtEvents&) = delete;
        PostRtEvents(const PostRtEvents&) = delete;

    } postRtEvents;

    struct PostProc {
        float dryWet;
        float volume;
        float balanceLeft;
        float balanceRight;
        float panning;

        PostProc()
            : dryWet(1.0f),
              volume(1.0f),
              balanceLeft(-1.0f),
              balanceRight(1.0f),
              panning(0.0f) {}

        PostProc(PostProc&) = delete;
        PostProc(const PostProc&) = delete;

    } postProc;

    struct OSC {
        CarlaOscData data;
        CarlaPluginThread thread;

        OSC(CarlaEngine* const engine, CarlaPlugin* const plugin)
            : thread(engine, plugin) {}

        OSC() = delete;
        OSC(OSC&) = delete;
        OSC(const OSC&) = delete;

    } osc;

    CarlaPluginProtectedData(CarlaEngine* const engine_, CarlaPlugin* const plugin)
        : engine(engine_),
          client(nullptr),
          gui(nullptr),
          active(false),
          activeBefore(false),
          needsReset(false),
          lib(nullptr),
          ctrlChannel(0),
          extraHints(0x0),
          latency(0),
          latencyBuffers(nullptr),
          osc(engine, plugin) {}

    CarlaPluginProtectedData() = delete;
    CarlaPluginProtectedData(CarlaPluginProtectedData&) = delete;
    CarlaPluginProtectedData(const CarlaPluginProtectedData&) = delete;

    void createUiIfNeeded(CarlaPluginGUI::Callback* const callback)
    {
        if (gui != nullptr)
            return;

        gui = new CarlaPluginGUI(getEngineApp(engine), callback);
    }

    void destroyUiIfNeeded()
    {
        if (gui == nullptr)
            return;

        gui->hide();
        delete gui;
        gui = nullptr;
    }

    static CarlaEngine* getEngine(CarlaPlugin* const plugin)
    {
        return plugin->kData->engine;
    }

    static CarlaEngineAudioPort* getAudioInPort(CarlaPlugin* const plugin, const uint32_t index)
    {
        return plugin->kData->audioIn.ports[index].port;
    }

    static CarlaEngineAudioPort* getAudioOutPort(CarlaPlugin* const plugin, const uint32_t index)
    {
        return plugin->kData->audioOut.ports[index].port;
    }

    static bool canRunInRack(CarlaPlugin* const plugin)
    {
        return (plugin->kData->extraHints & PLUGIN_HINT_CAN_RUN_RACK);
    }

    CARLA_LEAK_DETECTOR(CarlaPluginProtectedData)
};

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

CARLA_BACKEND_END_NAMESPACE

#endif // __CARLA_PLUGIN_INTERNAL_HPP__
