#include "audioplugin.h"

//#include <lilv-0/lilv/lilv.h>

//#include <lv2.h>
#include "logcategories.h"



audioPlugin::audioPlugin(int maxBufferSizeNeeded, pluginPathType path,
                         unsigned char chainPosition, QObject *parent) : QObject(parent)
{
    // Note: the size is for "interlaced" 2-channel 8-bit data, as is used outside of this plugin.
    // Please do not set this to an odd number.

    // We need each of our 16-bit one-channel buffers to be four times smaller in size.

    if(maxBufferSizeNeeded==0)
    {
        bufferSize = 2728/2; // seems like an acceptable default value
    } else {
        bufferSize = maxBufferSizeNeeded/4;
    }

    this->audioChain = path;
    this->chainPosition = chainPosition;

    instanceId = (uint64_t)this;
    instanceString = QString("0x%1").arg(instanceId, 16, 16, QChar('0'));

    inBypassMode = false;

    info.chainPosition = chainPosition;
    info.instanceID = instanceId;
    info.path = audioChain;

    pluginURIAsQString = QString("unset");

    externalSourceBuffer = NULL;
    externalSinkBuffer = NULL;

    sampleRate = 48000;

    makeByteTable();
    allocateBuffers();
    initPluginWorld();
}

audioPlugin::~audioPlugin()
{
    // free plugin helpers
    unloadPlugin();
    // TODO: consider locking the runPlugin carefully

    // free buffers
    deallocateBuffers();
}

void audioPlugin::setInputBuffer(char *stereoInterlacedInputBuffer)
{
    if(stereoInterlacedInputBuffer == NULL)
    {
        emit pluginErrorMessage("Could not use NULL input buffer.");
        haveExternalSourceBuffer = false;
        externalSourceBuffer = NULL;
    } else {
        this->externalSourceBuffer = (int16_t*)stereoInterlacedInputBuffer;
        haveExternalSourceBuffer = true;
    }
}

void audioPlugin::setOutputBuffer(char *stereoInterlacedOutputBuffer)
{
    if(stereoInterlacedOutputBuffer == NULL)
    {
        emit pluginErrorMessage("Could not use NULL output buffer.");
        haveExternalSinkBuffer = false;
        externalSinkBuffer = NULL;
    } else {
        this->externalSinkBuffer = (int16_t*)stereoInterlacedOutputBuffer;
        haveExternalSinkBuffer = true;
    }
}

int16_t* audioPlugin::getInputBuffer()
{
    return externalSourceBuffer;
}

int16_t* audioPlugin::getOutputBuffer()
{
    return externalSinkBuffer;
}

void audioPlugin::selectPluginByID(int pluginId)
{
    (void)pluginId;
}

void audioPlugin::selectPluginByLabel(QString pluginLabel)
{
    (void)pluginLabel;
}

void audioPlugin::selectPluginByName(QString pluginName)
{
    (void)pluginName;
}

void audioPlugin::allocateBuffers()
{
    // Internal use float buffers for audio
    // These buffers may be larger than required, but must not be smaller.
    // They will be accessed based on the size of the audio samples passing through.
    if(haveAllocatedInternalBuffers)
    {
        // realloc?
        emit pluginErrorMessage("WARNING: asked to reallocate buffer memory! This feature has not been implemented yet.");
    } else {
        inputBuffer[0]= (float*)malloc(sizeof(float) * bufferSize);
        inputBuffer[1]= (float*)malloc(sizeof(float) * bufferSize);

        outputBuffer[0]= (float*)malloc(sizeof(float) * bufferSize);
        outputBuffer[1]= (float*)malloc(sizeof(float) * bufferSize);

        haveAllocatedInternalBuffers = true;
    }
}

void audioPlugin::deallocateBuffers()
{
    if(haveAllocatedInternalBuffers)
    {
        haveAllocatedInternalBuffers = false;
        free(inputBuffer);
        free(outputBuffer);
    } else {
        emit pluginErrorMessage("WARNING: asked to deallocate memory that has not been allocated!");
    }
}

void audioPlugin::initPluginWorld()
{
    // Setup the "world" for the plugin functions:
    world = lilv_world_new();
    lilv_world_load_all(world);
    plugins = lilv_world_get_all_plugins(world);
    gControlPortClass = lilv_new_uri(world, LV2_CORE__ControlPort);
    // TODO: if thing == NULL... emit fail
}

