// qsamplerDeviceForm.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 "qsamplerDeviceForm.h"

#include "qsamplerAbout.h"
#include "qsamplerMainForm.h"

#include <QHeaderView>
#include <QMessageBox>

#include <QCheckBox>


namespace QSampler {

//-------------------------------------------------------------------------
// QSampler::DeviceForm -- Device form implementation.
//

DeviceForm::DeviceForm ( QWidget *pParent, Qt::WindowFlags wflags )
	: QDialog(pParent, wflags)
{
	m_ui.setupUi(this);

	// Initialize locals.
	m_iDirtySetup = 0;
	m_iDirtyCount = 0;
	m_bNewDevice  = false;
	m_deviceType  = Device::None;
	m_pAudioItems = nullptr;
	m_pMidiItems  = nullptr;
	// No exclusive mode as default.
	m_deviceTypeMode = Device::None;

	m_ui.DeviceListView->header()->hide();

	int iRowHeight = m_ui.DeviceParamTable->fontMetrics().height() + 4;
	m_ui.DeviceParamTable->verticalHeader()->setDefaultSectionSize(iRowHeight);
	m_ui.DevicePortParamTable->verticalHeader()->setDefaultSectionSize(iRowHeight);
	m_ui.DeviceParamTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
	m_ui.DevicePortParamTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);

	m_ui.DeviceParamTable->setModel(&m_deviceParamModel);
	m_ui.DeviceParamTable->setItemDelegate(&m_deviceParamDelegate);
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
	m_ui.DeviceParamTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
#else
	m_ui.DeviceParamTable->horizontalHeader()->setResizeMode(2, QHeaderView::Stretch);
#endif
	m_ui.DeviceParamTable->verticalHeader()->hide();

	m_ui.DevicePortParamTable->setModel(&m_devicePortParamModel);
	m_ui.DevicePortParamTable->setItemDelegate(&m_devicePortParamDelegate);
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
	m_ui.DevicePortParamTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
#else
	m_ui.DevicePortParamTable->horizontalHeader()->setResizeMode(2, QHeaderView::Stretch);
#endif
	m_ui.DevicePortParamTable->verticalHeader()->hide();

	// Initial contents.
	refreshDevices();
	// Try to restore normal window positioning.
	adjustSize();

	QObject::connect(m_ui.DeviceListView,
		SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
		SLOT(selectDevice()));
	QObject::connect(m_ui.DeviceListView,
		SIGNAL(customContextMenuRequested(const QPoint&)),
		SLOT(deviceListViewContextMenu(const QPoint&)));
	QObject::connect(m_ui.RefreshDevicesPushButton,
		SIGNAL(clicked()),
		SLOT(refreshDevices()));
	QObject::connect(m_ui.DriverNameComboBox,
		SIGNAL(activated(int)),
		SLOT(selectDriver(int)));
	QObject::connect(m_ui.DevicePortComboBox,
		SIGNAL(activated(int)),
		SLOT(selectDevicePort(int)));
	QObject::connect(m_ui.CreateDevicePushButton,
		SIGNAL(clicked()),
		SLOT(createDevice()));
	QObject::connect(m_ui.DeleteDevicePushButton,
		SIGNAL(clicked()),
		SLOT(deleteDevice()));
	QObject::connect(m_ui.ClosePushButton,
		SIGNAL(clicked()),
		SLOT(close()));
	QObject::connect(&m_deviceParamModel,
		SIGNAL(modelReset()),
		SLOT(updateCellRenderers()));
	QObject::connect(&m_deviceParamModel,
		SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
		SLOT(updateCellRenderers(const QModelIndex&, const QModelIndex&)));
	QObject::connect(&m_devicePortParamModel,
		SIGNAL(modelReset()),
		SLOT(updatePortCellRenderers()));
	QObject::connect(&m_devicePortParamModel,
		SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
		SLOT(updatePortCellRenderers(const QModelIndex&, const QModelIndex&)));
}


