/*
 * Copyright (C) 2014-2026 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 <QDir>

#include "src/datovka_shared/utility/strings.h" /* Utility::generateRandomString */
#include "src/datovka_shared/worker/pool.h"
#include "src/global.h"
#include "src/gui/dlg_message_check.h"
#include "src/gui/dlg_msg_box_detail.h"
#include "src/worker/message_emitter.h"
#include "src/worker/task_authenticate_message.h"
#include "ui_dlg_message_check.h"

DlgMessageCheck::DlgMessageCheck(QWidget *parent)
   : QDialog(parent),
   m_ui(new (::std::nothrow) Ui::DlgMessageCheck),
   m_tasks(),
   m_speeds()
{
	m_ui->setupUi(this);

	initDialogue();
}

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

void DlgMessageCheck::authenticateMessage(const AcntId &acntId,
    const QString &fileName, enum TaskAuthenticateMessage::MsgSizeType sizeType,
    QWidget *parent)
{
	if (Q_UNLIKELY(fileName.isEmpty())) {
		return;
	}

	DlgMessageCheck dlg(parent);

	dlg.m_ui->taskLabel->setText(tr("Checking file %1")
	    .arg(QDir::toNativeSeparators(fileName)));

	const QString timeStr = QDateTime::currentDateTimeUtc().toString();
	const QString transactionId = QString("%1_%2_%3_%4")
	    .arg(acntId.username())
	    .arg(timeStr)
	    .arg(0)
	    .arg(Utility::generateRandomString(6));

	TaskAuthenticateMessage *task = new (::std::nothrow) TaskAuthenticateMessage(
	    acntId, transactionId, fileName, sizeType);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	dlg.m_tasks[transactionId] = task;

	task->setAutoDelete(false);
	GlobInstcs::workPoolPtr->assignLo(task);

	dlg.setCursor(Qt::BusyCursor);

	dlg.exec();
}

void DlgMessageCheck::authenticateMessage(const AcntId &acntId,
    const QByteArray &data, enum TaskAuthenticateMessage::MsgSizeType sizeType,
    QWidget *parent)
{
	DlgMessageCheck dlg(parent);

	dlg.m_ui->taskLabel->setText(tr("Checking message"));

	const QString timeStr = QDateTime::currentDateTimeUtc().toString();
	const QString transactionId = QString("%1_%2_%3_%4")
	    .arg(acntId.username())
	    .arg(timeStr)
	    .arg(0)
	    .arg(Utility::generateRandomString(6));

	TaskAuthenticateMessage *task = new (::std::nothrow) TaskAuthenticateMessage(
	    acntId, transactionId, data, sizeType);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	dlg.m_tasks[transactionId] = task;

	task->setAutoDelete(false);
	GlobInstcs::workPoolPtr->assignLo(task);

	dlg.setCursor(Qt::BusyCursor);

	dlg.exec();
}

