/*
 * 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.
 */

#pragma once

#include <QDateTime>
#include <QMutex>
#include <QObject>
#include <QString>

#include "src/settings/system_detection.h"

class Prefs; /* Forward declaration. */
class ProxiesSettings; /* Forward declaration. */
class QNetworkAccessManager; /* Forward declaration. */
class QNetworkReply; /* Forward declaration. */

class UpdateChecker : public QObject {
	Q_OBJECT

public:
	enum State {
		STATE_IDLE = 0, /*!< Not doing anything, don't have any data. */
		STATE_DOWNLOADING_VERSION, /*!< Downloading version and package info. */
		STATE_HAVE_VERSION, /*!< Version and package info have been downloaded. */
		STATE_NEW_VERSION_AVAILABLE, /*!< New version is available. */
		STATE_CAN_DOWNLOAD_PACKAGE, /*!< Version and package info downloaded. Package URL available. */
		STATE_DOWNLOADING_PACKAGE, /*!< Downloading installation package. */
		STATE_HAVE_PACKAGE /*!< Latest installation package is available. */
	};

	/*!
	 * @brief Constructor.
	 *
	 * @param[in,out] prefs Preferences container.
	 * @param[in] proxies Proxies settings.
	 * @param[in] parent Parent object.
	 */
	UpdateChecker(Prefs &prefs, ProxiesSettings &proxies,
	    QObject *parent = Q_NULLPTR);

	/*!
	 * @brief Destructor.
	 */
	~UpdateChecker(void);

	/*!
	 * @brief Tries contacting the server to obtain latest version.
	 *
	 * @param[in] cooldownSecs If greater than number of seconds since last
	 *                         successful version check then no check will
	 *                         be performed.
	 *                         Use negative or zero to override.
	 * @return True if download started.
	 */
	bool startVersionCheck(int cooldownSecs = 0,
	    enum SysDetect::PkgType preferredPkgType = SysDetect::PKG_UNKNOWN);

	/*!
	 * @brief Tries downloading the installation package.
	 *
	 * @param[in] forceNewDownload Whether to overwrite existing downloaded
	 *                             package.
	 * @return True if download started.
	 */
	bool startPackageDownload(bool forceNewDownload = false);

	/*
	 * Use QCoreApplication::applicationVersion() to obtain application
	 * version.
	 */

	/*!
	 * @brief Return state.
	 *
	 * @return State.
	 */
	enum State state(void) const;

	/*!
	 * @brief Available version.
	 *
	 * @return Non-empty string containing newest available version if this
	 *     could be obtained.
	 */
	QString newestVersion(void) const;

	/*!
	 * @brief Check whether there is a newer version available.
	 */
	bool newerVersionAvailable(void) const;

	/*!
	 * @brief Get URL of the newest package suitable for the running
	 *     application.
	 *
	 * @return String with URL. Empty string if no suitable package found.
	 */
	QString packageUrl(void) const;

	/*!
	 * @brief Get path to latest installation package if there is one stored.
	 *
	 * @param[out] downloadedPkgType Downloaded package type.
	 * @return String containing path to an existing file.
	 */
	QString downloadedPackagePath(
	    enum SysDetect::PkgType *downloadedPkgType = Q_NULLPTR) const;

	/*!
	 * @brief Check whether a package type can be replaced by a different
	 *     type.
	 *
	 * @param[in] fromPkgType Original package type to be replaced.
	 * @param[in] toPkgType Replacement package type.
	 * @return True if original type can be replaced.
	 */
	static
	bool allowPackageTypeChange(enum SysDetect::PkgType fromPkgType,
	    enum SysDetect::PkgType toPkgType);

signals:
	/*!
	 * @brief Emitted when state changes.
	 *
	 * @param[in] state State.
	 */
	void stateChanged(int state);

	/*!
	 * @brief Emitted when newer available version is detected.
	 *
	 * @param[in] version Version string without leading non-numerical
	 *     characters.
	 */
	void newerVersionFound(const QString &version);

	/*!
	 * @brief Signals the package download progress.
	 *
	 * @param[in] bytesReceived Downloaded.
	 * @param[in] bytesTotal Total.
	 */
	void packageDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);

private slots:
	/*!
	 * @brief Read version reply from network reply.
	 *
	 * @param[in] reply Network reply.
	 */
	void processNetworkReply(QNetworkReply *reply);

	/*!
	 * @brief Process the package download reply progress.
	 *
	 * @param[in] bytesReceived Downloaded.
	 * @param[in] bytesTotal Total.
	 */
	void processPackageDownloadProgress(qint64 bytesReceived,
	    qint64 bytesTotal);

private:
	QMutex m_lock; /*!< Explicit lock. */

	Prefs &m_prefs; /*!< Preferences where to store persistent data. */
	ProxiesSettings &m_proxies; /*!< Proxy settings. */

	QDateTime m_latestCheckAttemptTime; /*!< Latest check time. */
	QDateTime m_latestSuccessfulCheckTime; /*!< Latest check time. */
	QString m_newestVersion; /*!< Obtained new version string. */
	QString m_csv; /*!< Package URLs. */

	enum SysDetect::PkgType m_downloadedPkgType;

	enum State m_state;

	bool m_versionFoundEmitted; /*!< Restricts multiple version found signals. */
	int m_packageDownloadAttempt; /*!< Package download attempt number. */

	QNetworkAccessManager *m_nam; /*!< Network access manager. */
	QNetworkReply *m_verRep; /*!< Version request reply. */
	QNetworkReply *m_cvsRep; /*!< Package CSV file reply. */
	QNetworkReply *m_sha256Rep; /*!< SHA256SUMS reply. */
	QNetworkReply *m_pkgRep; /*!< Package file reply. */
};
