// qsamplerInstrumentForm.cpp
//
/****************************************************************************
   Copyright (C) 2003-2021, rncbc aka Rui Nuno Capela. All rights reserved.
   Copyright (C) 2007, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

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

#include "qsamplerAbout.h"
#include "qsamplerInstrumentForm.h"

#include "qsamplerOptions.h"
#include "qsamplerChannel.h"
#include "qsamplerMainForm.h"

#include <QMessageBox>
#include <QPushButton>
#include <QFileDialog>

// Needed for lroundf()
#ifdef CONFIG_ROUND
#include <cmath>
#else
static inline long lroundf ( float x )
{
	if (x >= 0.0f)
		return long(x + 0.5f);
	else
		return long(x - 0.5f);
}
#endif


namespace QSampler {

//-------------------------------------------------------------------------
// QSampler::InstrumentForm -- Instrument map item form implementation.
//

InstrumentForm::InstrumentForm ( QWidget *pParent )
	: QDialog(pParent)
{
	m_ui.setupUi(this);

	// Initialize locals.
	m_pInstrument = nullptr;

	m_iDirtySetup = 0;
	m_iDirtyCount = 0;
	m_iDirtyName  = 0;

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


	QObject::connect(m_ui.MapComboBox,
		SIGNAL(activated(int)),
		SLOT(changed()));
	QObject::connect(m_ui.BankSpinBox,
		SIGNAL(valueChanged(int)),
		SLOT(changed()));
	QObject::connect(m_ui.ProgSpinBox,
		SIGNAL(valueChanged(int)),
		SLOT(changed()));
	QObject::connect(m_ui.NameLineEdit,
		SIGNAL(textChanged(const QString&)),
		SLOT(nameChanged(const QString&)));
	QObject::connect(m_ui.EngineNameComboBox,
		SIGNAL(activated(int)),
		SLOT(changed()));
	QObject::connect(m_ui.InstrumentFileComboBox,
		SIGNAL(activated(const QString&)),
		SLOT(updateInstrumentName()));
	QObject::connect(m_ui.InstrumentFileToolButton,
		SIGNAL(clicked()),
		SLOT(openInstrumentFile()));
	QObject::connect(m_ui.InstrumentNrComboBox,
		SIGNAL(activated(int)),
		SLOT(instrumentNrChanged()));
	QObject::connect(m_ui.VolumeSpinBox,
		SIGNAL(valueChanged(int)),
		SLOT(changed()));
	QObject::connect(m_ui.LoadModeComboBox,
		SIGNAL(activated(int)),
		SLOT(changed()));
	QObject::connect(m_ui.DialogButtonBox,
		SIGNAL(accepted()),
		SLOT(accept()));
	QObject::connect(m_ui.DialogButtonBox,
		SIGNAL(rejected()),
		SLOT(reject()));
}


InstrumentForm::~InstrumentForm (void)
{
}


// Channel dialog setup formal initializer.
void InstrumentForm::setup ( Instrument *pInstrument )
{
	m_pInstrument = pInstrument;

	m_iDirtySetup = 0;
	m_iDirtyCount = 0;
	m_iDirtyName  = 0;

	if (m_pInstrument == nullptr)
		return;

	// Check if we're up and connected.
	MainForm* pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;
	if (pMainForm->client() == nullptr)
		return;

	Options *pOptions = pMainForm->options();
	if (pOptions == nullptr)
		return;

	// It can be a brand new channel, remember?
	bool bNew = (m_pInstrument->bank() < 0 || m_pInstrument->prog() < 0);
	if (!bNew) {
		m_pInstrument->getInstrument();
		m_iDirtyName++;
	}

	// Avoid nested changes.
	m_iDirtySetup++;

	// Load combo box history...
	pOptions->loadComboBoxHistory(m_ui.InstrumentFileComboBox);

	// Populate maps list.
	m_ui.MapComboBox->clear();
	m_ui.MapComboBox->insertItems(0, Instrument::getMapNames());

	// Populate Engines list.
	const char **ppszEngines
		= ::lscp_list_available_engines(pMainForm->client());
	if (ppszEngines) {
		m_ui.EngineNameComboBox->clear();
		for (int iEngine = 0; ppszEngines[iEngine]; iEngine++)
			m_ui.EngineNameComboBox->addItem(ppszEngines[iEngine]);
	}
	else pMainForm->appendMessagesClient("lscp_list_available_engines");

	// Read proper instrument information,
	// and populate the instrument form fields.

	// Instrument map name...
	int iMap = (bNew ? pOptions->iMidiMap : m_pInstrument->map());
	if (iMap < 0)
		iMap = 0;
	const QString& sMapName = Instrument::getMapName(iMap);
	if (!sMapName.isEmpty()) {
		m_ui.MapComboBox->setCurrentIndex(
			m_ui.MapComboBox->findText(sMapName,
				Qt::MatchExactly | Qt::MatchCaseSensitive));
	}

	// It might be no maps around...
	bool bMapEnabled = (m_ui.MapComboBox->count() > 0);
	m_ui.MapTextLabel->setEnabled(bMapEnabled);
	m_ui.MapComboBox->setEnabled(bMapEnabled);

	// Instrument bank/program...
	int iBank = (bNew ? pOptions->iMidiBank : m_pInstrument->bank());
	int iProg = (bNew ? pOptions->iMidiProg : m_pInstrument->prog()) + 1;
	if (bNew && iProg > 128) {
		iProg = 1;
		iBank++;
	}
	m_ui.BankSpinBox->setValue(iBank);
	m_ui.ProgSpinBox->setValue(iProg);

	// Instrument name...
	m_ui.NameLineEdit->setText(m_pInstrument->name());

	// Engine name...
	QString sEngineName = m_pInstrument->engineName();
	if (sEngineName.isEmpty() || bNew)
		sEngineName = pOptions->sEngineName;
	if (sEngineName.isEmpty())
		sEngineName = Channel::noEngineName();
	if (m_ui.EngineNameComboBox->findText(sEngineName,
			Qt::MatchExactly | Qt::MatchCaseSensitive) < 0) {
		m_ui.EngineNameComboBox->addItem(sEngineName);
	}
	m_ui.EngineNameComboBox->setCurrentIndex(
		m_ui.EngineNameComboBox->findText(sEngineName,
			Qt::MatchExactly | Qt::MatchCaseSensitive));

	// Instrument filename and index...
	QString sInstrumentFile = m_pInstrument->instrumentFile();
	if (sInstrumentFile.isEmpty())
		sInstrumentFile = Channel::noInstrumentName();
	m_ui.InstrumentFileComboBox->setEditText(sInstrumentFile);
	m_ui.InstrumentNrComboBox->clear();
	m_ui.InstrumentNrComboBox->insertItems(0,
		Channel::getInstrumentList(sInstrumentFile,
		pOptions->bInstrumentNames));
	m_ui.InstrumentNrComboBox->setCurrentIndex(m_pInstrument->instrumentNr());

	// Instrument volume....
	int iVolume = (bNew ? pOptions->iVolume :
		::lroundf(100.0f * m_pInstrument->volume()));
	m_ui.VolumeSpinBox->setValue(iVolume);

	// Instrument load mode...
	int iLoadMode = (bNew ? pOptions->iLoadMode :
		m_pInstrument->loadMode());
	m_ui.LoadModeComboBox->setCurrentIndex(iLoadMode);

	// Done.
	m_iDirtySetup--;
	stabilizeForm();
}


// Special case for name change,
void InstrumentForm::nameChanged ( const QString& /* sName */ )
{
	if (m_iDirtySetup > 0)
		return;

	m_iDirtyName++;
	changed();
}


