// qsamplerDevice.cpp
//
/****************************************************************************
   Copyright (C) 2004-2020, rncbc aka Rui Nuno Capela. All rights reserved.
   Copyright (C) 2007, 2008 Christian Schoenebeck

   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.

   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.

   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

*****************************************************************************/

#include "qsamplerAbout.h"
#include "qsamplerDevice.h"

#include "qsamplerMainForm.h"
#include "qsamplerDeviceForm.h"

#include <QCheckBox>
#include <QSpinBox>
#include <QLineEdit>


namespace QSampler {

//-------------------------------------------------------------------------
// QSampler::DeviceParam - MIDI/Audio Device parameter structure.
//

// Constructors.
DeviceParam::DeviceParam ( lscp_param_info_t *pParamInfo,
	const char *pszValue )
{
	setParam(pParamInfo, pszValue);
}


// Initializer.
void DeviceParam::setParam ( lscp_param_info_t *pParamInfo,
	const char *pszValue )
{
	if (pParamInfo == nullptr)
		return;

	// Info structure field members.

	type = pParamInfo->type;

	if (pParamInfo->description)
		description = pParamInfo->description;
	else
		description.clear();

	mandatory = bool(pParamInfo->mandatory);
	fix = bool(pParamInfo->fix);
	multiplicity = bool(pParamInfo->multiplicity);

	depends.clear();
	for (int i = 0; pParamInfo->depends && pParamInfo->depends[i]; i++)
		depends.append(pParamInfo->depends[i]);

	if (pParamInfo->defaultv)
		defaultv = pParamInfo->defaultv;
	else
		defaultv.clear();

	if (pParamInfo->range_min)
		range_min = pParamInfo->range_min;
	else
		range_min.clear();

	if (pParamInfo->range_max)
		range_max = pParamInfo->range_max;
	else
		range_max.clear();

	possibilities.clear();
	for (int i = 0; pParamInfo->possibilities && pParamInfo->possibilities[i]; i++)
		possibilities.append(pParamInfo->possibilities[i]);

	// The current parameter value.
	if (pszValue)
		value = pszValue;
	else
		value.clear();
}


//-------------------------------------------------------------------------
// QSampler::Device - MIDI/Audio Device structure.
//

// Constructor.
Device::Device ( DeviceType deviceType, int iDeviceID )
{
//	m_ports.setAutoDelete(true);

	setDevice(deviceType, iDeviceID);
}

// Default destructor.
Device::~Device (void)
{
	qDeleteAll(m_ports);
	m_ports.clear();
}

// Copy constructor.
Device::Device ( const Device& device )
	: m_params(device.m_params), m_ports(device.m_ports)
{
	m_iDeviceID   = device.m_iDeviceID;
	m_deviceType  = device.m_deviceType;
	m_sDeviceType = device.m_sDeviceType;
	m_sDriverName = device.m_sDriverName;
	m_sDeviceName = device.m_sDeviceName;
}


// Initializer.
void Device::setDevice ( DeviceType deviceType, int iDeviceID )
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;
	if (pMainForm->client() == nullptr)
		return;

	// Device id and type should be always set.
	m_iDeviceID  = iDeviceID;
	m_deviceType = deviceType;

	// Reset device parameters and ports anyway.
	m_params.clear();
	qDeleteAll(m_ports);
	m_ports.clear();

	// Retrieve device info, if any.
	lscp_device_info_t *pDeviceInfo = nullptr;
	switch (deviceType) {
	case Device::Audio:
		m_sDeviceType = QObject::tr("Audio");
		if (m_iDeviceID >= 0 && (pDeviceInfo = ::lscp_get_audio_device_info(
				pMainForm->client(), m_iDeviceID)) == nullptr)
			appendMessagesClient("lscp_get_audio_device_info");
		break;
	case Device::Midi:
		m_sDeviceType = QObject::tr("MIDI");
		if (m_iDeviceID >= 0 && (pDeviceInfo = ::lscp_get_midi_device_info(
				pMainForm->client(), m_iDeviceID)) == nullptr)
			appendMessagesClient("lscp_get_midi_device_info");
		break;
	case Device::None:
		m_sDeviceType.clear();
		break;
	}
	// If we're bogus, bail out...
	if (pDeviceInfo == nullptr) {
		m_sDriverName.clear();
		m_sDeviceName = QObject::tr("New %1 device").arg(m_sDeviceType);
		return;
	}

	// Other device properties...
	m_sDriverName = pDeviceInfo->driver;
	m_sDeviceName = m_sDriverName + ' '
		+ QObject::tr("Device %1").arg(m_iDeviceID);