DeviceForm::~DeviceForm (void)
{
}


// Notify our parent that we're emerging.
void DeviceForm::showEvent ( QShowEvent *pShowEvent )
{
	MainForm* pMainForm = MainForm::getInstance();
	if (pMainForm)
		pMainForm->stabilizeForm();

	QWidget::showEvent(pShowEvent);

	refreshDevices();
}


// Notify our parent that we're closing.
void DeviceForm::hideEvent ( QHideEvent *pHideEvent )
{
	QWidget::hideEvent(pHideEvent);

	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm)
		pMainForm->stabilizeForm();

	// Signal special whether we changed the device set.
	if (m_iDirtyCount > 0) {
		m_iDirtyCount = 0;
		emit devicesChanged();
	}
}


// Set device type spacial exclusive mode.
void DeviceForm::setDeviceTypeMode (
	Device::DeviceType deviceTypeMode )
{
	// If it has not changed, do nothing.
	if (m_deviceTypeMode == deviceTypeMode)
		return;

	m_deviceTypeMode = deviceTypeMode;

	// OK. Do a whole refresh around.
	refreshDevices();
}


// Device driver name setup formal initializer.
void DeviceForm::setDriverName ( const QString& sDriverName )
{
	if (m_ui.DriverNameComboBox->findText(sDriverName) < 0)
		m_ui.DriverNameComboBox->insertItem(0, sDriverName);
	m_ui.DriverNameComboBox->setItemText(
		m_ui.DriverNameComboBox->currentIndex(),
		sDriverName);
}


// Set current selected device by type and id.
void DeviceForm::setDevice ( Device *pDevice )
{
	// In case no device is given...
	Device::DeviceType deviceType = m_deviceTypeMode;
	if (pDevice)
		deviceType = pDevice->deviceType();

	// Get the device view root item...
	DeviceItem *pRootItem = nullptr;
	switch (deviceType) {
	case Device::Audio:
		pRootItem = m_pAudioItems;
		break;
	case Device::Midi:
		pRootItem = m_pMidiItems;
		break;
	case Device::None:
		break;
	}

	// Is the root present?
	if (pRootItem == nullptr)
		return;

	// So there's no device huh?
	if (pDevice == nullptr) {
		m_ui.DeviceListView->setCurrentItem(pRootItem);
		return;
	}

	// For each child, test for identity...
	for (int i = 0; i < pRootItem->childCount(); i++) {
		DeviceItem* pDeviceItem =
			(DeviceItem*) pRootItem->child(i);

		// If identities match, select as current device item.
		if (pDeviceItem->device().deviceID() == pDevice->deviceID()) {
			pDeviceItem->setSelected(true);
			break;
		}
	}
}



// Create a new device from current table view.
void DeviceForm::createDevice (void)
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;

	QTreeWidgetItem *pItem = m_ui.DeviceListView->currentItem();
	if (pItem == nullptr || pItem->type() != QSAMPLER_DEVICE_ITEM)
		return;

	// About a brand new device instance...
	Device device(((DeviceItem *) pItem)->device());
	if (device.createDevice()) {
		// Now it depends on the device type...
		DeviceItem *pRootItem = nullptr;
		switch (device.deviceType()) {
		case Device::Audio:
			pRootItem = m_pAudioItems;
			break;
		case Device::Midi:
			pRootItem = m_pMidiItems;
			break;
		case Device::None:
			break;
		}
		// Append the new device item.
		DeviceItem *pDeviceItem = new DeviceItem(pRootItem,
			device.deviceType(), device.deviceID());
		// Just make it the new selection...
		pDeviceItem->setSelected(true);
		// Main session should be marked dirty.
		pMainForm->sessionDirty();
		m_iDirtyCount++;
	}
}


