// -*- c-basic-offset: 4 -*-
/*
    Rosegarden-4
    A sequencer and musical notation editor.

    This program is Copyright 2000-2006
        Guillaume Laurent   <glaurent@telegraph-road.org>,
        Chris Cannam        <cannam@all-day-breakfast.com>,
        Richard Bown        <bownie@bownie.com>

    The moral right of the authors to claim authorship of this work
    has been asserted.

    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.
*/

#include "audiopluginoscgui.h"
#include <qtimer.h>

#ifdef HAVE_LIBLO

#include <lo/lo.h>
#include <iostream>

#ifdef HAVE_DSSI
#include <dssi.h>
#endif

#include "PluginIdentifier.h"
#include "AudioPluginInstance.h"
#include "MappedCommon.h"
#include "Midi.h"
#include "MappedEvent.h"
#include "Exception.h"

#include "rosestrings.h"
#include "rosedebug.h"
#include "rosegardengui.h"
#include "studiocontrol.h"

#include <kprocess.h>
#include <klocale.h>
#include <qdir.h>
#include <qfileinfo.h>

using Rosegarden::Instrument;
using Rosegarden::InstrumentId;
using Rosegarden::AudioPluginInstance;


OSCMessage::~OSCMessage()
{
    clearArgs();
}

void
OSCMessage::clearArgs()
{
    while (!m_args.empty()) {
	free(m_args[0].second);
	m_args.erase(m_args.begin());
    }
}

void
OSCMessage::addArg(char type, lo_arg *arg)
{
    lo_arg *newarg = 0;

    if (type == 's') {

	size_t sz = strlen((char *)arg) + 1;
	if (sz < sizeof(lo_arg)) sz = sizeof(lo_arg);
	newarg = (lo_arg *)malloc(sz);
	strcpy((char *)newarg, (char *)arg);

    } else {

	newarg = (lo_arg *)malloc(sizeof(lo_arg));
	memcpy((char *)newarg, (char *)arg, sizeof(lo_arg));
    }

    m_args.push_back(OSCArg(type, newarg));
}

size_t
OSCMessage::getArgCount() const
{
    return m_args.size();
}

const lo_arg *
OSCMessage::getArg(size_t i, char &type) const
{
    type = m_args[i].first;
    return m_args[i].second;
}


static void osc_error(int num, const char *msg, const char *path)
{
    std::cerr << "Rosegarden: ERROR: liblo server error " << num
	      << " in path " << path << ": " << msg << std::endl;
}

static int osc_message_handler(const char *path, const char *types, lo_arg **argv,
			       int argc, lo_message, void *user_data)
{
    AudioPluginOSCGUIManager *manager = (AudioPluginOSCGUIManager *)user_data;

    InstrumentId instrument;
    int position;
    QString method;

    if (!manager->parseOSCPath(path, instrument, position, method)) {
	return 1;
    }

    OSCMessage *message = new OSCMessage();
    message->setTarget(instrument);
    message->setTargetData(position);
    message->setMethod(qstrtostr(method));

    int arg = 0;
    while (types && arg < argc && types[arg]) {
	message->addArg(types[arg], argv[arg]);
	++arg;
    }

    manager->postMessage(message);
    return 0;
}
    
AudioPluginOSCGUIManager::AudioPluginOSCGUIManager(RosegardenGUIApp *app) :
    m_app(app),
    m_studio(0),
    m_haveOSCThread(false),
    m_oscBuffer(1023),
    m_dispatchTimer(0)
{
}

AudioPluginOSCGUIManager::~AudioPluginOSCGUIManager()
{
    delete m_dispatchTimer;

    for (TargetGUIMap::iterator i = m_guis.begin(); i != m_guis.end(); ++i) {
	for (IntGUIMap::iterator j = i->second.begin(); j != i->second.end();
	     ++j) {
	    delete j->second;
	}
    }
    m_guis.clear();

#ifdef HAVE_LIBLO_THREADSTOP
    if (m_haveOSCThread) lo_server_thread_stop(m_serverThread);
#endif
}