	// Grab device parameters...
	for (int i = 0; pDeviceInfo->params && pDeviceInfo->params[i].key; i++) {
		const QString sParam = pDeviceInfo->params[i].key;
		lscp_param_info_t *pParamInfo = nullptr;
		switch (deviceType) {
		case Device::Audio:
			if ((pParamInfo = ::lscp_get_audio_driver_param_info(
					pMainForm->client(), m_sDriverName.toUtf8().constData(),
					sParam.toUtf8().constData(), nullptr)) == nullptr)
				appendMessagesClient("lscp_get_audio_driver_param_info");
			break;
		case Device::Midi:
			if ((pParamInfo = ::lscp_get_midi_driver_param_info(
					pMainForm->client(), m_sDriverName.toUtf8().constData(),
					sParam.toUtf8().constData(), nullptr)) == nullptr)
				appendMessagesClient("lscp_get_midi_driver_param_info");
			break;
		case Device::None:
			break;
		}
		if (pParamInfo) {
			m_params[sParam.toUpper()] = DeviceParam(pParamInfo,
				pDeviceInfo->params[i].value);
		}
	}

	// Refresh parameter dependencies...
	refreshParams();
	// Set port/channel list...
	refreshPorts();
}


// Driver name initializer/settler.
void Device::setDriver ( const QString& sDriverName )
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;
	if (pMainForm->client() == nullptr)
		return;

	// Valid only for scratch devices.
	if (m_sDriverName == sDriverName)
		return;

	// Reset device parameters and ports anyway.
	m_params.clear();
	qDeleteAll(m_ports);
	m_ports.clear();

	// Retrieve driver info, if any.
	lscp_driver_info_t *pDriverInfo = nullptr;
	switch (m_deviceType) {
	case Device::Audio:
		if ((pDriverInfo = ::lscp_get_audio_driver_info(pMainForm->client(),
				sDriverName.toUtf8().constData())) == nullptr)
			appendMessagesClient("lscp_get_audio_driver_info");
		break;
	case Device::Midi:
		if ((pDriverInfo = ::lscp_get_midi_driver_info(pMainForm->client(),
				sDriverName.toUtf8().constData())) == nullptr)
			appendMessagesClient("lscp_get_midi_driver_info");
		break;
	case Device::None:
		break;
	}

	// If we're bogus, bail out...
	if (pDriverInfo == nullptr)
		return;

	// Remember device parameters...
	m_sDriverName = sDriverName;

	// Grab driver parameters...
	for (int i = 0; pDriverInfo->parameters && pDriverInfo->parameters[i]; i++) {
		const QString sParam = pDriverInfo->parameters[i];
		lscp_param_info_t *pParamInfo = nullptr;
		switch (m_deviceType) {
		case Device::Audio:
			if ((pParamInfo = ::lscp_get_audio_driver_param_info(
					pMainForm->client(), sDriverName.toUtf8().constData(),
					sParam.toUtf8().constData(), nullptr)) == nullptr)
				appendMessagesClient("lscp_get_audio_driver_param_info");
			break;
		case Device::Midi:
			if ((pParamInfo = ::lscp_get_midi_driver_param_info(
					pMainForm->client(), sDriverName.toUtf8().constData(),
					sParam.toUtf8().constData(), nullptr)) == nullptr)
				appendMessagesClient("lscp_get_midi_driver_param_info");
			break;
		case Device::None:
			break;
		}
		if (pParamInfo) {
			m_params[sParam.toUpper()] = DeviceParam(pParamInfo,
				pParamInfo->defaultv);
		}
	}

	// Refresh parameter dependencies...
	refreshParams();
	// Set port/channel list...
	refreshPorts();
}


// Device property accessors.
int Device::deviceID (void) const
{
	return m_iDeviceID;
}

Device::DeviceType Device::deviceType (void) const
{
	return m_deviceType;
}

const QString& Device::deviceTypeName (void) const
{
	return m_sDeviceType;
}

const QString& Device::driverName (void) const
{
	return m_sDriverName;
}

// Special device name formatter.
QString Device::deviceName (void) const
{
	QString sPrefix;
	if (m_iDeviceID >= 0)
		sPrefix += m_sDeviceType + ' ';
	return sPrefix + m_sDeviceName;
}