// Delete current device in table view.
void DeviceForm::deleteDevice (void)
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;

	QTreeWidgetItem* pItem = m_ui.DeviceListView->currentItem();
	if (pItem == nullptr || pItem->type() != QSAMPLER_DEVICE_ITEM)
		return;

	Device& device = ((DeviceItem *) pItem)->device();

	// Prompt user if this is for real...
	Options *pOptions = pMainForm->options();
	if (pOptions && pOptions->bConfirmRemove) {
		const QString& sTitle = tr("Warning");
		const QString& sText = tr(
			"About to delete device:\n\n"
			"%1\n\n"
			"Are you sure?")
			.arg(device.deviceName());
	#if 0
		if (QMessageBox::warning(this, sTitle, sText,
			QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
			return;
	#else
		QMessageBox mbox(this);
		mbox.setIcon(QMessageBox::Warning);
		mbox.setWindowTitle(sTitle);
		mbox.setText(sText);
		mbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
		QCheckBox cbox(tr("Don't ask this again"));
		cbox.setChecked(false);
		cbox.blockSignals(true);
		mbox.addButton(&cbox, QMessageBox::ActionRole);
		if (mbox.exec() == QMessageBox::Cancel)
			return;
		if (cbox.isChecked())
			pOptions->bConfirmRemove = false;
	#endif
	}

	// Go and destroy...
	if (device.deleteDevice()) {
		// Remove it from the device view...
		delete pItem;
		// Main session should be marked dirty.
		pMainForm->sessionDirty();
		m_iDirtyCount++;
	}
}


// Refresh all device list and views.
void DeviceForm::refreshDevices (void)
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;

	// Avoid nested changes.
	m_iDirtySetup++;

	//
	// (Re)Load complete device configuration data ...
	//
	m_pAudioItems = nullptr;
	m_pMidiItems = nullptr;
	m_ui.DeviceListView->clear();
	if (pMainForm->client()) {
		int *piDeviceIDs;
		// Grab and pop Audio devices...
		if (m_deviceTypeMode == Device::None ||
			m_deviceTypeMode == Device::Audio) {
			m_pAudioItems = new DeviceItem(m_ui.DeviceListView,
				Device::Audio);
		}
		if (m_pAudioItems) {
			piDeviceIDs = Device::getDevices(pMainForm->client(),
				Device::Audio);
			for (int i = 0; piDeviceIDs && piDeviceIDs[i] >= 0; i++) {
				new DeviceItem(m_pAudioItems,
					Device::Audio, piDeviceIDs[i]);
			}
			m_pAudioItems->setExpanded(true);
		}
		// Grab and pop MIDI devices...
		if (m_deviceTypeMode == Device::None ||
			m_deviceTypeMode == Device::Midi) {
			m_pMidiItems = new DeviceItem(m_ui.DeviceListView,
				Device::Midi);
		}
		if (m_pMidiItems) {
			piDeviceIDs = Device::getDevices(pMainForm->client(),
				Device::Midi);
			for (int i = 0; piDeviceIDs && piDeviceIDs[i] >= 0; i++) {
				new DeviceItem(m_pMidiItems,
					Device::Midi, piDeviceIDs[i]);
			}
			m_pMidiItems->setExpanded(true);
		}
	}

	// Done.
	m_iDirtySetup--;

	// Show something.
	selectDevice();
}


// Driver selection slot.
void DeviceForm::selectDriver ( int iDriver )
{
	if (m_iDirtySetup > 0)
		return;

	//
	//  Driver name has changed for a new device...
	//
	const QString& sDriverName
		= m_ui.DriverNameComboBox->itemText(iDriver);
	if (sDriverName.isEmpty())
		return;

	QTreeWidgetItem* pItem = m_ui.DeviceListView->currentItem();
	if (pItem == nullptr || pItem->type() != QSAMPLER_DEVICE_ITEM)
		return;

	Device& device = ((DeviceItem *) pItem)->device();

	// Driver change is only valid for scratch devices...
	if (m_bNewDevice) {
		m_iDirtySetup++;
		device.setDriver(sDriverName);
		m_deviceParamModel.refresh(&device, m_bNewDevice);
		m_iDirtySetup--;
		// Done.
		stabilizeForm();
	}
}