bool audioPlugin::activatePlugin()
{
    // TODO: unactivate and unload reset bools
    if(haveLoadedPlugin && !haveActivatedPlugin)
    {
        lilv_instance_activate(instance);
        haveActivatedPlugin = true;
        emit pluginStatusMessage(QString("[%1] activated plugin.").arg(instanceString));
        return true;
    } else {
        emit pluginErrorMessage(QString("Cannot activate plugin that has not been loaded."));
        return false;
    }
}


void audioPlugin::setupPlugin(char *pluginURI)
{
    // This function loads exactly the URI it is told to load.
    // Future versions might include other, easier methods.
    bool loadedOk = false;
    bool activatedOk = false;
    this->pluginURIAsQString = QString("%1").arg(pluginURI);
    loadedOk = loadPlugin(pluginURI);
    haveLoadedPlugin = loadedOk;

    activatedOk = activatePlugin();

    info.isLoaded = loadedOk && activatedOk;
    info.pluginName = pluginNameAsQString;
    info.plugin = this;
    info.pluginURI = this->pluginURIAsQString;
    emit pluginLoaded(info);
}

bool audioPlugin::loadPlugin(char *pluginURI)
{
    bool success = false;
    lilv_plugins_get_by_uri(plugins, uri);

    uri = lilv_new_uri(world, pluginURI);
    plugin  = lilv_plugins_get_by_uri(plugins, uri);
    if(plugin == NULL)
    {
        success = false;
        this->info.isLoaded = false;
        emit pluginLoaded(this->info);
        emit pluginErrorMessage(QString("Could not find plugin by URI [%1]").arg(pluginURI));
        pluginNameAsQString = "";
        haveLoadedPlugin = false;
        return success;
    }

    pluginName = lilv_plugin_get_name(plugin);
    pluginNameAsQString = QString("%1").arg(lilv_node_as_string(pluginName));
    emit pluginStatusMessage(QString("Loaded plugin named [%1] from URI [%2]").arg(lilv_node_as_string(pluginName)).arg(pluginURI));
    success = true;

    success = createPorts();

    if(n_audio_in == 0 || n_audio_in > 2)
    {
        success = false;
        this->info.isLoaded = false;
        emit pluginLoaded(this->info);
        emit pluginErrorMessage(QString("Number of audio input ports (%1) for plugin [%2] is not compatible.")\
                                .arg(n_audio_in).arg(lilv_node_as_string(pluginName)));
        return success;
    }



    const uint32_t n_portsLoop = lilv_plugin_get_num_ports(plugin);
;
    feature_uri_map_data = { this, audioPlugin::urid_map };
    feature_uri_map = { LV2_URID__map, &feature_uri_map_data };

    feature_uri_unmap_data = { this, audioPlugin::urid_unmap };
    feature_uri_unmap = { LV2_URID__unmap, &feature_uri_unmap_data };

    feature_worker = { LV2_WORKER__schedule, (LV2_Worker_Schedule*)malloc(sizeof(LV2_Worker_Schedule)) };

    features[0] = &feature_uri_map;
    features[1] = &feature_uri_unmap;
    features[2] = &feature_worker;

    instance = lilv_plugin_instantiate(plugin, sampleRate, features);
    // The NULL is "features" ??

    if(instance == NULL)
    {
        success = false;
        emit pluginErrorMessage(QString("Plugin instance was NULL for plugin with name [%1]").arg(pluginNameAsQString));
        return success;
    }



    // This is where the controls, input buffer, and output buffer, are connected
    // into the plugin instance using pointers.

    for (uint32_t p = 0, i = 0, o = 0; p < n_portsLoop; ++p) {
      if (ports[p].type == TYPE_CONTROL) {
        lilv_instance_connect_port(instance, p, &ports[p].value);
      } else if (ports[p].type == TYPE_AUDIO) {
        if (ports[p].is_input) {
          lilv_instance_connect_port(instance, p, inputBuffer[ i++ ]);
        } else {
          lilv_instance_connect_port(instance, p, outputBuffer[ o++ ]);
        }
      } else {
        lilv_instance_connect_port(instance, p, NULL);
      }
    }

    success = true;

    return success;

}

