/*
 * Copyright (C) 2014-2024 CZ.NIC
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <QDesktopServices>
#include <QMessageBox>
#include <QMutex>
#include <QUrl>

#include "src/about.h"
#include "src/common.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/utility/strings.h"
#include "src/dimensions/dimensions.h"
#include "src/global.h"
#include "src/gui/dlg_about.h"
#include "src/gui/dlg_licence.h"
#include "src/gui/dlg_update_portable.h"
#include "src/gui/dlg_yes_no_checkbox.h"
#include "src/io/filesystem.h" /* suppliedTextFileContent() */
#include "src/io/update.h"
#include "src/io/update_checker.h"
#include "src/settings/preferences.h"
#include "src/settings/prefs_specific.h"
#include "ui_dlg_about.h"

DlgAbout::DlgAbout(UpdateChecker *updateChecker, int tabIdx, QWidget *parent)
    : QDialog(parent),
    m_ui(new (::std::nothrow) Ui::DlgAbout),
    m_updateChecker(updateChecker)
{
	m_ui->setupUi(this);

	m_ui->labelTitle->setText("Datovka"); /* APP_NAME is all lower case. */
	m_ui->labelDescription->setText(tr("Free client for Czech eGov data boxes."));

	QString bits;
	{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
		const QString cpu = QSysInfo::buildCpuArchitecture();
#else /* < Qt-5.4 */
		const QString cpu;
#endif /* >= Qt-5.4 */

		/* Crude architecture bit width check. */
		if (sizeof(void *) == 8) {
			bits = cpu.isEmpty() ? tr("(64-bit)") : tr("(%1; 64-bit)").arg(cpu);
		} else if (sizeof(void *) == 4) {
			bits = cpu.isEmpty() ? tr("(32-bit)") : tr("(%1; 32-bit)").arg(cpu);
		}
	}

#if defined(PORTABLE_APPLICATION)
	m_ui->labelVersion->setText(tr("Portable version") + ": " VERSION " " + bits);
#else
	m_ui->labelVersion->setText(tr("Version") + ": " VERSION " " + bits);
#endif /* PORTABLE_APPLICATION */

	if (m_updateChecker != Q_NULLPTR) {
		connect(m_updateChecker, SIGNAL(stateChanged(int)),
		    this, SLOT(reloadLatestVersionData()));
		connect(m_updateChecker, SIGNAL(packageDownloadProgress(qint64, qint64)),
		    this, SLOT(processPackageDownloadProgress(qint64, qint64)));
	}
	reloadLatestVersionData();
	if (m_updateChecker != Q_NULLPTR) {
		/* Automatically check for updates. */
		if (PrefsSpecific::checkNewVersions(*GlobInstcs::prefsPtr)) {
			m_updateChecker->startVersionCheck(
			    PrefsSpecific::checkNewVersionsCooldown(*GlobInstcs::prefsPtr));
		}
	}
	connect(m_ui->downloadButton, SIGNAL(clicked()), this, SLOT(downloadPkg()));
	connect(m_ui->updateButton, SIGNAL(clicked()), this, SLOT(updateApp()));

	m_ui->labelDonate->setText(
	    "<br/>"
	    + tr("This application is being developed by %1 as open source.").arg("<a href=\"" CZ_NIC_URL "\">CZ.NIC</a>")
	    + " "
	    + tr("It's distributed free of charge. It doesn't contain advertisements or in-app purchases.")
	    + "<br/><br/>"
	    + tr("We want to provide an easy-to-use data-box client.")
	    + " "
	    + tr("Do you want to support us in this effort?")
	    + QString(" <a href=\"" DATOVKA_DONATE_URL "\">%1</a>").arg(tr("Make a donation."))
	    + "<br/><br/>"
	    + tr("Did you know that there is a mobile version of Datovka?"));

	m_ui->homepage->setText("<a href=\"" DATOVKA_HOMEPAGE_URL "\">" + tr("Home Page") + "</a>");
	m_ui->userguide->setText("<a href=\"" DATOVKA_ONLINE_HELP_URL "\">" + tr("User Guide") + "</a>");
	m_ui->faq->setText("<a href=\"" DATOVKA_FAQ_URL "\">" + tr("FAQ") + "</a>");
	m_ui->bestpractice->setText("<a href=\"" DATOVKA_BEST_PRACTICE_URL "\">" + tr("Best Practice") + "</a>");

	QString copyrightHtml("Copyright &copy; 2014–2024 <a href=\"" CZ_NIC_URL "\">CZ.NIC, z. s. p. o.</a>");
	copyrightHtml += "<br/>" + tr("Support") + ": &lt;<a href=\"mailto:" SUPPORT_MAIL "?Subject=[Datovka%20" VERSION "]\">" SUPPORT_MAIL "</a>&gt;";
	m_ui->labelCopy->setText(copyrightHtml);

	QString librariesStr("<b>");
	librariesStr += tr("Depends on libraries:");
	librariesStr += "</b><br/>";
	m_ui->labelDevelopment->setText(
	    tr("This application is being developed by %1 as open source." ).arg("<a href=\"" CZ_NIC_URL "\">CZ.NIC, z. s. p. o.</a>")
	    + "<br/><br/>"
	    + tr("If you have any problems with the application or you have found any errors or you just have an idea how to improve this application please contact us using the following e-mail address:")
	    + "<br/>"
	    + "&lt;<a href=\"mailto:" SUPPORT_MAIL "?Subject=[Datovka%20" VERSION "]\">" SUPPORT_MAIL "</a>&gt;");
	m_ui->labelLibs->setText(librariesStr + libraryDependencies().join("<br/>"));

	m_ui->labelDisclaimer->setText(
	    tr("This application is available as it is. Usage of this application is at own risk. The association CZ.NIC is in no circumstances liable for any damage directly or indirectly caused by using or not using this application.")
	    + "<br/><br/>"
	    + tr("CZ.NIC isn't the operator nor the administrator of the Data Box Information System (ISDS). The operation of the ISDS is regulated by Czech law and regulations.")
	    + "<br/><br/>"
	    + tr("This software is distributed under the GNU GPL version 3 licence."));
	m_ui->textEditLicence->setPlainText(
	    suppliedTextFileContent(TEXT_FILE_LICENCE));
	if (m_ui->textEditLicence->toPlainText().isEmpty()) {
		m_ui->textEditLicence->setPlainText(
		    tr("File '%1' either doesn't exist or is empty.")
		        .arg(expectedTextFilePath(TEXT_FILE_LICENCE)));
	}

	QString html;
	html = "<center><h4>"
	    + tr("We're glad that you are using the Datovka application.")
	    + "</h4><p>"
	    + QStringLiteral("<img src=:/icons/64x64/datovka.png/>")
	    + "</p><p>"
	    + tr("The Datovka project aims to provide client software available for everyone for accessing the Data Box Information System.")
	    + "</p><p>"
	    + tr("CZ.NIC, z.s.p.o. provides this application free of charge and without advertisements.")
	    + "<br/>"
	    + tr("The state doesn't contribute to the development of this application.")
	    + "</p><p><b>"
	    + tr("We'll be very happy if you make a donation for the development and support of the application.")
	    + "</b></p></center>";
	m_ui->donateHtmlText->setText(html);
	m_ui->donateButton->setText(tr("Donate now"));

	if (tabIdx > m_ui->tabWidget->count()) {
		m_ui->tabWidget->setCurrentIndex(TAB_0_ABOUT);
	} else {
		m_ui->tabWidget->setCurrentIndex(tabIdx);
	}

//	connect(m_ui->licenceButton, SIGNAL(clicked()), this, SLOT(showLicence()));
	connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(close()));
	connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(close()));
	connect(m_ui->donateButton, SIGNAL(clicked()), this, SLOT(onDonate()));
}