void
AudioPluginOSCGUIManager::checkOSCThread()
{
    if (m_haveOSCThread) return;

    m_serverThread = lo_server_thread_new(NULL, osc_error);

    lo_server_thread_add_method(m_serverThread, NULL, NULL,
				osc_message_handler, this);

    lo_server_thread_start(m_serverThread);

    RG_DEBUG << "AudioPluginOSCGUIManager: Base OSC URL is "
	     << lo_server_thread_get_url(m_serverThread) << endl;

    m_dispatchTimer = new TimerCallbackAssistant(20, timerCallback, this);
    
    m_haveOSCThread = true;
}

bool
AudioPluginOSCGUIManager::hasGUI(InstrumentId instrument, int position)
{
    Instrument *i = m_studio->getInstrumentById(instrument);
    if (!i) return false;

    AudioPluginInstance *pluginInstance = i->getPlugin(position);
    if (!pluginInstance) return false;

    try {
	QString filePath = AudioPluginOSCGUI::getGUIFilePath
	    (strtoqstr(pluginInstance->getIdentifier()));
	return (filePath && filePath != "");
    } catch (Rosegarden::Exception e) { // that's OK
	return false;
    }
}

void
AudioPluginOSCGUIManager::startGUI(InstrumentId instrument, int position)
{
    RG_DEBUG << "AudioPluginOSCGUIManager::startGUI: " << instrument << "," << position
	     << endl;

    checkOSCThread();

    if (m_guis.find(instrument) != m_guis.end() &&
	m_guis[instrument].find(position) != m_guis[instrument].end()) {
	RG_DEBUG << "stopping GUI first" << endl;
	stopGUI(instrument, position);
    }

    // check the label
    Instrument *i = m_studio->getInstrumentById(instrument);
    if (!i) {
	RG_DEBUG << "AudioPluginOSCGUIManager::startGUI: no such instrument as "
		 << instrument << endl;
	return;
    }

    AudioPluginInstance *pluginInstance = i->getPlugin(position);
    if (!pluginInstance) {
	RG_DEBUG << "AudioPluginOSCGUIManager::startGUI: no plugin at position "
		 << position << " for instrument " << instrument << endl;
	return;
    }

    try {
	AudioPluginOSCGUI *gui =
	    new AudioPluginOSCGUI(pluginInstance,
				  getOSCUrl(instrument,
					    position,
					    strtoqstr(pluginInstance->getIdentifier())),
				  getFriendlyName(instrument,
						  position,
						  strtoqstr(pluginInstance->getIdentifier())));
	m_guis[instrument][position] = gui;

    } catch (Rosegarden::Exception e) {

	RG_DEBUG << "AudioPluginOSCGUIManager::startGUI: failed to start GUI: "
		 << e.getMessage() << endl;
    }
}

void
AudioPluginOSCGUIManager::showGUI(InstrumentId instrument, int position)
{
    RG_DEBUG << "AudioPluginOSCGUIManager::showGUI: " << instrument << "," << position
	     << endl;

    if (m_guis.find(instrument) != m_guis.end() &&
	m_guis[instrument].find(position) != m_guis[instrument].end()) {
	m_guis[instrument][position]->show();
    } else {
	startGUI(instrument, position);
    }
}

void
AudioPluginOSCGUIManager::stopGUI(InstrumentId instrument, int position)
{
    if (m_guis.find(instrument) != m_guis.end() &&
	m_guis[instrument].find(position) != m_guis[instrument].end()) {
	delete m_guis[instrument][position];
	m_guis[instrument].erase(position);
	if (m_guis[instrument].empty()) m_guis.erase(instrument);
    }
}

void
AudioPluginOSCGUIManager::stopAllGUIs()
{
    while (!m_guis.empty()) {
	while (!m_guis.begin()->second.empty()) {
	    delete (m_guis.begin()->second.begin()->second);
	    m_guis.begin()->second.erase(m_guis.begin()->second.begin());
	}
	m_guis.erase(m_guis.begin());
    }
}

void
AudioPluginOSCGUIManager::postMessage(OSCMessage *message)
{
    RG_DEBUG << "AudioPluginOSCGUIManager::postMessage" << endl;
    m_oscBuffer.write(&message, 1);
}