bool audioPlugin::createPorts()
{
    bool success = false;

    n_ports = lilv_plugin_get_num_ports(plugin);
    n_audio_in = 0;
    n_audio_out = 0;

    emit pluginStatusMessage(QString("Creating ports, number of ports returned: %1").arg(n_ports));
    ports = (Port*)calloc(n_ports, sizeof(Port));
    float *values = (float*)calloc(n_ports, sizeof(float)); // default values actually
    float *minValues = (float*)calloc(n_ports, sizeof(float));
    float *maxValues = (float*)calloc(n_ports, sizeof(float));
    controlsType singleControl;
    controls.clear();
    unsigned int controlNumber = 0;

    lilv_plugin_get_port_ranges_float(plugin, minValues, maxValues, values);
    // The two NULL above contain pointers to float arrays holding max and min values for the controls.

    LilvNode* lv2_InputPort   = lilv_new_uri(world, LV2_CORE__InputPort);
    LilvNode* lv2_OutputPort  = lilv_new_uri(world, LV2_CORE__OutputPort);
    LilvNode* lv2_AudioPort   = lilv_new_uri(world, LV2_CORE__AudioPort);
    LilvNode* lv2_ControlPort = lilv_new_uri(world, LV2_CORE__ControlPort);
    LilvNode* lv2_EventPort = lilv_new_uri(world, LV2_EVENT__EventPort);
    LilvNode* lv2_AtomPort = lilv_new_uri(world, LV2_ATOM__AtomPort);
    LilvNode* lv2_connectionOptional = lilv_new_uri(world, LV2_CORE__connectionOptional);
    LilvNode* lv2_urid_map = lilv_new_uri(world, LV2_URID__map);
    LilvNode* lv2_urid_unmap = lilv_new_uri(world, LV2_URID__unmap);
    LilvNode* lv2_worker_schedule = lilv_new_uri(world, LV2_WORKER__schedule);


    for (uint32_t i = 0; i < n_ports; ++i) {
        Port *port = &ports[i];
        const LilvPort *lport = lilv_plugin_get_port_by_index(plugin, i);
        port->lilv_port = lport;
        port->index = i;
        port->value = isnan(values[i]) ? 0.0f : values[i];
        port->optional = lilv_port_has_property(plugin, lport, lv2_connectionOptional);

        // Check if input or output:
        if (lilv_port_is_a(plugin, lport, lv2_InputPort)) {
            port->is_input = true;
        } else if (!lilv_port_is_a(plugin, lport, lv2_OutputPort) && !port->optional)
        {
            success = false;
            emit pluginErrorMessage(QString("Port %1 is neither input nor output").arg(i));
            return success;
        }

        // Check if port is audio or control:
        if (lilv_port_is_a(plugin, lport, lv2_ControlPort)) {
            port->type = TYPE_CONTROL;
            singleControl.instanceId = instanceId;
            singleControl.portIndex = i;
            singleControl.controlsIndex = controlNumber++;
            singleControl.def = isnan(values[i]) ? 0.0f : values[i];
            singleControl.min = isnan(minValues[i]) ? 0.0f : minValues[i];
            singleControl.max = isnan(maxValues[i]) ? 0.0f : maxValues[i];
            singleControl.value = singleControl.def; // start at default value
            singleControl.label = QString("%1").arg(lilv_node_as_string(lilv_port_get_name(plugin, lport)));
            controls.push_back(singleControl);
        }
        else if (lilv_port_is_a(plugin, lport, lv2_AudioPort)) {
            port->type = TYPE_AUDIO;
            if (port->is_input) {
                ++n_audio_in;
            }
            else {
                ++n_audio_out;
            }
        }
        else if (lilv_port_is_a(plugin, lport, lv2_EventPort)) {
            emit pluginStatusMessage(QString("[%1] is an event port").arg(i));
            port->type = TYPE_EVENT;
        }
        else if (lilv_port_is_a(plugin, lport, lv2_AtomPort)) {
            emit pluginStatusMessage(QString("[%1] is an atom port").arg(i));
            port->type = TYPE_ATOM;
        }
        else if (!port->optional)
        {
            success = false;
            emit pluginErrorMessage(QString("Port %1 has an unsupported type.").arg(i));
            return success;
        }
    }

    if (lilv_plugin_has_feature(plugin, lv2_urid_map)) {
        emit pluginStatusMessage(QString("Plugin is requesting urid_map feature"));
    }
    if (lilv_plugin_has_feature(plugin, lv2_urid_unmap)) {
        emit pluginStatusMessage(QString("Plugin is requesting urid_unmap feature"));
    }
    if (lilv_plugin_has_feature(plugin, lv2_worker_schedule)) {
        emit pluginStatusMessage(QString("Plugin is requesting worker_schedule feature"));
    }

    // At this point, the plugin is "discovered" and the temporary variables can be free'd:

    lilv_node_free(lv2_connectionOptional);
    lilv_node_free(lv2_ControlPort);
    lilv_node_free(lv2_AudioPort);
    lilv_node_free(lv2_OutputPort);
    lilv_node_free(lv2_InputPort);
    lilv_node_free(lv2_AtomPort);

    free(values);
    free(minValues);
    free(maxValues);

    // What persists is the "ports" holder, which is now populated,
    // as well as the counters for input and output ports.

    success = true;


    return success;
}