// Set the proper device parameter value.
bool Device::setParam ( const QString& sParam,
	const QString& sValue )
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return false;
	if (pMainForm->client() == nullptr)
		return false;

	// Set proper device parameter.
	m_params[sParam.toUpper()].value = sValue;

	// If the device already exists, things get immediate...
	int iRefresh = 0;
	if (m_iDeviceID >= 0 && !sValue.isEmpty()) {

		// we need temporary byte arrrays with the final strings, because
		// i.e. QString::toUtf8() only returns a temporary object and the
		// C-style char* pointers for liblscp would immediately be invalidated
		QByteArray finalParamKey = sParam.toUtf8();
		QByteArray finalParamVal = sValue.toUtf8();

		// Prepare parameter struct.
		lscp_param_t param;
		param.key   = (char *) finalParamKey.constData();
		param.value = (char *) finalParamVal.constData();
		// Now it depends on the device type...
		lscp_status_t ret = LSCP_FAILED;
		switch (m_deviceType) {
		case Device::Audio:
			if (sParam == "CHANNELS") iRefresh++;
			if ((ret = ::lscp_set_audio_device_param(pMainForm->client(),
					m_iDeviceID, &param)) != LSCP_OK)
				appendMessagesClient("lscp_set_audio_device_param");
			break;
		case Device::Midi:
			if (sParam == "PORTS") iRefresh++;
			if ((ret = ::lscp_set_midi_device_param(pMainForm->client(),
					m_iDeviceID, &param)) != LSCP_OK)
				appendMessagesClient("lscp_set_midi_device_param");
			break;
		case Device::None:
			break;
		}
		// Show result.
		if (ret == LSCP_OK) {
			appendMessages(QString("%1: %2.").arg(sParam).arg(sValue));
			// Special care for specific parameter changes...
			if (iRefresh > 0)
				iRefresh += refreshPorts();
			iRefresh += refreshDepends(sParam);
		} else {
			// Oops...
			appendMessagesError(
				QObject::tr("Could not set device parameter value.\n\nSorry."));
		}
	}

	// Return whether we're need a view refresh.
	return (iRefresh > 0);
}


// Device parameter accessor.
const DeviceParamMap& Device::params (void) const
{
	return m_params;
}


// Device port/channel list accessor.
DevicePortList& Device::ports (void)
{
	return m_ports;
}


// Create a new device, as a copy of this current one.
bool Device::createDevice (void)
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return false;
	if (pMainForm->client() == nullptr)
		return false;

	// we need temporary lists with the final strings, because i.e.
	// QString::toUtf8() only returns a temporary object and the
	// C-style char* pointers for liblscp would immediately be invalidated
	QList<QByteArray> finalKeys;
	QList<QByteArray> finalVals;

	DeviceParamMap::ConstIterator iter;
	for (iter = m_params.begin(); iter != m_params.end(); ++iter) {
		if (iter.value().value.isEmpty()) continue;
		finalKeys.push_back(iter.key().toUtf8());
		finalVals.push_back(iter.value().value.toUtf8());
	}

	// yeah, we DO HAVE to do the two loops separately !

	// Build the parameter list...
	lscp_param_t *pParams = new lscp_param_t [finalKeys.count() + 1];
	int iParam;
	for (iParam = 0; iParam < finalKeys.count(); iParam++) {
		pParams[iParam].key   = (char *) finalKeys[iParam].constData();
		pParams[iParam].value = (char *) finalVals[iParam].constData();
	}
	// Null terminated.
	pParams[iParam].key   = nullptr;
	pParams[iParam].value = nullptr;

	// Now it depends on the device type...
	switch (m_deviceType) {
	case Device::Audio:
		if ((m_iDeviceID = ::lscp_create_audio_device(pMainForm->client(),
				m_sDriverName.toUtf8().constData(), pParams)) < 0)
			appendMessagesClient("lscp_create_audio_device");
		break;
	case Device::Midi:
		if ((m_iDeviceID = ::lscp_create_midi_device(pMainForm->client(),
				m_sDriverName.toUtf8().constData(), pParams)) < 0)
			appendMessagesClient("lscp_create_midi_device");
		break;
	case Device::None:
		break;
	}

	// Free used parameter array.
	delete[] pParams;

	// Show result.
	if (m_iDeviceID >= 0) {
		// Refresh our own stuff...
		setDevice(m_deviceType, m_iDeviceID);
		appendMessages(QObject::tr("created."));
	} else {
		appendMessagesError(QObject::tr("Could not create device.\n\nSorry."));
	}

	// Return whether we're a valid device...
	return (m_iDeviceID >= 0);
}


// Destroy existing device.
bool Device::deleteDevice (void)
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return false;
	if (pMainForm->client() == nullptr)
		return false;

	// Now it depends on the device type...
	lscp_status_t ret = LSCP_FAILED;
	switch (m_deviceType) {
	case Device::Audio:
		if ((ret = ::lscp_destroy_audio_device(pMainForm->client(),
				m_iDeviceID)) != LSCP_OK)
			appendMessagesClient("lscp_destroy_audio_device");
		break;
	case Device::Midi:
		if ((ret = ::lscp_destroy_midi_device(pMainForm->client(),
				m_iDeviceID)) != LSCP_OK)
			appendMessagesClient("lscp_destroy_midi_device");
		break;
	case Device::None:
		break;
	}

	// Show result.
	if (ret == LSCP_OK) {
		appendMessages(QObject::tr("deleted."));
		m_iDeviceID = -1;
	} else {
		appendMessagesError(QObject::tr("Could not delete device.\n\nSorry."));
	}

	// Return whether we've done it..
	return (ret == LSCP_OK);
}


