/*
 * Copyright (C) 2014-2022 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/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/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, 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;
	/* Crude architecture bit width check. */
	if (sizeof(void *) == 8) {
		bits = tr("(64-bit)");
	} else if (sizeof(void *) == 4) {
		bits = tr("(32-bit)");
	}

#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(tr("Do you want to help?") +
	    QString(" <a href=\"" DATOVKA_DONATE_URL "\">%1</a>").arg(tr("Make a donation.")));

	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–2022 <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->labelLibs->setText(librariesStr + libraryDependencies().join("<br/>"));

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

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

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

	if (displayMutex.tryLock()) {
		DlgAbout dlg(updateChecker, parent);
		dlg.exec();
		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)
{
	if (Q_UNLIKELY(m_updateChecker == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	const QString newVersion = m_updateChecker->newestVersion();
	const QString pkgPath = m_updateChecker->downloadedPackagePath();
	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;
	}

	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 {
		QMessageBox msgBox(mainWin);
		msgBox.setIcon(QMessageBox::Critical);
		msgBox.setWindowTitle(tr("Update failed"));
		msgBox.setText(tr("Update to version '%1' failed. Cannot run downloaded installer."));
		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));
		}
	}
}