// Device selection slot.
void DeviceForm::selectDevice ()
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;

	if (m_iDirtySetup > 0)
		return;

	//
	//  Device selection has changed...
	//

	QTreeWidgetItem* pItem = m_ui.DeviceListView->currentItem();
	if (pItem == nullptr || pItem->type() != QSAMPLER_DEVICE_ITEM) {
		m_deviceType = Device::None;
		m_ui.DeviceNameTextLabel->setText(QString());
		m_deviceParamModel.clear();
		m_ui.DevicePortComboBox->clear();
		m_devicePortParamModel.clear();
		m_ui.DevicePortTextLabel->setEnabled(false);
		m_ui.DevicePortComboBox->setEnabled(false);
		m_ui.DevicePortParamTable->setEnabled(false);
		stabilizeForm();
		return;
	}

	Device& device = ((DeviceItem *) pItem)->device();

	m_iDirtySetup++;
	// Flag whether this is a new device.
	m_bNewDevice = (device.deviceID() < 0);

	// Fill the device/driver heading...
	m_ui.DeviceNameTextLabel->setText(device.deviceName());
	// The driver combobox is only rebuilt if device type has changed...
	if (device.deviceType() != m_deviceType) {
		m_ui.DriverNameComboBox->clear();
		m_ui.DriverNameComboBox->insertItems(0,
			Device::getDrivers(pMainForm->client(), device.deviceType()));
		m_deviceType = device.deviceType();
	}
	// Do we need a driver name?
	if (m_bNewDevice || device.driverName().isEmpty())
		device.setDriver(m_ui.DriverNameComboBox->currentText());
	setDriverName(device.driverName());
	m_ui.DriverNameTextLabel->setEnabled(m_bNewDevice);
	m_ui.DriverNameComboBox->setEnabled(m_bNewDevice);
	// Fill the device parameter table...
	m_deviceParamModel.refresh(&device, m_bNewDevice);
	// And now the device port/channel parameter table...
	switch (device.deviceType()) {
	case Device::Audio:
		m_ui.DevicePortTextLabel->setText(tr("Ch&annel:"));
		break;
	case Device::Midi:
		m_ui.DevicePortTextLabel->setText(tr("P&ort:"));
		break;
	case Device::None:
		break;
	}
	m_ui.DevicePortComboBox->clear();
	m_devicePortParamModel.clear();
	if (m_bNewDevice) {
		m_ui.DevicePortTextLabel->setEnabled(false);
		m_ui.DevicePortComboBox->setEnabled(false);
		m_ui.DevicePortParamTable->setEnabled(false);
	} else {
		QPixmap pixmap;
		switch (device.deviceType()) {
		case Device::Audio:
			pixmap = QPixmap(":/images/audio2.png");
			break;
		case Device::Midi:
			pixmap = QPixmap(":/images/midi2.png");
			break;
		case Device::None:
			break;
		}
		DevicePortList& ports = device.ports();
		QListIterator<DevicePort *> iter(ports);
		while (iter.hasNext()) {
			DevicePort *pPort = iter.next();
			m_ui.DevicePortComboBox->addItem(pixmap,
				device.deviceTypeName()
				+ ' ' + device.driverName()
				+ ' ' + pPort->portName());
		}
		bool bEnabled = (ports.count() > 0);
		m_ui.DevicePortTextLabel->setEnabled(bEnabled);
		m_ui.DevicePortComboBox->setEnabled(bEnabled);
		m_ui.DevicePortParamTable->setEnabled(bEnabled);
	}
	// Done.
	m_iDirtySetup--;

	// Make the device port/channel selection effective.
	selectDevicePort(m_ui.DevicePortComboBox->currentIndex());
}