// Device parameter dependencies refreshner.
int Device::refreshParams (void)
{
	// This should only make sense for scratch devices...
	if (m_iDeviceID >= 0)
		return 0;
	// Refresh all parameters that have dependencies...
	int iParams = 0;
	DeviceParamMap::ConstIterator iter;
	for (iter = m_params.begin(); iter != m_params.end(); ++iter)
		iParams += refreshParam(iter.key());
	// Return how many parameters have been refreshed...
	return iParams;
}


// Device port/channel list refreshner.
int Device::refreshPorts (void)
{
	// This should only make sense for actual devices...
	if (m_iDeviceID < 0)
		return 0;
	// Port/channel count determination...
	int iPorts = 0;
	switch (m_deviceType) {
	case Device::Audio:
		iPorts = m_params["CHANNELS"].value.toInt();
		break;
	case Device::Midi:
		iPorts = m_params["PORTS"].value.toInt();
		break;
	case Device::None:
		break;
	}
	// Retrieve port/channel information...
	qDeleteAll(m_ports);
	m_ports.clear();
	for (int iPort = 0; iPort < iPorts; iPort++)
		m_ports.append(new DevicePort(*this, iPort));
	// Return how many ports have been refreshed...
	return iPorts;
}


// Refresh/set dependencies given that some parameter has changed.
int Device::refreshDepends ( const QString& sParam )
{
	// This should only make sense for scratch devices...
	if (m_iDeviceID >= 0)
		return 0;
	// Refresh all parameters that depend on this one...
	int iDepends = 0;
	DeviceParamMap::ConstIterator iter;
	for (iter = m_params.begin(); iter != m_params.end(); ++iter) {
		const QStringList& depends = iter.value().depends;
		if (depends.indexOf(sParam) >= 0)
			iDepends += refreshParam(iter.key());
	}
	// Return how many dependencies have been refreshed...
	return iDepends;
}


// Refresh/set given parameter based on driver supplied dependencies.
int Device::refreshParam ( const QString& sParam )
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return 0;
	if (pMainForm->client() == nullptr)
		return 0;

	// Check if we have dependencies...
	DeviceParam& param = m_params[sParam.toUpper()];
	if (param.depends.isEmpty())
		return 0;

	int iRefresh = 0;

	// Build dependency list...
	lscp_param_t *pDepends = new lscp_param_t [param.depends.count() + 1];
	int iDepend = 0;

	// we need temporary lists with the final strings, because i.e.
	// QString::toUtf8() only returns a temporary object and the
	// C-style char* pointers for liblscp would immediately be invalidated
	QList<QByteArray> finalKeys;
	QList<QByteArray> finalVals;
	for (int i = 0; i < param.depends.count(); i++) {
		const QString& sDepend = param.depends[i];
		finalKeys.push_back(sDepend.toUtf8());
		finalVals.push_back(m_params[sDepend.toUpper()].value.toUtf8());
	}
	// yeah, we DO HAVE to do those two loops separately !
	for (int i = 0; i < param.depends.count(); i++) {
		pDepends[iDepend].key   = (char *) finalKeys[i].constData();
		pDepends[iDepend].value = (char *) finalVals[i].constData();
		++iDepend;
	}
	// Null terminated.
	pDepends[iDepend].key   = nullptr;
	pDepends[iDepend].value = nullptr;

	// FIXME: Some parameter dependencies (e.g.ALSA CARD)
	// are blocking for no reason, causing potential timeout-crashes.
	// hopefully this gets mitigated if this dependency hell is only
	// carried out for scratch devices...

	// Retrieve some modern parameters...
	lscp_param_info_t *pParamInfo = nullptr;
	switch (m_deviceType) {
	case Device::Audio:
		if ((pParamInfo = ::lscp_get_audio_driver_param_info(
				pMainForm->client(), m_sDriverName.toUtf8().constData(),
				sParam.toUtf8().constData(), pDepends)) == nullptr)
			appendMessagesClient("lscp_get_audio_driver_param_info");
		break;
	case Device::Midi:
		if ((pParamInfo = ::lscp_get_midi_driver_param_info(
				pMainForm->client(), m_sDriverName.toUtf8().constData(),
				sParam.toUtf8().constData(), pDepends)) == nullptr)
			appendMessagesClient("lscp_get_midi_driver_param_info");
		break;
	case Device::None:
		break;
	}
	if (pParamInfo) {
		param = DeviceParam(pParamInfo,
			param.value.isEmpty() ? nullptr : param.value.toUtf8().constData());
		iRefresh++;
	}

	// Free used parameter array.
	delete[] pDepends;

	// Return whether the parameters has been changed...
	return iRefresh;
}


