#ifndef AUDIOPLUGIN_H
#define AUDIOPLUGIN_H

#include <math.h>

#include <lv2.h>
#ifdef _WIN32
#include <lilv/lilv.h>
#include "lv2/event/event.h"
#include "lv2/atom/atom.h"
#include "lv2/worker/worker.h"
#else
#include <lilv-0/lilv/lilv.h>
#include <lv2/lv2plug.in/ns/ext/event/event.h>
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
#endif

#include <QObject>
#include <QMutex>
#include <QDebug>


enum pluginType {
    pluginNone = 0,
    pluginDynamics = 1,
    pluginTimebase = 2,
    pluginGenerator = 3,
    pluginUtility = 4
};

enum pluginPathType {
    pluginPathNone = 0,
    pluginPathReceiveAudio,
    pluginPathTransmitAudio,
    pluginPathStreamingAudio,
    pluginPathUtilityAudio
};

// A vector of controlsType will get sent to the UI for setup.
// We can also pass these back and forth, using the portIndex
// and value to set the controls locally.

struct controlsType {
    uint64_t instanceId;
    unsigned int controlsIndex = 0;
    unsigned int portIndex = 0;
    float value = 0.0f;
    // Setup:
    float min = 0.0f;
    float max = 0.0f;
    float def = 0.0f;
    bool audioTaper = false;
    QString label;
};

class audioPlugin;

struct pluginInstanceInfoType {
    uint64_t instanceID = 0; // currently this is the address of the audioPlugin class running the plugin.
    QString pluginName;
    QString pluginURI;
    pluginPathType path = pluginPathNone; // TX or RX path, basically.
    unsigned char chainPosition; // 0 = start of plugin chain
    bool isLoaded = false; // set to true to indicate unloading a plugin
    audioPlugin* plugin = NULL; // this is a pointer to the audioPlugin class running the plugin.
};


typedef struct URITable {
    char** uris;
    size_t n_uris;
} URITable;


typedef enum { TYPE_CONTROL, TYPE_AUDIO, TYPE_ATOM, TYPE_EVENT } PortType;

typedef struct Param {
  const char* sym;   ///< Port symbol
  float       value; ///< Control value
} Param;

typedef struct {
  const LilvPort* lilv_port; ///< Port description
  PortType        type;      ///< Datatype
  uint32_t        index;     ///< Port index
  float           value;     ///< Control value (if applicable)
  bool            is_input;  ///< True iff an input port
  bool            optional;  ///< True iff connection optional
} Port;

class audioPlugin : public QObject
{
    Q_OBJECT

public:
    explicit audioPlugin(int maxBufferSizeNeeded, pluginPathType audioChain,
                         unsigned char chainPosition, QObject *parent = nullptr);
    ~audioPlugin();

    void setupPlugin(char* pluginURI);

    void setControlsPointer();

    // Maybe these should be uint16 buffers, stereo interlaced?
    void setInputBuffer(char* stereoInterlacedInputBuffer);
    void setOutputBuffer(char* stereoInterlacedOutputBuffer);
    int16_t *getInputBuffer();
    int16_t *getOutputBuffer();

signals:
    void pluginLoaded(pluginInstanceInfoType info);
    void processedAudioReady(); // might not use this...
    void pluginErrorMessage(QString errorMessage);
    void pluginStatusMessage(QString statusMessage);
    void haveAudioLevels(float inRMS, float inPeak, float outRMS, float outPeak);
    void haveControlPorts(QVector<controlsType> controls);

public slots:
    // For performance reasons, do not call runPlugin with the signal/slot mechanism.
    // Only call this function if the buffers have already been connected.
    // The function will load from the input buffer, and alter the output buffer.
    void runPlugin(int samples);
    void setBypass(bool bypass);

    void getPluginControlPorts(pluginInstanceInfoType info);
    void handleAdjustedPluginControls(controlsType control);

    // These functions are not complete yet:
    void selectPluginByName(QString pluginName);
    void selectPluginByLabel(QString pluginLabel);
    void selectPluginByID(int pluginId);

    void debugThis(); // nice debug info here

private:
    uint64_t instanceId;
    QString instanceString;
    pluginType kind;
    pluginPathType audioChain;
    unsigned char chainPosition;
    pluginInstanceInfoType info;
    QMutex pluginRunLocker;
    bool forceOutputMono = true;
    bool computeLevels = false;
    bool inBypassMode;
    int bufferSize;
    int externalBufferSize;
    bool haveAllocatedInternalBuffers = false;
    bool haveExternalSourceBuffer = false;
    bool haveExternalSinkBuffer = false;
    bool haveLoadedPlugin = false;
    bool haveActivatedPlugin = false;

    // These two buffers are allocated *external* to the audioPlugin class,
    // and are expected to be acceptable size-wise, at minimum, per the argument to runPlugin.
    // The format used internally here is unsigned 16-bit interleved stereo.
    // The external format is split 8-8 interleved stereo.
    int16_t *externalSourceBuffer;
    int16_t *externalSinkBuffer;

    float *inputBuffer[2];
    float *outputBuffer[2];
    void allocateBuffers();
    void deallocateBuffers();
    void convertInputBuffer();
    void convertOutputBuffer();

    uint16_t externalSampleCount; // from the interleved 8-8 QByteArray.length()
    uint16_t sourceBufferSampleCount; // size of our 16-bit casted array of same data
    uint16_t inputBufferSampleCount; // used size of the float buffers, per 16-bit channel.

    float computeBufferRMS(float *buffer);
    float inRMS = 0.0f;
    float inPeak = 0.0f;
    float outRMS = 0.0f;
    float outPeak = 0.0f;

    void unloadPlugin(); // deactivate, unload, and free

    void initPluginWorld();
    bool loadPlugin(char *pluginURI);
    void findPlugin();
    bool createPorts();

    void createControls();
    void deleteControls();
    void copyControlsToPluginInterface();

    bool activatePlugin();
    // Plugin world items:
    LilvNode* gControlPortClass;
    LilvWorld* world = NULL;
    const LilvPlugins *plugins;
    const LilvPlugin *plugin;
    LilvNode *uri = NULL;
    LilvNode *pluginName;
    QString pluginNameAsQString;
    QString pluginURIAsQString;
    LilvInstance *instance;
    unsigned int n_params;
    Param *params;
    unsigned int n_ports;
    unsigned int n_audio_in;
    unsigned int n_audio_out;
    unsigned int sampleRate = 48000;
    Port *ports;
    Port *portsExternal; // ports for UI adjustments
    QVector <controlsType> controls;

    QString getPLuginControlInfo();
    uint16_t byteTable[65535];
    void makeByteTable();
    uint16_t byteSwapper();

    /* URID MAP FEATURE */
    
    LV2_Feature *features[3];

    static LV2_URID urid_map(void* handle, const char* uri);
    static const char* urid_unmap(void* handle, LV2_URID urid);

    std::map<std::string, LV2_URID> urid_map_data;
    LV2_URID current_urid = 1; /* 0 is reserverd */

    LV2_URID_Map feature_uri_map_data;
    LV2_Feature feature_uri_map;

    LV2_URID_Unmap feature_uri_unmap_data;
    LV2_Feature feature_uri_unmap;

    LV2_Feature feature_worker;

};

Q_DECLARE_METATYPE(struct pluginInstanceInfoType)
Q_DECLARE_METATYPE(struct controlsType)

#endif // AUDIOPLUGIN_H