DlgAbout::~DlgAbout(void)
{
	delete m_ui;
}

bool DlgAbout::about(UpdateChecker *updateChecker, int tabIdx, QWidget *parent)
{
	/* Display the about dialogue only once. */
	static QMutex displayMutex;

	if (displayMutex.tryLock()) {

		DlgAbout dlg(updateChecker, tabIdx, parent);

		const QString dlgName("about");
		const QSize dfltSize = dlg.size();
		{
			const QSize newSize = Dimensions::dialogueSize(&dlg,
			    PrefsSpecific::dlgSize(*GlobInstcs::prefsPtr, dlgName),
			    dfltSize);
			if (newSize.isValid()) {
			    dlg.resize(newSize);
			}
		}

		dlg.exec();

		PrefsSpecific::setDlgSize(*GlobInstcs::prefsPtr, dlgName,
		    dlg.size(), dfltSize);

		displayMutex.unlock();
		return true;
	} else {
		return false;
	}
}

void DlgAbout::showLicence(void)
{
	DlgLicence::showLicence(this);
}

void DlgAbout::reloadLatestVersionData(void)
{
	if (m_updateChecker != Q_NULLPTR) {
		switch (m_updateChecker->state()) {
		case UpdateChecker::STATE_IDLE:
			m_ui->labelVersionCheck->setVisible(false);
			m_ui->downloadButton->setVisible(false);
			m_ui->labelDownload->setVisible(false);
			m_ui->updateButton->setVisible(false);
			break;
		case UpdateChecker::STATE_DOWNLOADING_VERSION:
			m_ui->labelVersionCheck->setText(
			    tr("Checking for new version..."));
			m_ui->labelVersionCheck->setVisible(true);
			m_ui->downloadButton->setVisible(false);
			m_ui->labelDownload->setVisible(false);
			m_ui->updateButton->setVisible(false);
			break;
		case UpdateChecker::STATE_HAVE_VERSION:
			m_ui->labelVersionCheck->setVisible(false);
			m_ui->downloadButton->setVisible(false);
			m_ui->labelDownload->setVisible(false);
			m_ui->updateButton->setVisible(false);
			break;
		case UpdateChecker::STATE_NEW_VERSION_AVAILABLE:
			m_ui->labelVersionCheck->setText(
			    tr("New version %1 is available.").arg(m_updateChecker->newestVersion()));
			m_ui->labelVersionCheck->setVisible(true);
			m_ui->downloadButton->setVisible(false);
			m_ui->labelDownload->setVisible(false);
			m_ui->updateButton->setVisible(false);
			break;
		case UpdateChecker::STATE_CAN_DOWNLOAD_PACKAGE:
			m_ui->labelVersionCheck->setVisible(false);
			m_ui->downloadButton->setText(
			    tr("Download version %1").arg(m_updateChecker->newestVersion()));
			m_ui->downloadButton->setVisible(true);
			m_ui->updateButton->setText(
			    tr("Update to version %1").arg(m_updateChecker->newestVersion()));
			m_ui->labelDownload->setVisible(false);
			m_ui->updateButton->setVisible(false);
			break;
		case UpdateChecker::STATE_DOWNLOADING_PACKAGE:
			m_ui->labelVersionCheck->setVisible(false);
			m_ui->downloadButton->setVisible(false);
			m_ui->labelDownload->setVisible(true);
			m_ui->updateButton->setVisible(false);
			break;
		case UpdateChecker::STATE_HAVE_PACKAGE:
			m_ui->labelVersionCheck->setVisible(false);
			m_ui->downloadButton->setVisible(false);
			m_ui->labelDownload->setVisible(false);
			m_ui->updateButton->setText(
			    tr("Update to version %1").arg(m_updateChecker->newestVersion()));
			m_ui->updateButton->setVisible(true);
			break;
		default:
			Q_ASSERT(0);
			break;
		}
	} else {
		m_ui->labelVersionCheck->setVisible(false);
		m_ui->downloadButton->setVisible(false);
		m_ui->labelDownload->setVisible(false);
		m_ui->updateButton->setVisible(false);
	}
}