// Device port/channel selection slot.
void DeviceForm::selectDevicePort ( int iPort )
{
	if (m_iDirtySetup > 0)
		return;

	//
	//  Device port/channel selection has changed...
	//

	QTreeWidgetItem* pItem = m_ui.DeviceListView->currentItem();
	if (pItem == nullptr || pItem->type() != QSAMPLER_DEVICE_ITEM)
		return;

	Device& device = ((DeviceItem *) pItem)->device();
	DevicePort *pPort = nullptr;
	if (iPort >= 0 && iPort < device.ports().count())
		pPort = device.ports().at(iPort);
	if (pPort) {
		m_iDirtySetup++;
		m_devicePortParamModel.refresh(pPort, false);
		m_iDirtySetup--;
	}
	// Done.
	stabilizeForm();
}


// Device parameter value change slot.
void DeviceForm::changeDeviceParam ( int iRow, int iCol )
{
	if (m_iDirtySetup > 0)
		return;
	if (iRow < 0 || iCol < 0)
		return;

	//
	//  Device parameter change...
	//

/* we do that in the model class now ...
	QTreeWidgetItem* pItem = m_ui.DeviceListView->currentItem();
	if (pItem == nullptr || pItem->type() != QSAMPLER_DEVICE_ITEM)
		return;

	Device& device = ((DeviceItem *) pItem)->device();

	// Table 1st column has the parameter name;
	//const QString sParam = m_ui.DeviceParamTable->text(iRow, 0);
	//const QString sValue = m_ui.DeviceParamTable->text(iRow, iCol);
	const QString sParam = m_deviceParamModel.data(m_deviceParamModel.index(iRow, 0), Qt::DisplayRole).value<DeviceParameterRow>().name;
	const QString sValue = m_deviceParamModel.data(m_deviceParamModel.index(iRow, iCol), Qt::DisplayRole).value<DeviceParameterRow>().param.value;
	// Set the local device parameter value.
	if (device.setParam(sParam, sValue)) {
		selectDevice();
	} else {
		stabilizeForm();
	}
*/

	// Main session should be dirtier...
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm)
		pMainForm->sessionDirty();
}


// Device port/channel parameter value change slot.
void DeviceForm::changeDevicePortParam ( int iRow, int iCol )
{
	if (m_iDirtySetup > 0)
		return;
	if (iRow < 0 || iCol < 0)
		return;

	//
	//  Device port/channel parameter change...
	//

/* we do that in the model class now ...
	QTreeWidgetItem* pItem = m_ui.DeviceListView->currentItem();
	if (pItem == nullptr || pItem->type() != QSAMPLER_DEVICE_ITEM)
		return;

	Device& device = ((DeviceItem *) pItem)->device();

	int iPort = m_ui.DevicePortComboBox->currentIndex();
	DevicePort *pPort = nullptr;
	if (iPort >= 0 && iPort < device.ports().count())
		pPort = device.ports().at(iPort);
	if (pPort == nullptr)
		return;

	// Table 1st column has the parameter name;
	//const QString sParam = m_ui.DevicePortParamTable->text(iRow, 0);
	//const QString sValue = m_ui.DevicePortParamTable->text(iRow, iCol);
	const QString sParam = m_devicePortParamModel.data(m_devicePortParamModel.index(iRow, 0), Qt::DisplayRole).value<DeviceParameterRow>().name;
	const QString sValue = m_devicePortParamModel.data(m_devicePortParamModel.index(iRow, iCol), Qt::DisplayRole).value<DeviceParameterRow>().param.value;

	// Set the local device port/channel parameter value.
	pPort->setParam(sParam, sValue);
*/

	// Done.
	stabilizeForm();

	// Main session should be dirtier...
	MainForm* pMainForm = MainForm::getInstance();
	if (pMainForm)
		pMainForm->sessionDirty();
}