void
AudioPluginOSCGUIManager::updateProgram(InstrumentId instrument, int position)
{
    RG_DEBUG << "AudioPluginOSCGUIManager::updateProgram(" << instrument << ","
	     << position << ")" << endl;

    if (m_guis.find(instrument) == m_guis.end() ||
	m_guis[instrument].find(position) == m_guis[instrument].end()) return;

    Instrument *i = m_studio->getInstrumentById(instrument);
    if (!i) return;

    AudioPluginInstance *pluginInstance = i->getPlugin(position);
    if (!pluginInstance) return;

    unsigned long rv = Rosegarden::StudioControl::getPluginProgram
	(pluginInstance->getMappedId(), strtoqstr(pluginInstance->getProgram()));

    int bank = rv >> 16;
    int program = rv - (bank << 16);

    RG_DEBUG << "AudioPluginOSCGUIManager::updateProgram(" << instrument << ","
	     << position << "): rv " << rv << ", bank " << bank << ", program " << program << endl;

    m_guis[instrument][position]->sendProgram(bank, program);
}

void
AudioPluginOSCGUIManager::updatePort(InstrumentId instrument, int position,
				     int port)
{
    RG_DEBUG << "AudioPluginOSCGUIManager::updatePort(" << instrument << ","
	     << position <<  "," << port << ")" << endl;

    if (m_guis.find(instrument) == m_guis.end() ||
	m_guis[instrument].find(position) == m_guis[instrument].end()) return;

    Instrument *i = m_studio->getInstrumentById(instrument);
    if (!i) return;

    AudioPluginInstance *pluginInstance = i->getPlugin(position);
    if (!pluginInstance) return;

    Rosegarden::PluginPortInstance *porti = pluginInstance->getPort(port);
    if (!porti) return;

    RG_DEBUG << "AudioPluginOSCGUIManager::updatePort(" << instrument << ","
	     << position <<  "," << port << "): value " << porti->value << endl;

    m_guis[instrument][position]->sendPortValue(port, porti->value);
}

void
AudioPluginOSCGUIManager::updateConfiguration(InstrumentId instrument, int position,
					      QString key)
{
    RG_DEBUG << "AudioPluginOSCGUIManager::updateConfiguration(" << instrument << ","
	     << position <<  "," << key << ")" << endl;

    if (m_guis.find(instrument) == m_guis.end() ||
	m_guis[instrument].find(position) == m_guis[instrument].end()) return;

    Instrument *i = m_studio->getInstrumentById(instrument);
    if (!i) return;

    AudioPluginInstance *pluginInstance = i->getPlugin(position);
    if (!pluginInstance) return;

    QString value = strtoqstr(pluginInstance->getConfigurationValue(qstrtostr(key)));

    RG_DEBUG << "AudioPluginOSCGUIManager::updatePort(" << instrument << ","
	     << position <<  "," << key << "): value " << value << endl;

    m_guis[instrument][position]->sendConfiguration(key, value);
}
   
QString
AudioPluginOSCGUIManager::getOSCUrl(InstrumentId instrument, int position,
				    QString identifier)
{
    // OSC URL will be of the form
    //   osc.udp://localhost:54343/plugin/dssi/<instrument>/<position>/<label>
    // where <position> will be "synth" for synth plugins

    QString type, soName, label;
    Rosegarden::PluginIdentifier::parseIdentifier(identifier, type, soName, label);

    QString baseUrl = lo_server_thread_get_url(m_serverThread);
    if (!baseUrl.endsWith("/")) baseUrl += '/';

    QString url = QString("%1%2/%3/%4/%5/%6")
	.arg(baseUrl)
	.arg("plugin")
	.arg(type)
	.arg(instrument);

    if (position == int(Instrument::SYNTH_PLUGIN_POSITION)) {
	url = url.arg("synth");
    } else {
	url = url.arg(position);
    }

    url = url.arg(label);

    return url;
}