void DlgAbout::downloadPkg(void)
{
	if (m_updateChecker != Q_NULLPTR) {
		m_updateChecker->startPackageDownload();
	}
}

void DlgAbout::processPackageDownloadProgress(qint64 bytesReceived,
    qint64 bytesTotal)
{
	m_ui->labelDownload->setText(tr("Downloaded %1 of %2.")
	    .arg(Utility::sizeString(bytesReceived)).arg(Utility::sizeString(bytesTotal)));
}

/*!
 * @brief Creates a notification informing the user that the application is
 *     going to be closed.
 *
 * @param[in] newVersion New application version string.
 * @param[in] parent Window parent.
 * @return True if the application should be closed.
 */
static
bool quitAppNotification(const QString &newVersion, QWidget *parent)
{
	/* The notification is disabled in the specific preferences. */
	if (!PrefsSpecific::showUpdateQuitAppNotification(*GlobInstcs::prefsPtr)) {
		return true;
	}

	bool portable = false;
	QString installerNotification = DlgAbout::tr(
	    "The installation of the new application version '%1' will be attempted. "
	    "The installer will automatically be executed. "
	    "The running application will be closed before the installation. "
	    "Stored user data won't be changed.").arg(newVersion);
	QString portableNotification = DlgAbout::tr(
	    "The installation of the new application version '%1' will be attempted. "
	    "The extraction of the new application package will be attempted. "
	    "The running application will be closed before the extraction.").arg(newVersion);
#if defined(PORTABLE_APPLICATION)
	portable = true;
#endif /* PORTABLE_APPLICATION */

	DlgYesNoCheckbox questionDlg(
	    DlgAbout::tr("Update to version %1").arg(newVersion),
	    (!portable) ? installerNotification : portableNotification,
	    DlgAbout::tr("Don't show this notification again."),
	    DlgAbout::tr("Do you want to proceed?"), parent);

	const int retval = questionDlg.exec();

	if ((retval == DlgYesNoCheckbox::YesChecked) ||
	    (retval == DlgYesNoCheckbox::NoChecked)) {
		/* Disable this notification. */
		PrefsSpecific::setShowUpdateQuitAppNotification(
		    (*GlobInstcs::prefsPtr), false);
	}

	return (retval == DlgYesNoCheckbox::YesUnchecked) ||
	       (retval == DlgYesNoCheckbox::YesChecked);
}