// Browse and open an instrument file.
void InstrumentForm::openInstrumentFile (void)
{
	MainForm* pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;

	Options *pOptions = pMainForm->options();
	if (pOptions == nullptr)
		return;

	// FIXME: the instrument file filters should be restricted,
	// depending on the current engine.
	const QString& sEngineName = m_ui.EngineNameComboBox->currentText().toUpper();
	QStringList filters;
	if (sEngineName.contains("GIG"))
		filters << tr("GIG Instrument files") + " (*.gig *.dls)";
	if (sEngineName.contains("SFZ"))
		filters << tr("SFZ Instrument files") + " (*.sfz)";
	if (sEngineName.contains("SF2"))
		filters << tr("SF2 Instrument files") + " (*.sf2)";
	const QString& filter = filters.join(";;");

	QString sInstrumentFile = QFileDialog::getOpenFileName(this,
		tr("Instrument files"),   // Caption.
		pOptions->sInstrumentDir, // Start here.
		filter                    // File filter.
	);

	if (sInstrumentFile.isEmpty())
		return;

	m_ui.InstrumentFileComboBox->setEditText(sInstrumentFile);
	updateInstrumentName();
}


// Refresh the actual instrument name.
void InstrumentForm::updateInstrumentName (void)
{
	MainForm* pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;

	Options *pOptions = pMainForm->options();
	if (pOptions == nullptr)
		return;

	// TODO: this better idea would be to use libgig
	// to retrieve the REAL instrument names.
	m_ui.InstrumentNrComboBox->clear();
	m_ui.InstrumentNrComboBox->insertItems(0,
		Channel::getInstrumentList(
			m_ui.InstrumentFileComboBox->currentText(),
			pOptions->bInstrumentNames)
	);

	instrumentNrChanged();
}