// Redirected messages output methods.
void Device::appendMessages( const QString& s ) const
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm)
		pMainForm->appendMessages(deviceName() + ' ' + s);
}

void Device::appendMessagesColor( const QString& s,	const QColor& rgb ) const
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm)
		pMainForm->appendMessagesColor(deviceName() + ' ' + s, rgb);
}

void Device::appendMessagesText( const QString& s ) const
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm)
		pMainForm->appendMessagesText(deviceName() + ' ' + s);
}

void Device::appendMessagesError( const QString& s ) const
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm)
		pMainForm->appendMessagesError(deviceName() + "\n\n" + s);
}

void Device::appendMessagesClient( const QString& s ) const
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm)
		pMainForm->appendMessagesClient(deviceName() + ' ' + s);
}


// Device ids enumerator.
int *Device::getDevices ( lscp_client_t *pClient,
	DeviceType deviceType )
{
	int *piDeviceIDs = nullptr;
	switch (deviceType) {
	case Device::Audio:
		piDeviceIDs = ::lscp_list_audio_devices(pClient);
		break;
	case Device::Midi:
		piDeviceIDs = ::lscp_list_midi_devices(pClient);
		break;
	case Device::None:
		break;
	}
	return piDeviceIDs;
}

std::set<int> Device::getDeviceIDs(lscp_client_t *pClient,
	DeviceType deviceType)
{
	std::set<int> result;
	int* piDeviceIDs = getDevices(pClient, deviceType);
	if (!piDeviceIDs) return result;
	for (int i = 0; piDeviceIDs[i] != -1; ++i)
		result.insert(piDeviceIDs[i]);
	return result;
}


// Driver names enumerator.
QStringList Device::getDrivers ( lscp_client_t *pClient,
	DeviceType deviceType )
{
	QStringList drivers;

	const char **ppszDrivers = nullptr;
	switch (deviceType) {
	case Device::Audio:
		ppszDrivers = ::lscp_list_available_audio_drivers(pClient);
		break;
	case Device::Midi:
		ppszDrivers = ::lscp_list_available_midi_drivers(pClient);
		break;
	case Device::None:
		break;
	}

	for (int iDriver = 0; ppszDrivers && ppszDrivers[iDriver]; iDriver++)
		drivers.append(ppszDrivers[iDriver]);

	return drivers;
}


//-------------------------------------------------------------------------
// QSampler::DevicePort - MIDI/Audio Device port/channel structure.
//

// Constructor.
DevicePort::DevicePort ( Device& device,
	int iPortID ) : m_device(device)
{
	setDevicePort(iPortID);
}

// Default destructor.
DevicePort::~DevicePort (void)
{
}


// Initializer.
void DevicePort::setDevicePort ( int iPortID )
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;
	if (pMainForm->client() == nullptr)
		return;

	// Device port id should be always set.
	m_iPortID = iPortID;

	// Reset port parameters anyway.
	m_params.clear();

	// Retrieve device port/channel info, if any.
	lscp_device_port_info_t *pPortInfo = nullptr;
	switch (m_device.deviceType()) {
	case Device::Audio:
		if ((pPortInfo = ::lscp_get_audio_channel_info(pMainForm->client(),
				m_device.deviceID(), m_iPortID)) == nullptr)
			m_device.appendMessagesClient("lscp_get_audio_channel_info");
		break;
	case Device::Midi:
		if ((pPortInfo = ::lscp_get_midi_port_info(pMainForm->client(),
				m_device.deviceID(), m_iPortID)) == nullptr)
			m_device.appendMessagesClient("lscp_get_midi_port_info");
		break;
	case Device::None:
		break;
	}

	// If we're bogus, bail out...
	if (pPortInfo == nullptr) {
		m_sPortName.clear();
		return;
	}

	// Set device port/channel properties...
	m_sPortName = pPortInfo->name;

	// Grab device port/channel parameters...
	m_params.clear();
	for (int i = 0; pPortInfo->params && pPortInfo->params[i].key; i++) {
		const QString sParam = pPortInfo->params[i].key;
		lscp_param_info_t *pParamInfo = nullptr;
		switch (m_device.deviceType()) {
		case Device::Audio:
			if ((pParamInfo = ::lscp_get_audio_channel_param_info(
					pMainForm->client(), m_device.deviceID(),
					m_iPortID, sParam.toUtf8().constData())) == nullptr)
				m_device.appendMessagesClient("lscp_get_audio_channel_param_info");
			break;
		case Device::Midi:
			if ((pParamInfo = ::lscp_get_midi_port_param_info(
					pMainForm->client(), m_device.deviceID(),
					m_iPortID, sParam.toUtf8().constData())) == nullptr)
				m_device.appendMessagesClient("lscp_get_midi_port_param_info");
			break;
		case Device::None:
			break;
		}
		if (pParamInfo) {
			m_params[sParam.toUpper()] = DeviceParam(pParamInfo,
				pPortInfo->params[i].value);
		}
	}
}