// Device list view context menu handler.
void DeviceForm::deviceListViewContextMenu ( const QPoint& pos )
{
	MainForm *pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;

	QTreeWidgetItem* pItem = m_ui.DeviceListView->itemAt(pos);
	if (pItem == nullptr)
		return;

	// Build the device context menu...
	QMenu menu(this);
	QAction *pAction;

	bool bClient = (pMainForm->client() != nullptr);
	bool bEnabled = (pItem != nullptr);
	pAction = menu.addAction(
		QIcon(":/images/deviceCreate.png"),
		tr("&Create device"), this, SLOT(createDevice()));
	pAction->setEnabled(bEnabled || (bClient && m_bNewDevice));
	pAction = menu.addAction(
		QIcon(":/images/deviceDelete.png"),
		tr("&Delete device"), this, SLOT(deleteDevice()));
	pAction->setEnabled(bEnabled && !m_bNewDevice);
	menu.addSeparator();
	pAction = menu.addAction(
		QIcon(":/images/formRefresh.png"),
		tr("&Refresh"), this, SLOT(refreshDevices()));
	pAction->setEnabled(bClient);

	menu.exec(pos);
}


// Stabilize current form state.
void DeviceForm::stabilizeForm (void)
{
	MainForm* pMainForm = MainForm::getInstance();
	QTreeWidgetItem* pItem = m_ui.DeviceListView->currentItem();
	bool bClient = (pMainForm && pMainForm->client() != nullptr);
	bool bEnabled = (pItem != nullptr);
	m_ui.DeviceNameTextLabel->setEnabled(bEnabled && !m_bNewDevice);
	m_ui.DriverNameTextLabel->setEnabled(bEnabled &&  m_bNewDevice);
	m_ui.DriverNameComboBox->setEnabled(bEnabled && m_bNewDevice);
	m_ui.DeviceParamTable->setEnabled(bEnabled);
	m_ui.RefreshDevicesPushButton->setEnabled(bClient);
	m_ui.CreateDevicePushButton->setEnabled(bEnabled || (bClient && m_bNewDevice));
	m_ui.DeleteDevicePushButton->setEnabled(bEnabled && !m_bNewDevice);
}


void DeviceForm::updateCellRenderers (void)
{
	const int rows = m_deviceParamModel.rowCount();
	const int cols = m_deviceParamModel.columnCount();
	updateCellRenderers(
		m_deviceParamModel.index(0, 0),
		m_deviceParamModel.index(rows - 1, cols - 1));
}


void DeviceForm::updateCellRenderers (
	const QModelIndex& topLeft, const QModelIndex& bottomRight )
{
	for (int r = topLeft.row(); r <= bottomRight.row(); r++) {
		for (int c = topLeft.column(); c <= bottomRight.column(); c++) {
			const QModelIndex index = m_deviceParamModel.index(r, c);
			m_ui.DeviceParamTable->openPersistentEditor(index);
		}
	}
}


void DeviceForm::updatePortCellRenderers (void)
{
	const int rows = m_devicePortParamModel.rowCount();
	const int cols = m_devicePortParamModel.columnCount();
	updatePortCellRenderers(
		m_devicePortParamModel.index(0, 0),
		m_devicePortParamModel.index(rows - 1, cols - 1));
}


void DeviceForm::updatePortCellRenderers ( 
	const QModelIndex& topLeft, const QModelIndex& bottomRight )
{
	for (int r = topLeft.row(); r <= bottomRight.row(); r++) {
		for (int c = topLeft.column(); c <= bottomRight.column(); c++) {
			const QModelIndex index = m_devicePortParamModel.index(r, c);
			m_ui.DevicePortParamTable->openPersistentEditor(index);
		}
	}
}

} // namespace QSampler


// end of qsamplerDeviceForm.cpp