void audioPlugin::unloadPlugin()
{
    haveActivatedPlugin = false;
    haveLoadedPlugin = false;

    lilv_instance_deactivate(instance);
    lilv_world_free(world);
    emit pluginStatusMessage(QString("[%1] unloaded plugin [%2]")\
        .arg(instanceString)\
        .arg(pluginNameAsQString));
}

void audioPlugin::runPlugin(int samples)
{
    // samples is the number of samples *at 8-8 bit stereo interlaced*
    // The interface here is designed so that one can call it with the QByteArray.length() number.

    if(inBypassMode)
        return;

    if(haveActivatedPlugin && haveExternalSinkBuffer && haveExternalSourceBuffer)
    {
        // Let's make these calculations only at one location in the code:
        externalSampleCount = samples;
        sourceBufferSampleCount = samples / 2; // 16-bit is half as many
        inputBufferSampleCount = samples / 4; // 16-bit per-channel is 1/4 as many per channel.

        // You can safely comment out any of these functions to determine where the crash is:
        convertInputBuffer(); // seems ok
        copyControlsToPluginInterface(); // seems ok
        lilv_instance_run(instance, inputBufferSampleCount);

        convertOutputBuffer(); // seems ok

    } else {
        emit pluginErrorMessage(QString("[%1] Tried to run without complete plugin activation.").arg(instanceString));
        emit pluginErrorMessage(QString("[%1] has the following data: activated: %2, have sink: %3, have source: %4")\
                                .arg(instanceString)\
                                .arg(haveActivatedPlugin)\
                                .arg(haveExternalSinkBuffer)\
                                .arg(haveExternalSourceBuffer));
    }
}

void audioPlugin::getPluginControlPorts(pluginInstanceInfoType info)
{
    // For this, we should only be returning control ports, not audio or other ports,
    // and we should be sending a copy of the port data, not pointers.

    // We need to do this with an array of ports...

    uint64_t instanceRequested = info.instanceID;

    if(instanceRequested != this->instanceId)
    {
        emit pluginErrorMessage(QString("Plugin Controls Query received for instance 0x%1 but this is instance 0x%2.")\
                                .arg(instanceRequested, 16, 16, QChar('0'))\
                                .arg(instanceId, 16, 16, QChar('0')));
        return;
    }

    if(haveActivatedPlugin)
    {
        emit haveControlPorts(controls);
    } else {
        emit pluginErrorMessage(QString("Cannot return plugin ports of unactivated plugin"));
    }
}

void audioPlugin::handleAdjustedPluginControls(controlsType control)
{
    // Access the local vector of controls, called "controls".
    // Alter the control->controlsIndex member to have equal value.

    if(control.controlsIndex <= (unsigned int)controls.size())
    {
        controls[control.controlsIndex].value = control.value;
    } else {
        emit pluginErrorMessage(QString("Error in plugin [%1][%4]: Asked to adjust control at index %2 when the max index is %3.")\
                                .arg(pluginNameAsQString)\
                                .arg(control.controlsIndex)\
                                .arg(controls.size())\
                                .arg(instanceString));
    }
}

void audioPlugin::setBypass(bool bypass)
{
    this->inBypassMode = bypass;
}

void audioPlugin::copyControlsToPluginInterface()
{
    // Haven't figured out exactly how to do this one yet. Something like this:
    // Loop across all ports, modify if matching index.

    // Should I have used the n_params variable here instead of portIndex?

    // I have assumed that "value" is the control value.

    controlsType c;

    for(int i=0; i < controls.size(); i++)
    {
        c = controls.at(i);
        this->ports[ c.portIndex ].value = c.value;
    }
}