// Special case for instrumnet index change,
void InstrumentForm::instrumentNrChanged (void)
{
	if (m_iDirtySetup > 0)
		return;

	if (m_ui.NameLineEdit->text().isEmpty() || m_iDirtyName == 0) {
		m_ui.NameLineEdit->setText(m_ui.InstrumentNrComboBox->currentText());
		m_iDirtyName = 0;
	}

	changed();
}


// Accept settings (OK button slot).
void InstrumentForm::accept (void)
{
	if (m_pInstrument == nullptr)
		return;

	MainForm* pMainForm = MainForm::getInstance();
	if (pMainForm == nullptr)
		return;
	if (pMainForm->client() == nullptr)
		return;

	Options *pOptions = pMainForm->options();
	if (pOptions == nullptr)
		return;

	if (m_iDirtyCount > 0) {
		m_pInstrument->setMap(m_ui.MapComboBox->currentIndex());
		m_pInstrument->setBank(m_ui.BankSpinBox->value());
		m_pInstrument->setProg(m_ui.ProgSpinBox->value() - 1);
		m_pInstrument->setName(m_ui.NameLineEdit->text());
		m_pInstrument->setEngineName(m_ui.EngineNameComboBox->currentText());
		m_pInstrument->setInstrumentFile(m_ui.InstrumentFileComboBox->currentText());
		m_pInstrument->setInstrumentNr(m_ui.InstrumentNrComboBox->currentIndex());
		m_pInstrument->setVolume(0.01f * float(m_ui.VolumeSpinBox->value()));
		m_pInstrument->setLoadMode(m_ui.LoadModeComboBox->currentIndex());
	}

	// Save default engine name, instrument directory and history...
	pOptions->sInstrumentDir = QFileInfo(
		m_ui.InstrumentFileComboBox->currentText()).dir().absolutePath();
	pOptions->sEngineName = m_ui.EngineNameComboBox->currentText();
	pOptions->iMidiMap  = m_ui.MapComboBox->currentIndex();
	pOptions->iMidiBank = m_ui.BankSpinBox->value();
	pOptions->iMidiProg = m_ui.ProgSpinBox->value();
	pOptions->iVolume   = m_ui.VolumeSpinBox->value();
	pOptions->iLoadMode = m_ui.LoadModeComboBox->currentIndex();
	pOptions->saveComboBoxHistory(m_ui.InstrumentFileComboBox);

	// Just go with dialog acceptance.
	QDialog::accept();
}


// Reject settings (Cancel button slot).
void InstrumentForm::reject (void)
{
	bool bReject = true;

	// Check if there's any pending changes...
	if (m_iDirtyCount > 0) {
		switch (QMessageBox::warning(this,
			tr("Warning"),
			tr("Some channel settings have been changed.\n\n"
			"Do you want to apply the changes?"),
			QMessageBox::Apply |
			QMessageBox::Discard |
			QMessageBox::Cancel)) {
		case QMessageBox::Apply:
			accept();
			return;
		case QMessageBox::Discard:
			break;
		default:    // Cancel.
			bReject = false;
			break;
		}
	}

	if (bReject)
		QDialog::reject();
}


// Dirty up settings.
void InstrumentForm::changed (void)
{
	if (m_iDirtySetup > 0)
		return;

	m_iDirtyCount++;
	stabilizeForm();
}


// Stabilize current form state.
void InstrumentForm::stabilizeForm (void)
{
	bool bValid = !m_ui.NameLineEdit->text().isEmpty()
		&& m_ui.EngineNameComboBox->currentIndex() >= 0
		&& m_ui.EngineNameComboBox->currentText() != Channel::noEngineName();

	const QString& sPath = m_ui.InstrumentFileComboBox->currentText();
	bValid = bValid && !sPath.isEmpty() && QFileInfo(sPath).exists();

	m_ui.DialogButtonBox->button(
		QDialogButtonBox::Ok)->setEnabled(m_iDirtyCount > 0 && bValid);
}

} // namespace QSampler


// end of qsamplerInstrumentForm.cpp