bool
AudioPluginOSCGUIManager::parseOSCPath(QString path, InstrumentId &instrument,
				       int &position, QString &method)
{
    RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath(" << path << ")" << endl;
    if (!m_studio) return false;

    QString pluginStr("/plugin/");

    if (path.startsWith("//")) {
	path = path.right(path.length()-1);
    }

    if (!path.startsWith(pluginStr)) {
	RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: malformed path "
		 << path << endl;
	return false;
    }

    path = path.right(path.length() - pluginStr.length());

    QString type = path.section('/', 0, 0);
    QString instrumentStr = path.section('/', 1, 1);
    QString positionStr = path.section('/', 2, 2);
    QString label = path.section('/', 3, -2);
    method = path.section('/', -1, -1);

    if (!instrumentStr || !positionStr) {
	RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: no instrument or position in " << path << endl;
	return false;
    }

    instrument = instrumentStr.toUInt();

    if (positionStr == "synth") {
	position = Instrument::SYNTH_PLUGIN_POSITION;
    } else {
	position = positionStr.toInt();
    }

    // check the label
    Instrument *i = m_studio->getInstrumentById(instrument);
    if (!i) {
	RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: no such instrument as "
		 << instrument << " in path " << path << endl;
	return false;
    }

    AudioPluginInstance *pluginInstance = i->getPlugin(position);
    if (!pluginInstance) {
	RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: no plugin at position "
		 << position << " for instrument " << instrument << " in path "
		 << path << endl;
	return false;
    }

    QString identifier = strtoqstr(pluginInstance->getIdentifier());
    QString iType, iSoName, iLabel;
    Rosegarden::PluginIdentifier::parseIdentifier(identifier, iType, iSoName, iLabel);
    if (iLabel != label) {
	RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: wrong label for plugin"
		 << " at position " << position << " for instrument " << instrument
		 << " in path " << path << " (actual label is " << iLabel
		 << ")" << endl;
	return false;
    }

    RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: good path " << path
	     << ", got mapped id " << pluginInstance->getMappedId() << endl;

    return true;
}


QString
AudioPluginOSCGUIManager::getFriendlyName(InstrumentId instrument, int position,
					  QString)
{
    Instrument *i = m_studio->getInstrumentById(instrument);
    if (!i) return i18n("Rosegarden Plugin");
    else {
	if (position == int(Instrument::SYNTH_PLUGIN_POSITION)) {
	    return i18n("Rosegarden: %1").arg(strtoqstr(i->getPresentationName()));
	} else {
	    return i18n("Rosegarden: %1: %2").arg(strtoqstr(i->getPresentationName()))
		.arg(i18n("Plugin slot %1").arg(position));
	}
    }
}

void
AudioPluginOSCGUIManager::timerCallback(void *data)
{
    AudioPluginOSCGUIManager *manager = (AudioPluginOSCGUIManager *)data;
    manager->dispatch();
}