// Device port/channel property accessors.
int DevicePort::portID (void) const
{
	return m_iPortID;
}

const QString& DevicePort::portName (void) const
{
	return m_sPortName;
}

// Device port/channel parameter accessor.
const DeviceParamMap& DevicePort::params (void) const
{
	return m_params;
}


// Set the proper device port/channel parameter value.
bool DevicePort::setParam ( const QString& sParam,
	const QString& sValue )
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return false;
	if (pMainForm->client() == nullptr)
		return false;

	// Set proper port/channel parameter.
	m_params[sParam.toUpper()].value = sValue;

	// If the device already exists, things get immediate...
	int iRefresh = 0;
	if (m_device.deviceID() >= 0 && m_iPortID >= 0) {

		// we need temporary byte arrrays with the final strings, because
		// i.e. QString::toUtf8() only returns a temporary object and the
		// C-style char* pointers for liblscp would immediately be invalidated
		QByteArray finalParamKey = sParam.toUtf8();
		QByteArray finalParamVal = sValue.toUtf8();

		// Prepare parameter struct.
		lscp_param_t param;
		param.key   = (char *) finalParamKey.constData();
		param.value = (char *) finalParamVal.constData();
		// Now it depends on the device type...
		lscp_status_t ret = LSCP_FAILED;
		switch (m_device.deviceType()) {
		case Device::Audio:
			if ((ret = ::lscp_set_audio_channel_param(pMainForm->client(),
					m_device.deviceID(), m_iPortID, &param)) != LSCP_OK)
				m_device.appendMessagesClient("lscp_set_audio_channel_param");
			break;
		case Device::Midi:
			if ((ret = ::lscp_set_midi_port_param(pMainForm->client(),
					m_device.deviceID(), m_iPortID, &param)) != LSCP_OK)
				m_device.appendMessagesClient("lscp_set_midi_port_param");
			break;
		case Device::None:
			break;
		}
		// Show result.
		if (ret == LSCP_OK) {
			m_device.appendMessages(m_sPortName
				+ ' ' + QString("%1: %2.").arg(sParam).arg(sValue));
			iRefresh++;
		} else {
			m_device.appendMessagesError(
				QObject::tr("Could not set %1 parameter value.\n\n"
					"Sorry.").arg(m_sPortName));
		}
	}

	// Return whether we're need a view refresh.
	return (iRefresh > 0);
}


//-------------------------------------------------------------------------
// QSampler::DeviceItem - QTreeWidget device item.
//

// Constructors.
DeviceItem::DeviceItem ( QTreeWidget* pTreeWidget,
	Device::DeviceType deviceType )
	: QTreeWidgetItem(pTreeWidget, QSAMPLER_DEVICE_ITEM),
		m_device(deviceType)
{
	switch(m_device.deviceType()) {
	case Device::Audio:
		setIcon(0, QPixmap(":/images/audio1.png"));
		setText(0, QObject::tr("Audio Devices"));
		break;
	case Device::Midi:
		setIcon(0, QPixmap(":/images/midi1.png"));
		setText(0, QObject::tr("MIDI Devices"));
		break;
	case Device::None:
		break;
	}

	// Root items are not selectable...
	setFlags(flags() & ~Qt::ItemIsSelectable);
}

DeviceItem::DeviceItem ( QTreeWidgetItem* pItem,
	Device::DeviceType deviceType,
	int iDeviceID )
	: QTreeWidgetItem(pItem, QSAMPLER_DEVICE_ITEM),
		m_device(deviceType, iDeviceID)
{
	switch(m_device.deviceType()) {
	case Device::Audio:
		setIcon(0, QPixmap(":/images/audio2.png"));
		break;
	case Device::Midi:
		setIcon(0, QPixmap(":/images/midi2.png"));
		break;
	case Device::None:
		break;
	}

	setText(0, m_device.deviceName());
}

// Default destructor.
DeviceItem::~DeviceItem ()
{
}

// Instance accessors.
Device& DeviceItem::device ()
{
	return m_device;
}