void DlgMessageCheck::done(int resultCode)
{
	/* Don't ask if global halt requested. */
	if ((!GlobInstcs::req_halt) && (!m_tasks.empty())) {

		int ret = DlgMsgBoxDetail::message(this, QMessageBox::Question,
		    tr("Close Message Check Dialogue?"),
		    tr("Do you want to close this dialogue?"),
		    tr("Closing this dialogue will abort all unfinished actions."), QString(),
		    QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
		if (ret == QMessageBox::No) {
			/* Don't close the dialogue. */
			return;
		}
	}

	/* Ask all pending tasks to quit. */
	for (TaskAuthenticateMessage *task : m_tasks) {
		task->requestQuit();
	}

	/*
	 * Wait for tasks to finish and collect them if to global shutdown
	 * required.
	 *
	 * TODO -- A better way to dispose discarded tasks must be implemented.
	 *
	 * The use of QCoreApplication::processEvents() is discouraged by Qt
	 * documentation.
	 *
	 * Extend worker/pool interface to accommodate more generic approach.
	 * All tasks should have a transaction/stak identifier.
	 * All tasks should have a requestQuit() method.
	 * The pool could have a storage for finished tasks.
	 * Finished tasks should be acquired via a poll interface rather than
	 * remembering their pointers in the caller code.
	 * Uncollected tasks should be deleted after a period of time?
	 */
	if (!GlobInstcs::req_halt) {
		while (!m_tasks.isEmpty()) {
			QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
		}
	}

	/* Close the dialogue. */
	QDialog::done(resultCode);
}

void DlgMessageCheck::watchUploadProgress(const AcntId &acntId,
    const QString &transactId, qint64 uploadTotal, qint64 uploadCurrent)
{
	Q_UNUSED(acntId);

	if (m_tasks.contains(transactId)) {

		if (Q_UNLIKELY(!m_speeds.contains(transactId))) {
			m_speeds[transactId] = NetworkSpeed::createCounter(uploadCurrent);
		} else {
			NetworkSpeed &netSpeed = m_speeds[transactId];
			netSpeed.updateCounter(uploadCurrent);
		}

		const NetworkSpeed &netSpeed = m_speeds[transactId];

		m_ui->singleProgressBar->setMinimum(0);
		m_ui->singleProgressBar->setMaximum(uploadTotal);
		m_ui->singleProgressBar->setValue(uploadCurrent);
		/*
		 * Don't set speed in m_ui->singleProgressBar->setFormat()
		 * because when maximum us unknown the no text is displayed.
		 */
		m_ui->singleResultLabel->setText(netSpeed.uploadOverview());
	}
}

void DlgMessageCheck::watchUploadProgressFinished(const AcntId &acntId,
    const QString &transactId, int result, const QString &resultDesc,
    const QVariant &resultVal)
{
	Q_UNUSED(acntId);
	Q_UNUSED(result);
	Q_UNUSED(resultDesc);
	Q_UNUSED(resultVal);

	if (m_tasks.contains(transactId)) {

		m_speeds.remove(transactId);

		m_ui->singleProgressBar->setMinimum(0);
		m_ui->singleProgressBar->setMaximum(0);
		m_ui->singleProgressBar->setValue(0);
		m_ui->singleResultLabel->setText(QString());

	}
}

void DlgMessageCheck::watchDownloadProgress(const AcntId &acntId,
    const QString &transactId, const MsgId &msgId,
    qint64 downloadTotal, qint64 downloadCurrent)
{
	Q_UNUSED(acntId);
	Q_UNUSED(transactId);
	Q_UNUSED(msgId);
	Q_UNUSED(downloadTotal);
	Q_UNUSED(downloadCurrent);

	/* Do nothing as very little data are downloaded. */
}

void DlgMessageCheck::watchDownloadProgressFinished(const AcntId &acntId,
    const QString &transactId, const MsgId &msgId,
    int result, const QString &resultDesc)
{
	Q_UNUSED(acntId);
	Q_UNUSED(msgId);
	Q_UNUSED(result);

	if (m_tasks.contains(transactId)) {

		m_speeds.remove(transactId);

		m_ui->singleProgressBar->setMinimum(0);
		m_ui->singleProgressBar->setMaximum(0);
		m_ui->singleProgressBar->setValue(0);
		m_ui->singleProgressBar->resetFormat();
		m_ui->singleProgressBar->setEnabled(false);
		m_ui->singleProgressBar->setVisible(false);
		m_ui->singleResultLabel->setText(resultDesc);

		TaskAuthenticateMessage *task = m_tasks.take(transactId);

		QString titleString;
		QString overviewString;
		QString resultString;
		if (Q_NULLPTR != task) {
			switch (task->m_result) {
			case TaskAuthenticateMessage::AUTH_SUCCESS:
				titleString = tr("Message Authentic");
				overviewString =
				    tr("The ISDS server confirms that the message is authentic.")
				        + "<br/>";
				resultString =
				    tr("The message was <b>successfully authenticated</b> against data on the ISDS server.")
				        + "<br/><br/>"
				        + tr("This message has passed through the ISDS system and has not been tampered with since.");
				break;
			case TaskAuthenticateMessage::AUTH_NOT_EQUAL:
				titleString = tr("Message Not Authentic");
				overviewString =
				    tr("The ISDS server informs that the message is not authentic.")
				        + "<br/>";
				resultString =
				    tr("The message was <b>not</b> authenticated by the ISDS.") +
				        + "<br/><br/>"
				        + tr("It is either not a valid ZFO file or it was modified since it was obtained from ISDS.");
				break;
			case TaskAuthenticateMessage::AUTH_ISDS_ERROR:
				titleString = tr("Authentication Failed");
				overviewString =
				    tr("Message authentication failed.")
				        + "<br/>";
				resultString =
				    tr("Authentication of the message has been interrupted because the connection to ISDS failed.") +
				        + "<br/><br/>"
				        + tr("Check your internet connection.");
				break;
			case TaskAuthenticateMessage::AUTH_DATA_ERROR:
				titleString = tr("Authentication Failed");
				overviewString =
				    tr("Message authentication failed.")
				        + "<br/>";
				resultString =
				    tr("Authentication of the message has been stopped because the message file has a wrong format.");
				break;
			case TaskAuthenticateMessage::AUTH_ERR:
			default:
				titleString = tr("Authentication Failed");
				overviewString =
				    tr("Message authentication failed.")
				        + "<br/>";
				resultString =
				    tr("An undefined error occurred.")
				        + "<br/><br/>"
				        + tr("Try it again later.");
				break;
			}
		}
		setWindowTitle(titleString);
		m_ui->taskLabel->setText(overviewString);
		m_ui->singleResultLabel->setText(resultString);

		delete task;

		if (m_tasks.isEmpty()) {
			this->setCursor(Qt::ArrowCursor);
		}

	}
}

void DlgMessageCheck::initDialogue(void)
{
	setWindowTitle(tr("Check Message"));

	m_ui->singleProgressBar->setMinimum(0);
	m_ui->singleProgressBar->setMaximum(100);
	m_ui->singleProgressBar->setValue(0);
//	m_ui->singleProgressBar->setEnabled(true);
//	m_ui->singleProgressBar->setVisible(true);
	m_ui->singleResultLabel->setText(QString());

	connect(GlobInstcs::msgProcEmitterPtr, SIGNAL(uploadProgress(AcntId, QString, qint64, qint64)),
	    this, SLOT(watchUploadProgress(AcntId, QString, qint64, qint64)),
	    Qt::QueuedConnection); /* Qt::QueuedConnection because it must be executed in receiver's thread. */

	connect(GlobInstcs::msgProcEmitterPtr, SIGNAL(uploadProgressFinished(AcntId, QString, int, QString, QVariant)),
	    this, SLOT(watchUploadProgressFinished(AcntId, QString, int, QString, QVariant)),
	    Qt::QueuedConnection); /* Qt::QueuedConnection because it must be executed in receiver's thread. */

	connect(GlobInstcs::msgProcEmitterPtr, SIGNAL(downloadProgress(AcntId, QString, MsgId, qint64, qint64)),
	    this, SLOT(watchDownloadProgress(AcntId, QString, MsgId, qint64, qint64)),
	    Qt::QueuedConnection); /* Qt::QueuedConnection because it must be executed in receiver's thread. */

	connect(GlobInstcs::msgProcEmitterPtr, SIGNAL(downloadProgressFinished(AcntId, QString, MsgId, int, QString)),
	    this, SLOT(watchDownloadProgressFinished(AcntId, QString, MsgId, int, QString)),
	    Qt::QueuedConnection); /* Qt::QueuedConnection because it must be executed in receiver's thread. */

}