void
AudioPluginOSCGUIManager::dispatch()
{
    if (!m_studio) return;

    while (m_oscBuffer.getReadSpace() > 0) {

	OSCMessage *message = 0;
	m_oscBuffer.read(&message, 1);

	int instrument = message->getTarget();
	int position = message->getTargetData();

	Instrument *i = m_studio->getInstrumentById(instrument);
	if (!i) continue;

	AudioPluginInstance *pluginInstance = i->getPlugin(position);
	if (!pluginInstance) continue;

	AudioPluginOSCGUI *gui = 0;

	if (m_guis.find(instrument) == m_guis.end()) {
	    RG_DEBUG << "AudioPluginOSCGUIManager: no GUI for instrument "
		     << instrument << endl;
	} else if (m_guis[instrument].find(position) == m_guis[instrument].end()) {
	    RG_DEBUG << "AudioPluginOSCGUIManager: no GUI for instrument "
		     << instrument << ", position " << position << endl;
	} else {
	    gui = m_guis[instrument][position];
	}

	std::string method = message->getMethod();

	char type;
	const lo_arg *arg;

	// These generally call back on the RosegardenGUIApp.  We'd
	// like to emit signals, but making AudioPluginOSCGUIManager a
	// QObject is problematic if it's only conditionally compiled.

	if (method == "control") {
	    
	    if (message->getArgCount() != 2) {
		RG_DEBUG << "AudioPluginOSCGUIManager: wrong number of args ("
			 << message->getArgCount() << ") for control method"
			 << endl;
		goto done;
	    }
	    if (!(arg = message->getArg(0, type)) || type != 'i') {
		RG_DEBUG << "AudioPluginOSCGUIManager: failed to get port number"
			 << endl;
		goto done;
	    }
	    int port = arg->i;
	    if (!(arg = message->getArg(1, type)) || type != 'f') {
		RG_DEBUG << "AudioPluginOSCGUIManager: failed to get port value"
			 << endl;
		goto done;
	    }
	    float value = arg->f;

	    RG_DEBUG << "AudioPluginOSCGUIManager: setting port " << port
		     << " to value " << value << endl;

	    m_app->slotChangePluginPort(instrument, position, port, value);

	} else if (method == "program") {

	    if (message->getArgCount() != 2) {
		RG_DEBUG << "AudioPluginOSCGUIManager: wrong number of args ("
			 << message->getArgCount() << ") for program method"
			 << endl;
		goto done;
	    }
	    if (!(arg = message->getArg(0, type)) || type != 'i') {
		RG_DEBUG << "AudioPluginOSCGUIManager: failed to get bank number"
			 << endl;
		goto done;
	    }
	    int bank = arg->i;
	    if (!(arg = message->getArg(1, type)) || type != 'i') {
		RG_DEBUG << "AudioPluginOSCGUIManager: failed to get program number"
			 << endl;
		goto done;
	    }
	    int program = arg->i;

	    QString programName = Rosegarden::StudioControl::getPluginProgram
		(pluginInstance->getMappedId(), bank, program);

	    m_app->slotChangePluginProgram(instrument, position, programName);

	} else if (method == "update") {

	    if (message->getArgCount() != 1) {
		RG_DEBUG << "AudioPluginOSCGUIManager: wrong number of args ("
			 << message->getArgCount() << ") for update method"
			 << endl;
		goto done;
	    }
	    if (!(arg = message->getArg(0, type)) || type != 's') {
		RG_DEBUG << "AudioPluginOSCGUIManager: failed to get GUI URL"
			 << endl;
		goto done;
	    }
	    QString url = &arg->s;
	    
	    if (!gui) {
		RG_DEBUG << "AudioPluginOSCGUIManager: no GUI for update method"
			 << endl;
		goto done;
	    }

	    gui->setGUIUrl(url);

	    for (AudioPluginInstance::ConfigMap::const_iterator i =
		     pluginInstance->getConfiguration().begin();
		 i != pluginInstance->getConfiguration().end(); ++i) {

		QString key = strtoqstr(i->first);
		QString value = strtoqstr(i->second);

#ifdef DSSI_PROJECT_DIRECTORY_KEY
		if (key == Rosegarden::PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY) {
		    key = DSSI_PROJECT_DIRECTORY_KEY;
		}
#endif

		RG_DEBUG << "update: configuration: " << key << " -> "
			 << value << endl;

		gui->sendConfiguration(key, value);
	    }
		
	    unsigned long rv = Rosegarden::StudioControl::getPluginProgram
		(pluginInstance->getMappedId(), strtoqstr(pluginInstance->getProgram()));

	    int bank = rv >> 16;
	    int program = rv - (bank << 16);
	    gui->sendProgram(bank, program);

	    int controlCount = 0;
	    for (Rosegarden::PortInstanceIterator i = pluginInstance->begin();
		 i != pluginInstance->end(); ++i) {
		gui->sendPortValue((*i)->number, (*i)->value);
		/* Avoid overloading the GUI if there are lots and lots of ports */
		if (++controlCount % 50 == 0) usleep(300000);
	    }
	    
	    gui->show();

	} else if (method == "configure") {

	    if (message->getArgCount() != 2) {
		RG_DEBUG << "AudioPluginOSCGUIManager: wrong number of args ("
			 << message->getArgCount() << ") for configure method"
			 << endl;
		goto done;
	    }

	    if (!(arg = message->getArg(0, type)) || type != 's') {
		RG_DEBUG << "AudioPluginOSCGUIManager: failed to get configure key"
			 << endl;
		goto done;
	    }
	    QString key = &arg->s;

	    if (!(arg = message->getArg(1, type)) || type != 's') {
		RG_DEBUG << "AudioPluginOSCGUIManager: failed to get configure value"
			 << endl;
		goto done;
	    }
	    QString value = &arg->s;

#ifdef DSSI_RESERVED_CONFIGURE_PREFIX
	    if (key.startsWith(DSSI_RESERVED_CONFIGURE_PREFIX) || 
		key == Rosegarden::PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY) {
		RG_DEBUG << "AudioPluginOSCGUIManager: illegal reserved configure call from gui: " << key << " -> " << value << endl;
		goto done;
	    }
#endif

	    RG_DEBUG << "AudioPluginOSCGUIManager: configure(" << key << "," << value
		     << ")" << endl;

	    m_app->slotChangePluginConfiguration(instrument, position,
#ifdef DSSI_GLOBAL_CONFIGURE_PREFIX
						 key.startsWith(DSSI_GLOBAL_CONFIGURE_PREFIX),
#else
						 false,
#endif
						 key, value);

	} else if (method == "midi") {

	    if (message->getArgCount() != 1) {
		RG_DEBUG << "AudioPluginOSCGUIManager: wrong number of args ("
			 << message->getArgCount() << ") for midi method"
			 << endl;
		goto done;
	    }
	    if (!(arg = message->getArg(0, type)) || type != 'm') {
		RG_DEBUG << "AudioPluginOSCGUIManager: failed to get MIDI event"
			 << endl;
		goto done;
	    }
	    
	    RG_DEBUG << "AudioPluginOSCGUIManager: handling MIDI message" << endl;

	    // let's only handle note on and note off

	    int eventCode = arg->m[1];
	    int eventType = eventCode & Rosegarden::MIDI_MESSAGE_TYPE_MASK;
	    if (eventType == Rosegarden::MIDI_NOTE_ON ||
		eventType == Rosegarden::MIDI_NOTE_OFF) {
		Rosegarden::MappedEvent ev(instrument,
					   Rosegarden::MappedEvent::MidiNote,
					   Rosegarden::MidiByte(arg->m[2]),
					   Rosegarden::MidiByte(arg->m[3]),
					   Rosegarden::RealTime::zeroTime,
					   Rosegarden::RealTime::zeroTime,
					   Rosegarden::RealTime::zeroTime);
		if (eventType == Rosegarden::MIDI_NOTE_OFF) ev.setVelocity(0);
		Rosegarden::StudioControl::sendMappedEvent(ev);
	    }		

	} else if (method == "exiting") {

	    RG_DEBUG << "AudioPluginOSCGUIManager: GUI exiting" << endl;
	    stopGUI(instrument, position);
	    m_app->slotPluginGUIExited(instrument, position);

	} else {

	    RG_DEBUG << "AudioPluginOSCGUIManager: unknown method " << method << endl;
	}

    done:
	delete message;
    }
}