//-------------------------------------------------------------------------
// QSampler::AbstractDeviceParamModel - data model base class for device parameters
//

AbstractDeviceParamModel::AbstractDeviceParamModel ( QObject* pParent )
	: QAbstractTableModel(pParent), m_bEditable(false)
{
	m_pParams = nullptr;
}


int AbstractDeviceParamModel::rowCount ( const QModelIndex& /*parent*/) const
{
	//std::cout << "model size=" << params.size() << "\n" << std::flush;
	return (m_pParams ? m_pParams->size() : 0);
}


int AbstractDeviceParamModel::columnCount ( const QModelIndex& /*parent*/) const
{
	return 3;
}


Qt::ItemFlags AbstractDeviceParamModel::flags ( const QModelIndex& /*index*/) const
{
	return Qt::ItemIsEditable | Qt::ItemIsEnabled;
}


QVariant AbstractDeviceParamModel::headerData (
	int section, Qt::Orientation orientation, int role) const
{
	if (role != Qt::DisplayRole)
		return QVariant();

	if (orientation == Qt::Horizontal) {
		switch (section) {
			case 0:  return tr("Parameter");
			case 1:  return tr("Value");
			case 2:  return tr("Description");
			default: return QVariant();
		}
	}

	return QVariant();
}


void AbstractDeviceParamModel::refresh (
	const DeviceParamMap* pParams, bool bEditable )
{
	m_pParams   = pParams;
	m_bEditable = bEditable;
	// inform the outer world (QTableView) that our data changed
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
	QAbstractTableModel::reset();
#else
	QAbstractTableModel::beginResetModel();
	QAbstractTableModel::endResetModel();
#endif
}


void AbstractDeviceParamModel::clear (void)
{
	m_pParams = nullptr;
	// inform the outer world (QTableView) that our data changed
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
	QAbstractTableModel::reset();
#else
	QAbstractTableModel::beginResetModel();
	QAbstractTableModel::endResetModel();
#endif
}


//-------------------------------------------------------------------------
// QSampler::DeviceParamModel - data model for device parameters
//                              (used for QTableView)

DeviceParamModel::DeviceParamModel ( QObject *pParent )
	: AbstractDeviceParamModel(pParent)
{
	m_pDevice = nullptr;
}

QVariant DeviceParamModel::data (
	const QModelIndex &index, int role) const
{
	if (!index.isValid())
		return QVariant();

	if (role != Qt::DisplayRole)
		return QVariant();

	DeviceParameterRow item;
	item.name  = m_pParams->keys()[index.row()];
	item.param = (*m_pParams)[item.name];
	item.alive = (m_pDevice && m_pDevice->deviceID() >= 0);

	return QVariant::fromValue(item);
}


bool DeviceParamModel::setData (
	const QModelIndex& index, const QVariant& value, int /*role*/)
{
	if (!index.isValid())
		return false;

	QString key = m_pParams->keys()[index.row()];
	//m_pParams[key].value = value.toString();
	m_pDevice->setParam(key, value.toString());
	emit dataChanged(index, index);
	return true;
}


void DeviceParamModel::refresh ( Device* pDevice, bool bEditable )
{
	m_pDevice = pDevice;
	AbstractDeviceParamModel::refresh(&pDevice->params(), bEditable);
}


void DeviceParamModel::clear (void)
{
	AbstractDeviceParamModel::clear();
	m_pDevice = nullptr;
}


//-------------------------------------------------------------------------
// QSampler::PortParamModel - data model for port parameters
//                            (used for QTableView)

PortParamModel::PortParamModel ( QObject *pParent)
	: AbstractDeviceParamModel(pParent)
{
	m_pPort = nullptr;
}

QVariant PortParamModel::data ( const QModelIndex &index, int role ) const
{
	if (!index.isValid())
		return QVariant();

	if (role != Qt::DisplayRole)
		return QVariant();

	DeviceParameterRow item;
	item.name  = m_pParams->keys()[index.row()];
	item.param = (*m_pParams)[item.name];
	item.alive = (m_pPort && m_pPort->portID() >= 0);

	return QVariant::fromValue(item);
}


bool PortParamModel::setData (
	const QModelIndex& index, const QVariant& value, int /*role*/)
{
	if (!index.isValid())
		return false;

	QString key = m_pParams->keys()[index.row()];
	//params[key].value = value.toString();
	m_pPort->setParam(key, value.toString());
	emit dataChanged(index, index);
	return true;
}


void PortParamModel::refresh ( DevicePort* pPort, bool bEditable )
{
	m_pPort = pPort;
	AbstractDeviceParamModel::refresh(&pPort->params(), bEditable);
}


void PortParamModel::clear (void)
{
	AbstractDeviceParamModel::clear();
	m_pPort = nullptr;
}