void audioPlugin::convertInputBuffer()
{
    // Copy from the interleaved uint16 buffer
    // into the float buffer for the plugin to process
    // Includes amplitude scaling into -1 to +1

    // externalSourceBuffer --> scale --> inputBuffer

    float max = 65535.0f / 2.0f;
    //float mid = max / 2.0f;
    float scalingFactor = 1.0f / max;

    // Note, we have already cast the 8MSB, 8LSB type data into a 16-bit holder
    // So accessing one member should result in a full 16-bit number.

    for (int n = 0; n < sourceBufferSampleCount; n += 2) {
        inputBuffer[0][n/2] = (  externalSourceBuffer[n]) * scalingFactor;
        inputBuffer[1][n/2] = (  externalSourceBuffer[n+1]) * scalingFactor;

//        inputBuffer[0][n/2] = (externalSourceBuffer[n] - mid) * scalingFactor;
//        inputBuffer[1][n/2] = (externalSourceBuffer[n+1] - mid) * scalingFactor;
    }
}

void audioPlugin::convertOutputBuffer()
{
    // Copy from the plugin float output
    // to the interleaved uint16 buffer for the audioHandler.
    // Includes amplitude scaling back to uint16 levels.
    // Also converts to "dual mono" if so requested, copying
    // the channel 0 data over to channel 1.
    // Useful for mono plugins that do not create a second output stream.

    // outputBuffer -->  scale -->  externalSinkBuffer

    float max = 65535.0f;
    float mid = max / 2.0f; // set to zero for muted testing

    // copy through test:

    // Copy input to output:
//    for (int n = 0; n+1 < sourceBufferSampleCount; n += 1) {
//        externalSinkBuffer[n] =  externalSourceBuffer[n];
//    }

    // Copy float of input to output:
//    for (int n = 0; n+1 < sourceBufferSampleCount; n += 2) {
//        externalSinkBuffer[n] = (inputBuffer[0][n/2] + 1) * mid; // L
//        externalSinkBuffer[n+1] = (inputBuffer[0][n/2] + 1) * mid; // Copy L
//    }

    if(forceOutputMono)
    {
        for (int n = 0; n+1 < sourceBufferSampleCount; n += 2) {
            externalSinkBuffer[n] = (outputBuffer[0][n/2]) * mid; // L
            externalSinkBuffer[n+1] = (outputBuffer[0][n/2]) * mid; // Copy L
        }
    } else {

        for (int n = 0; n+1 < sourceBufferSampleCount; n += 2) {
            externalSinkBuffer[n] = (outputBuffer[0][n/2]) * mid; // L
            externalSinkBuffer[n+1] = (outputBuffer[1][n/2]) * mid; // R
        }
    }

}

void audioPlugin::makeByteTable()
{
    for(unsigned int i=0; i < 65535; i++)
    {
        byteTable[i] = ((i&0x00ff) << 8) | ( (i&0xff00) >> 8 ) ;
    }
}

QString audioPlugin::getPLuginControlInfo()
{
    QString info = QString("Plugin controls information for plugin named [%1] on instance [%2]: \n")\
            .arg(pluginNameAsQString)\
            .arg(instanceString);
    controlsType c;

    for(int i = 0; i < controls.size(); i ++)
    {
        c = controls.at(i);
        info.append(QString("Control %1:   port index: %7, min: %2, max: %3, default: %4, current value: %5, name: [%6]   \n")\
                    .arg(c.controlsIndex)
                    .arg(c.min)\
                    .arg(c.max)\
                    .arg(c.def)\
                    .arg(c.value)\
                    .arg(c.label)\
                    .arg(c.portIndex));
    }

    return info;
}

LV2_URID audioPlugin::urid_map(LV2_URID_Map_Handle handle, const char* uri)
{
    audioPlugin* lv2 = (audioPlugin*)handle;
    LV2_URID urid;

    std::string key = uri;

    if (lv2->urid_map_data.find(key) == lv2->urid_map_data.end()) {
        urid = lv2->current_urid++;
        lv2->urid_map_data[key] = urid;
    }
    else {
        urid = lv2->urid_map_data[key];
    }

    return urid;
}

const char* audioPlugin::urid_unmap(void* handle, LV2_URID urid)
{
    audioPlugin* lv2 = (audioPlugin*)handle;
    const char* value = nullptr;

    for (auto const& p : lv2->urid_map_data) {
        if (p.second == urid) {
            value = p.first.c_str();
        }
    }

    return value;
}

void audioPlugin::debugThis()
{
    qDebug() << __PRETTY_FUNCTION__ << "Reached plugin debug function for instance" << instanceString;
    emit pluginStatusMessage(getPLuginControlInfo());
}




