AudioPluginOSCGUI::AudioPluginOSCGUI(Rosegarden::AudioPluginInstance *instance,
				     QString serverURL, QString friendlyName) :
    m_gui(0),
    m_address(0),
    m_basePath(""),
    m_serverUrl(serverURL)
{
    QString identifier = strtoqstr(instance->getIdentifier());

    QString filePath = getGUIFilePath(identifier);
    if (!filePath) {
	throw Rosegarden::Exception("No GUI found");
    }

    QString type, soName, label;
    Rosegarden::PluginIdentifier::parseIdentifier(identifier, type, soName, label);
    QFileInfo soInfo(soName);

    // arguments: osc url, dll name, label, instance tag

    m_gui = new KProcess();

    *m_gui << filePath
	   << m_serverUrl
	   << soInfo.fileName()
	   << label
	   << friendlyName;
    
    RG_DEBUG << "AudioPluginOSCGUI::AudioPluginOSCGUI: Starting process "
	     << filePath << " " << m_serverUrl << " "
	     << soInfo.fileName() << " " << label << " " << friendlyName << endl;

    if (!m_gui->start(KProcess::NotifyOnExit, KProcess::NoCommunication)) {
	RG_DEBUG << "AudioPluginOSCGUI::AudioPluginOSCGUI: Couldn't start process " << filePath << endl;
	delete m_gui;
	m_gui = 0;
	throw Rosegarden::Exception("Failed to start GUI");
    }
}

AudioPluginOSCGUI::~AudioPluginOSCGUI()
{
    quit();
}

