// qmidinetOptions.cpp
//
/****************************************************************************
   Copyright (C) 2010-2025, rncbc aka Rui Nuno Capela. All rights reserved.

   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 "qmidinetAbout.h"
#include "qmidinetOptions.h"

#include <QTextStream>

#include <QApplication>

#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
#include <QCommandLineParser>
#include <QCommandLineOption>
#if defined(Q_OS_WINDOWS)
#include <QMessageBox>
#endif
#endif


//-------------------------------------------------------------------------
// qmidinetOptions - Prototype settings structure (pseudo-singleton).
//

// Singleton instance pointer.
qmidinetOptions *qmidinetOptions::g_pOptions = nullptr;

// Singleton instance accessor (static).
qmidinetOptions *qmidinetOptions::getInstance (void)
{
	return g_pOptions;
}


// Constructor.
qmidinetOptions::qmidinetOptions (void)
	: m_settings(QMIDINET_DOMAIN, QMIDINET_TITLE)
{
	// Pseudo-singleton reference setup.
	g_pOptions = this;

	loadOptions();
}


// Default Destructor.
qmidinetOptions::~qmidinetOptions (void)
{
	saveOptions();

	// Pseudo-singleton reference shut-down.
	g_pOptions = nullptr;
}


// Explicit load method.
void qmidinetOptions::loadOptions (void)
{
	// And go into general options group.
	m_settings.beginGroup("/Options");

	// General options...
	m_settings.beginGroup("/General");
	iNumPorts = m_settings.value("/NumPorts", 1).toInt();
	bAlsaMidi = m_settings.value("/AlsaMidi", true).toBool();
	bJackMidi = m_settings.value("/JackMidi", false).toBool();
	m_settings.endGroup();

	// Network specific options...
	m_settings.beginGroup("/Network");
	sInterface = m_settings.value("/Interface").toString();
	sUdpAddr = m_settings.value("/UdpAddr", QMIDINET_UDP_IPV4_ADDR).toString();
	iUdpPort = m_settings.value("/UdpPort", QMIDINET_UDP_PORT).toInt();
	m_settings.endGroup();

	m_settings.endGroup();
}


// Explicit save method.
void qmidinetOptions::saveOptions (void)
{
	// And go into general options group.
	m_settings.beginGroup("/Options");

	// General options...
	m_settings.beginGroup("/General");
	m_settings.setValue("/NumPorts", iNumPorts);
	m_settings.setValue("/AlsaMidi", bAlsaMidi);
	m_settings.setValue("/JackMidi", bJackMidi);
	m_settings.endGroup();

	// Network specific options...
	m_settings.beginGroup("/Network");
	m_settings.setValue("/Interface", sInterface);
	m_settings.setValue("/UdpAddr", sUdpAddr);
	m_settings.setValue("/UdpPort", iUdpPort);
	m_settings.endGroup();

	m_settings.endGroup();

	// Save/commit to disk.
	m_settings.sync();
}


// Settings accessor.
QSettings& qmidinetOptions::settings (void)
{
	return m_settings;
}


#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)

void qmidinetOptions::show_error( const QString& msg )
{
#if defined(Q_OS_WINDOWS)
	QMessageBox::information(nullptr, QApplication::applicationName(), msg);
#else
	const QByteArray tmp = msg.toUtf8() + '\n';
	::fputs(tmp.constData(), stderr);
#endif
}

#else

// Help about command line options.
void qmidinetOptions::print_usage ( const QString& arg0 )
{
	QTextStream out(stderr);
	const QString sEot = "\n\t";
	const QString sEol = "\n\n";

	out << QObject::tr("Usage: %1 [options]").arg(arg0) + sEol;
	out << QMIDINET_TITLE " - " << QObject::tr(QMIDINET_SUBTITLE) + sEol;
	out << QObject::tr("Options:") + sEol;
	out << "  -n, --num-ports <num-ports>" + sEot +
		QObject::tr("Use this number of ports (default = %1)")
			.arg(iNumPorts) + sEol;
	out << "  -i, --interface <interface>" + sEot +
		QObject::tr("Use specific network interface (default = %1)")
			.arg(sInterface.isEmpty() ? "all" : sInterface) + sEol;
	out << "  -u, --udp-addr <addr>" + sEot +
		QObject::tr("Use specific network address (default = %1)")
			.arg(sUdpAddr) + sEol;
	out << "  -p, --udp-port <port>" + sEot +
		QObject::tr("Use specific network port (default = %1)")
			.arg(iUdpPort) + sEol;
	out << "  -a, --alsa-midi <flag>" + sEot +
		QObject::tr("Enable ALSA MIDI (0|1|yes|no|on|off, default = %1)")
			.arg(int(bAlsaMidi)) + sEol;
	out << "  -j, --jack-midi <flag>" + sEot +
		QObject::tr("Enable JACK MIDI (0|1|yes|no|on|off, default = %1)")
			.arg(int(bJackMidi)) + sEol;
	out << "  -g, --no-gui" + sEot +
		QObject::tr("Disable the graphical user interface (GUI)") + sEol;
	out << "  -?, --help" + sEot +
		QObject::tr("Show help about command line options.") + sEol;
	out << "  -v, --version" + sEot +
		QObject::tr("Show version information.") + sEol;
}

#endif


// Parse command line arguments into m_settings.
bool qmidinetOptions::parse_args ( const QStringList& args )
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)

	QCommandLineParser parser;
	parser.setApplicationDescription(
		QMIDINET_TITLE " - " + QObject::tr(QMIDINET_SUBTITLE));

	const QString s_num_ports  = "num-ports";
	const QString s_interface  = "interface";
	const QString s_udp_addr   = "udp-addr";
	const QString s_udp_port   = "udp-port";
	const QString s_alsa_midi  = "alsa-midi";
	const QString s_jack_midi  = "jack-midi";
	const QString s_no_gui     = "no-gui";
	const QString s_help       = "help";

	parser.addOption({{"n", s_num_ports},
		QObject::tr("Use this number of ports (default = %1)")
			.arg(iNumPorts), "num"});
	parser.addOption({{"i", s_interface},
		QObject::tr("Use specific network interface (default = %1)")
			.arg(sInterface.isEmpty() ? "all" : sInterface), "name"});
	parser.addOption({{"u", s_udp_addr},
		QObject::tr("Use specific network address (default = %1)")
			.arg(sUdpAddr), "addr"});
	parser.addOption({{"p", s_udp_port},
		QObject::tr("Use specific network port (default = %1)")
			.arg(iUdpPort), "port"});
	parser.addOption({{"a", s_alsa_midi},
		QObject::tr("Enable ALSA MIDI (0|1|yes|no|on|off, default = %1)")
			.arg(int(bAlsaMidi)), "flag"});
	parser.addOption({{"j", s_jack_midi},
		QObject::tr("Enable JACK MIDI (0|1|yes|no|on|off, default = %1)")
			.arg(int(bJackMidi)), "flag"});
	parser.addOption({{"g", s_no_gui},
		QObject::tr("Disable the graphical user interface (GUI)")});
	parser.addOption({{"?", s_help},
		QObject::tr("Displays help on command-line options.")});
	const QCommandLineOption& versionOption = parser.addVersionOption();

	if (!parser.parse(args)) {
		show_error(parser.errorText());
		return false;
	}

	if (parser.isSet(s_help)) {
		show_error(parser.helpText());
		return false;
	}

	if (parser.isSet(versionOption)) {
		QString sVersion = QString("%1 %2\n")
			.arg(QMIDINET_TITLE)
			.arg(QCoreApplication::applicationVersion());
		sVersion += QString("Qt: %1").arg(qVersion());
	#if defined(QT_STATIC)
		sVersion += "-static";
	#endif
	#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
		sVersion += ' ';
		sVersion += '(';
		sVersion += QApplication::platformName();
		sVersion += ')';
	#endif
		sVersion += '\n';
		show_error(sVersion);
		return false;
	}

	if (parser.isSet(s_num_ports)) {
		bool bOK = false;
		const int iVal = parser.value(s_num_ports).toInt(&bOK);
		if (!bOK) {
			show_error(QObject::tr("Option -n requires an argument (num)."));
			return false;
		}
		iNumPorts = iVal;
	}

	if (parser.isSet(s_interface)) {
		sInterface = parser.value(s_interface); // Maybe empty!
	}

	if (parser.isSet(s_udp_addr)) {
		const QString& sVal = parser.value(s_udp_addr);
		if (sVal.isEmpty()) {
			show_error(QObject::tr("Option -u requires an argument (addr)."));
			return false;
		}
		sUdpAddr = sVal;
	}

	if (parser.isSet(s_udp_port)) {
		bool bOK = false;
		const int iVal = parser.value(s_udp_port).toInt(&bOK);
		if (!bOK) {
			show_error(QObject::tr("Option -p requires an argument (port)."));
			return false;
		}
		iUdpPort = iVal;
	}

	if (parser.isSet(s_alsa_midi)) {
		const QString& sVal = parser.value(s_alsa_midi);
		if (sVal.isEmpty()) {
			bAlsaMidi = true;
		} else {
			bAlsaMidi = !(sVal == "0" || sVal == "no" || sVal == "off");
		}
	}

	if (parser.isSet(s_jack_midi)) {
		const QString& sVal = parser.value(s_jack_midi);
		if (sVal.isEmpty()) {
			bJackMidi = true;
		} else {
			bJackMidi = !(sVal == "0" || sVal == "no" || sVal == "off");
		}
	}

	if (parser.isSet(s_no_gui)) {
		// Ignored: parsed on startup...
	}

#else

	QTextStream out(stderr);
	const QString sEot = "\n\t";
	const QString sEol = "\n\n";
	const int argc = args.count();

	for (int i = 1; i < argc; ++i) {

		QString sVal;
		QString sArg = args.at(i);
		const int iEqual = sArg.indexOf('=');
		if (iEqual >= 0) {
			sVal = sArg.right(sArg.length() - iEqual - 1);
			sArg = sArg.left(iEqual);
		} else if (i < argc - 1) {
			sVal = args.at(i + 1);
			if (sVal[0] == '-')
				sVal.clear();
		}

		if (sArg == "-n" || sArg == "--num-ports") {
			if (sVal.isEmpty()) {
				out << QObject::tr("Option -n requires an argument (num-ports).") + sEol;
				return false;
			}
			iNumPorts = sVal.toInt();
			if (iEqual < 0) ++i;
		}
		else
		if (sArg == "-i" || sArg == "--interface") {
			sInterface = sVal; // Maybe empty!
			if (iEqual < 0) ++i;
		}
		else
		if (sArg == "-u" || sArg == "--udp-addr") {
			if (sVal.isEmpty()) {
				out << QObject::tr("Option -d requires an argument (address).") + sEol;
				return false;
			}
			sUdpAddr = sVal;
			if (iEqual < 0) ++i;
		}
		else
		if (sArg == "-p" ||  sArg == "--udp-port") {
			if (sVal.isEmpty()) {
				out << QObject::tr("Option -p requires an argument (port).") + sEol;
				return false;
			}
			iUdpPort = sVal.toInt();
			if (iEqual < 0) ++i;
		}
		else
		if (sArg == "-a" || sArg == "--alsa-midi") {
			if (sVal.isEmpty()) {
				bAlsaMidi = true;
			} else {
				bAlsaMidi = !(sVal == "0" || sVal == "no" || sVal == "off");
				if (iEqual < 0) ++i;
			}
		}
		else
		if (sArg == "-j" || sArg == "--jack-midi") {
			if (sVal.isEmpty()) {
				bJackMidi = true;
			} else {
				bJackMidi = !(sVal == "0" || sVal == "no" || sVal == "off");
				if (iEqual < 0) ++i;
			}
		}
		else
		if (sArg == "-?" || sArg == "--help") {
			print_usage(args.at(0));
			return false;
		}
		else if (sArg == "-v" || sArg == "--version") {
			out << QString("%1: %2\n")
				.arg(QMIDINET_TITLE)
				.arg(PROJECT_VERSION);
			out << QString("Qt: %1").arg(qVersion());
		#if defined(QT_STATIC)
			out << "-static";
		#endif
			out << '\n';
			return false;
		}
	}

#endif

	// Alright with argument parsing.
	return true;
}


// end of qmidinetOptions.cpp