//-------------------------------------------------------------------------
// QSampler::DeviceParamDelegate - table cell renderer for device/port parameters
//

DeviceParamDelegate::DeviceParamDelegate ( QObject *pParent)
	: QItemDelegate(pParent)
{
}


QWidget* DeviceParamDelegate::createEditor ( QWidget *pParent,
	const QStyleOptionViewItem& /* option */, const QModelIndex& index ) const
{
	if (!index.isValid())
		return nullptr;

	DeviceParameterRow r = index.model()->data(index,
		Qt::DisplayRole).value<DeviceParameterRow>();

	const bool bEnabled = (r.alive) ? !r.param.fix : true;
	const bool bFix = r.param.fix;

	QString val = (r.alive) ? r.param.value : r.param.defaultv;

	switch (index.column()) {
		case 0:
			return new QLabel(r.name, pParent);
		case 1: {
			if (r.param.type == LSCP_TYPE_BOOL) {
				QCheckBox *pCheckBox = new QCheckBox(pParent);
				if (!val.isEmpty())
					pCheckBox->setChecked(val.toLower() == "true");
				pCheckBox->setEnabled(bEnabled);
				pCheckBox->setCheckable(!bFix);
				return pCheckBox;
			} else if (r.param.possibilities.count() > 0) {
				QStringList opts = r.param.possibilities;
				if (r.param.multiplicity)
					opts.prepend(tr("(none)"));
				QComboBox *pComboBox = new QComboBox(pParent);
				pComboBox->addItems(opts);
				if (r.param.value.isEmpty())
					pComboBox->setCurrentIndex(0);
				else
					pComboBox->setCurrentIndex(pComboBox->findText(val));
				pComboBox->setEnabled(bEnabled);
				pComboBox->setEditable(!bFix);
				return pComboBox;
			} else if (r.param.type == LSCP_TYPE_INT && bEnabled) {
				QSpinBox *pSpinBox = new QSpinBox(pParent);
				pSpinBox->setMinimum(
					(!r.param.range_min.isEmpty()) ?
						r.param.range_min.toInt() : 0 // or better a negative default min value ?
				);
				pSpinBox->setMaximum(
					(!r.param.range_max.isEmpty()) ?
						r.param.range_max.toInt() : (1 << 24) // or better a higher default max value ?
				);
				pSpinBox->setValue(val.toInt());
				pSpinBox->setReadOnly(bFix);
				return pSpinBox;
			} else if (bEnabled) {
				QLineEdit *pLineEdit = new QLineEdit(val, pParent);
				pLineEdit->setReadOnly(bFix);
				return pLineEdit;
			} else {
				QLabel *pLabel = new QLabel(val, pParent);
				return pLabel;
			}
		}
		case 2:
			return new QLabel(r.param.description, pParent);
		default:
			return nullptr;
	}
}


void DeviceParamDelegate::setEditorData (
	QWidget* /*pEditor*/, const QModelIndex& /*index*/) const
{
	// Unused, since we set the editor data already in createEditor()
}


void DeviceParamDelegate::setModelData ( QWidget *pEditor,
	QAbstractItemModel *pModel, const QModelIndex& index ) const
{
	if (index.column() == 1) {
		DeviceParameterRow r = index.model()->data(index,
			Qt::DisplayRole).value<DeviceParameterRow> ();
		if (pEditor->metaObject()->className() == QString("QCheckBox")) {
			QCheckBox *pCheckBox = static_cast<QCheckBox *> (pEditor);
			pModel->setData(index, QVariant(pCheckBox->checkState() == Qt::Checked));
		} else if (pEditor->metaObject()->className() == QString("QComboBox")) {
			QComboBox *pComboBox = static_cast<QComboBox *> (pEditor);
			pModel->setData(index, pComboBox->currentText());
		} else if (pEditor->metaObject()->className() == QString("QSpinBox")) {
			QSpinBox *pSpinBox = static_cast<QSpinBox *> (pEditor);
			pModel->setData(index, pSpinBox->value());
		} else if (pEditor->metaObject()->className() == QString("QLineEdit")) {
			QLineEdit *pLineEdit = static_cast<QLineEdit *> (pEditor);
			pModel->setData(index, pLineEdit->text());
		} else if (pEditor->metaObject()->className() == QString("QLabel")) {
			QLabel *pLabel = static_cast<QLabel *> (pEditor);
			pModel->setData(index, pLabel->text());
		}
	}
}

void DeviceParamDelegate::updateEditorGeometry ( QWidget *pEditor,
	const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
	if (pEditor)
		pEditor->setGeometry(option.rect);
}

} // namespace QSampler

// end of qsamplerDevice.cpp