QString
AudioPluginOSCGUI::getGUIFilePath(QString identifier)
{
    QString type, soName, label;
    Rosegarden::PluginIdentifier::parseIdentifier(identifier, type, soName, label);

    RG_DEBUG << "AudioPluginOSCGUI::getGUIFilePath(" << identifier << ")" << endl;

    QFileInfo soInfo(soName);
    if (soInfo.isRelative()) {
	//!!!
	RG_DEBUG << "AudioPluginOSCGUI::AudioPluginOSCGUI: Unable to deal with relative .so path \"" << soName << "\" in identifier \"" << identifier << "\" yet" << endl;
	throw Rosegarden::Exception("Can't deal with relative .soname");
    }

    QDir dir(soInfo.dir());
    QString fileBase(soInfo.baseName(TRUE));

    if (!dir.cd(fileBase)) {
	RG_DEBUG << "AudioPluginOSCGUI::AudioPluginOSCGUI: No GUI subdir for plugin .so " << soName << endl;
	throw Rosegarden::Exception("No GUI subdir available");
    }

    const QFileInfoList *list = dir.entryInfoList();

    // in order of preference:
    const char *suffixes[] = { "_rg", "_kde", "_qt", "_gtk2", "_gtk", "_x11", "_gui" };
    int nsuffixes = sizeof(suffixes)/sizeof(suffixes[0]);

    for (int k = 0; k <= nsuffixes; ++k) {

	for (int fuzzy = 0; fuzzy <= 1; ++fuzzy) {

	    QFileInfoListIterator i(*list);
	    QFileInfo *info;

	    while ((info = i.current()) != 0) {
		
		RG_DEBUG << "Looking at " << info->fileName() << " in path "
			 << info->filePath() << " for suffix " << (k == nsuffixes ? "(none)" : suffixes[k]) << ", fuzzy " << fuzzy << endl;

		++i;

		if (!(info->isFile() || info->isSymLink())
		    || !info->isExecutable()) {
		    RG_DEBUG << "(not executable)" << endl;
		    continue;
		}

		if (fuzzy) {
		    if (info->fileName().left(fileBase.length()) != fileBase) continue;
		    RG_DEBUG << "(is file base)" << endl;
		} else {
		    if (info->fileName().left(label.length()) != label) continue;
		    RG_DEBUG << "(is label)" << endl;
		}

		if (k == nsuffixes || info->fileName().lower().endsWith(suffixes[k])) {
		    RG_DEBUG << "(ends with suffix " << (k == nsuffixes ? "(none)" : suffixes[k]) << " or out of suffixes)" << endl;
		    return info->filePath();
		}
		RG_DEBUG << "(doesn't end with suffix " << (k == nsuffixes ? "(none)" : suffixes[k]) << ")" << endl;
	    }
	}
    }
	
    return QString();
}

void
AudioPluginOSCGUI::setGUIUrl(QString url)
{
    if (m_address) lo_address_free(m_address);

    char *host = lo_url_get_hostname(url);
    char *port = lo_url_get_port(url);
    m_address = lo_address_new(host, port);
    free(host);
    free(port);

    m_basePath = lo_url_get_path(url);
}

void
AudioPluginOSCGUI::show()
{
    RG_DEBUG << "AudioPluginOSCGUI::show" << endl;

    if (!m_address) return;
    QString path = m_basePath + "/show";
    lo_send(m_address, path, "");
}

void
AudioPluginOSCGUI::hide()
{
    if (!m_address) return;
    QString path = m_basePath + "/hide";
    lo_send(m_address, path, "");
}

void
AudioPluginOSCGUI::quit()
{
    if (!m_address) return;
    QString path = m_basePath + "/quit";
    lo_send(m_address, path, "");
}

void
AudioPluginOSCGUI::sendProgram(int bank, int program)
{
    if (!m_address) return;
    QString path = m_basePath + "/program";
    lo_send(m_address, path, "ii", bank, program);
}

void
AudioPluginOSCGUI::sendPortValue(int port, float value)
{
    if (!m_address) return;
    QString path = m_basePath + "/control";
    lo_send(m_address, path, "if", port, value);
}
    
void
AudioPluginOSCGUI::sendConfiguration(QString key, QString value)
{
    if (!m_address) return;
    QString path = m_basePath + "/configure";
    lo_send(m_address, path, "ss", key.data(), value.data());
}

#endif

TimerCallbackAssistant::TimerCallbackAssistant(int ms, void (*callback)(void *data),
					       void *data) :
    m_callback(callback),
    m_data(data)
{
    QTimer *timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(slotCallback()));
    timer->start(ms, FALSE);
}

TimerCallbackAssistant::~TimerCallbackAssistant()
{
    // nothing -- the QTimer is deleted automatically by its parent QObject (me)
}

void
TimerCallbackAssistant::slotCallback()
{
    m_callback(m_data);
}


#include "audiopluginoscgui.moc"