void DlgAbout::updateApp(void)
{
	static const QString cpu_i386("i386");
	static const QString cpu_x86_64("x86_64");

	if (Q_UNLIKELY(m_updateChecker == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	const QString newVersion = m_updateChecker->newestVersion();
	enum SysDetect::PkgType pkgType = SysDetect::PKG_UNKNOWN;
	const QString pkgPath = m_updateChecker->downloadedPackagePath(&pkgType);
	if (Q_UNLIKELY(newVersion.isEmpty() || pkgPath.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	QWidget *mainWin = qobject_cast<QWidget *>(parent());
	if (Q_UNLIKELY(mainWin == Q_NULLPTR)) {
		logErrorNL("%s", "Cannot obtain pointer to main window.");
		return;
	}

#if defined(Q_OS_WIN)
#  if defined(PORTABLE_APPLICATION)
	/* Show info about 64-bit portable version. */
	if ((cpu_i386 == QSysInfo::buildCpuArchitecture())
	    && (cpu_x86_64 == QSysInfo::currentCpuArchitecture())
	    && (SysDetect::PKG_WIN32_PORTABLE == pkgType)) {
		QMessageBox msgBox(mainWin);
		msgBox.setIcon(QMessageBox::Question);
		msgBox.setWindowTitle(tr("64-Bit Version %1").arg(newVersion));
		msgBox.setText(tr("A 64-bit portable package in the version %1 is available. You can migrate to the 64-bit version.").arg(newVersion));
		msgBox.setInformativeText(tr("Do you want to download the 64-bit package and update to the new version %1?").arg(newVersion));
		msgBox.setDetailedText(tr(
		     "Warning: A 64-bit application may not operate on other computers with 32-bit Windows. If you are using this application from a removable media and you want to use it on a 32-bit system then continue using the 32-bit version."));
		msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes);
		msgBox.setDefaultButton(QMessageBox::No);
		if (QMessageBox::Yes == msgBox.exec()) {
			m_updateChecker->startVersionCheck(0, SysDetect::PKG_WIN64_PORTABLE);
			return;
		}
	}
#  endif /* PORTABLE_APPLICATION */
#endif /* Q_OS_WIN */

	close(); /* Close this dialogue. */

	if (!quitAppNotification(newVersion, mainWin)) {
		/* Don't do anything. */
		return;
	}

#if defined(PORTABLE_APPLICATION)
	{
		/* Set portable update context. */
		DlgUpdatePortable::updatePortableData =
		    DlgUpdatePortable::createUpdatePortableData(
		        m_updateChecker->downloadedPackagePath(),
		        m_updateChecker->newestVersion(),
		        GlobInstcs::iniPrefsPtr->confDir());
		mainWin->close(); /* Close main window. Continue in main(). */
		return;
	}
#endif /* PORTABLE_APPLICATION */

	if (Update::executeInstaller(pkgPath)) {
		mainWin->close(); /* Close main window. Let detached process run. */
		return;
	} else {
		logErrorNL("%s", "Automated installer execution failed.");
		QMessageBox msgBox(mainWin);
		msgBox.setIcon(QMessageBox::Critical);
		msgBox.setWindowTitle(tr("Update failed"));
		msgBox.setText(tr("Update to version '%1' failed. Cannot run downloaded installer.").arg(newVersion));
		msgBox.setInformativeText(tr("Do you want to download and install the new version manually?"));
		msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes);
		msgBox.setDefaultButton(QMessageBox::Yes);
		if (QMessageBox::Yes == msgBox.exec()) {
			QDesktopServices::openUrl(QUrl(DATOVKA_DOWNLOAD_URL,
			    QUrl::TolerantMode));
		}
	}
}

void DlgAbout::onDonate(void)
{
	QDesktopServices::openUrl(QUrl(DATOVKA_DONATE_URL, QUrl::TolerantMode));
	close();
}
