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

#include "src/datovka_shared/io/records_management_db.h"
#include "src/datovka_shared/log/log.h"
#include "src/global.h"
#include "src/gui/datovka.h" /* MainWindow */
#include "src/gui/dlg_download_messages.h"
#include "src/gui/dlg_msg_box_informative.h"
#include "src/gui/dlg_send_message.h" /* dzPrefix() */
#include "src/gui/icon_container.h"
#include "src/gui/message_operations.h" /* MsgOrigin */
#include "src/io/message_db_set_container.h"
#include "src/io/message_db_set.h"
#include "src/records_management/content/automatic_upload_target.h"
#include "src/records_management/content/automatic_upload_target_interaction.h"
#include "src/records_management/gui/dlg_records_management_check_msgs.h"
#include "src/records_management/gui/dlg_records_management_stored.h"
#include "src/records_management/gui/dlg_records_management_upload.h"
#include "src/views/table_home_end_filter.h"
#include "src/views/table_space_selection_filter.h"
#include "src/views/table_tab_ignore_filter.h"
#include "ui_dlg_records_management_check_msgs.h"

/*!
 * @brief Add data into the account model.
 *
 * @param[in,out] model Account model.
 * @param[in]     acntList Account list.
 */
static
void initialiseAccountModel(BackupSelectionModel &model,
    const QList<DlgRecordsManagementCheckMsgs::LocAcntData> &acntList)
{
	for (const DlgRecordsManagementCheckMsgs::LocAcntData &acntData : acntList) {
		model.appendData(false, acntData.accountName, acntData.acntId,
		    QString());
	}
}

DlgRecordsManagementCheckMsgs::DlgRecordsManagementCheckMsgs(
    const RecordsManagementSettings &recMgmtSettings,
    const QList<LocAcntData> &accounts, MainWindow *mw, QWidget *parent)
    : QDialog(parent),
    m_ui(new (::std::nothrow) Ui::DlgRecordsManagementCheckMsgs),
    m_actionDownloadMessage(Q_NULLPTR),
    m_actionSendToRecordsManagement(Q_NULLPTR),
    m_mw(mw),
    m_recMgmtSettings(recMgmtSettings),
    m_accounts(accounts),
    m_recMgmtTargets(),
    m_msgModelRowSelection(),
    m_accountListProxyModel(this),
    m_accountSelectionModel(this),
    m_messageListProxyModel(this),
    m_foundTableModel(this),
    m_uploadInfoCol(-1),
    m_abortUpload(false)
{
	m_ui->setupUi(this);

	m_ui->allCheckBox->setCheckState(Qt::Unchecked);
	m_ui->allCheckBox->setTristate(false);

	connect(m_ui->allCheckBox, SIGNAL(stateChanged(int)),
	    this, SLOT(applyAllSelection(int)));

	m_ui->checkMsgsButton->setEnabled(false);

	connect(m_ui->checkMsgsButton, SIGNAL(clicked(bool)),
	    this, SLOT(checkUploadedMsgs()));
	connect(m_ui->uploadPushButton, SIGNAL(clicked(bool)),
	    this, SLOT(uploadMsgsToRm()));
	m_ui->progressBar->setVisible(false);

	/* Set table view model and properties. */
	m_ui->accountTableView->setNarrowedLineHeight();
	m_ui->accountTableView->horizontalHeader()->setDefaultAlignment(
	    Qt::AlignLeft);
	m_ui->accountTableView->setSelectionMode(
	    QAbstractItemView::ExtendedSelection);
	m_ui->accountTableView->setSelectionBehavior(
	    QAbstractItemView::SelectRows);

	m_ui->msgTableView->setNarrowedLineHeight();
	m_ui->msgTableView->horizontalHeader()->setDefaultAlignment(
	    Qt::AlignLeft);
	m_ui->msgTableView->setSelectionMode(
	    QAbstractItemView::ExtendedSelection);
	m_ui->msgTableView->setSelectionBehavior(
	    QAbstractItemView::SelectRows);

	m_ui->msgTableView->setColumnHidden(MsgFoundModel::COL_SENDER, true);
	m_ui->msgTableView->setColumnHidden(MsgFoundModel::COL_RECIPIENT, true);
	m_ui->msgTableView->setColumnHidden(MsgFoundModel::COL_DELIVERY_TIME, true);
	m_ui->msgTableView->setColumnHidden(MsgFoundModel::COL_ACCEPT_TIME, true);

	connect(&m_accountSelectionModel,
	    SIGNAL(dataChanged(QModelIndex, QModelIndex)),
	    this, SLOT(modifiedAccountSelection()));

	initialiseAccountModel(m_accountSelectionModel, m_accounts);

	m_accountListProxyModel.setSortRole(BackupSelectionModel::ROLE_PROXYSORT);
	m_accountListProxyModel.setSourceModel(&m_accountSelectionModel);
	{
		QList<int> columnList;
		columnList.append(BackupSelectionModel::COL_ACCOUNT_NAME);
		columnList.append(BackupSelectionModel::COL_USERNAME);
		m_accountListProxyModel.setFilterKeyColumns(columnList);
	}
	m_ui->accountTableView->setModel(&m_accountListProxyModel);

	m_ui->accountTableView->setColumnWidth(BackupSelectionModel::COL_ACCOUNT_NAME, 250);

	m_uploadInfoCol = m_foundTableModel.addCustomColumn(tr("Upload info"));

	connect(&m_foundTableModel,
	    SIGNAL(dataChanged(QModelIndex, QModelIndex)),
	    this, SLOT(modifiedMessageDownloadStatus()));

	m_messageListProxyModel.setSortRole(MsgFoundModel::ROLE_PROXYSORT);
	m_messageListProxyModel.setSourceModel(&m_foundTableModel);
	{
		QList<int> columnList;
		columnList.append(MsgFoundModel::COL_ACCOUNT_ID);
		columnList.append(MsgFoundModel::COL_MESSAGE_ID);
		columnList.append(MsgFoundModel::COL_ANNOTATION);
		columnList.append(m_uploadInfoCol);
		m_messageListProxyModel.setFilterKeyColumns(columnList);
	}
	m_ui->msgTableView->setModel(&m_messageListProxyModel);

	m_ui->msgTableView->setSortingEnabled(true);
	m_ui->msgTableView->sortByColumn(MsgFoundModel::COL_ACCOUNT_ID,
	    Qt::AscendingOrder);

	m_ui->msgTableView->setColumnHidden(MsgFoundModel::COL_SENDER, true);
	m_ui->msgTableView->setColumnHidden(MsgFoundModel::COL_RECIPIENT, true);
	m_ui->msgTableView->setColumnHidden(MsgFoundModel::COL_DELIVERY_TIME, true);
	m_ui->msgTableView->setColumnHidden(MsgFoundModel::COL_ACCEPT_TIME, true);
	m_ui->msgTableView->setSquareColumnWidth(MsgFoundModel::COL_ATTACH_DOWNLOADED);

	m_ui->msgTableView->horizontalHeader()->setStretchLastSection(true);

	m_ui->msgTableView->setColumnWidth(MsgFoundModel::COL_ANNOTATION, 250);

	m_ui->accountTableView->installEventFilter(
	    new (::std::nothrow) TableHomeEndFilter(m_ui->accountTableView));
	m_ui->accountTableView->installEventFilter(
	    new (::std::nothrow) TableSpaceSelectionFilter(
	        BackupSelectionModel::COL_MSGS_CHECKBOX, m_ui->accountTableView));
	m_ui->accountTableView->installEventFilter(
	    new (::std::nothrow) TableTabIgnoreFilter(m_ui->accountTableView));

	m_ui->accountFilterLine->setFixedWidth(200);
	m_ui->accountFilterLine->setToolTip(tr("Enter sought expression"));
	m_ui->accountFilterLine->setClearButtonEnabled(true);

	connect(m_ui->accountFilterLine, SIGNAL(textChanged(QString)),
	    this, SLOT(filterAccount(QString)));

	m_ui->accountTableView->setSquareColumnWidth(
	    BackupSelectionModel::COL_MSGS_CHECKBOX);
	m_ui->accountTableView->setColumnHidden(
	    BackupSelectionModel::COL_TESTING, true);
	m_ui->accountTableView->setColumnHidden(
	    BackupSelectionModel::COL_BOX_ID, true);

	m_ui->downloadMissingAttachCheckBox->setCheckState(Qt::Checked);

	m_ui->messageFilterLine->setFixedWidth(200);
	m_ui->messageFilterLine->setToolTip(tr("Enter sought expression"));
	m_ui->messageFilterLine->setClearButtonEnabled(true);

	connect(m_ui->messageFilterLine, SIGNAL(textChanged(QString)),
	    this, SLOT(filterMessage(QString)));

	m_ui->msgTableView->installEventFilter(
	    new (::std::nothrow) TableHomeEndFilter(m_ui->msgTableView));
	m_ui->msgTableView->installEventFilter(
	    new (::std::nothrow) TableTabIgnoreFilter(m_ui->msgTableView));

	m_ui->msgTableView->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(m_ui->msgTableView, SIGNAL(customContextMenuRequested(QPoint)),
	    this, SLOT(viewMessageListContextMenu(QPoint)));
	connect(m_ui->msgTableView, SIGNAL(doubleClicked(QModelIndex)),
	    this, SLOT(messageItemDoubleClicked(QModelIndex)));
	connect(m_ui->msgTableView->selectionModel(),
	    SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
	    this,
	    SLOT(updateMessageSelection(QItemSelection, QItemSelection)));

	modifiedMessageDownloadStatus();
	m_ui->msgGroupBox->setEnabled(m_foundTableModel.rowCount() > 0);

	connect(m_ui->buttonBox, SIGNAL(rejected()),
	    this, SLOT(abortUpload()));

	setupActions();
	databaseConnectActions();
}

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

void DlgRecordsManagementCheckMsgs::openDlg(
    const RecordsManagementSettings &recMgmtSettings,
    const QList<LocAcntData> &accounts, MainWindow *mw, QWidget *parent)
{
	DlgRecordsManagementCheckMsgs dlg(recMgmtSettings, accounts, mw, parent);

	if (mw != Q_NULLPTR) {
		connect(&dlg, SIGNAL(focusSelectedMsg(AcntId, qint64, QString, int)),
		    mw, SLOT(messageItemFromSearchSelection(AcntId, qint64, QString, int)));
	}

	dlg.exec();

	if (mw != Q_NULLPTR) {
		dlg.disconnect(SIGNAL(focusSelectedMsg(AcntId, qint64, QString, int)),
		    mw, SLOT(messageItemFromSearchSelection(AcntId, qint64, QString, int)));
	}
}

void DlgRecordsManagementCheckMsgs::applyAllSelection(int state)
{
	/* Temporarily disconnect actions which may trigger this slot. */
	m_accountSelectionModel.disconnect(
	    SIGNAL(dataChanged(QModelIndex, QModelIndex)),
	    this, SLOT(modifiedAccountSelection()));

	switch (state) {
	case Qt::Unchecked:
		m_ui->allCheckBox->setTristate(false);
		m_accountSelectionModel.setAllCheck(false);
		break;
	case Qt::PartiallyChecked:
		/* Do nothing. */
		break;
	case Qt::Checked:
		m_ui->allCheckBox->setTristate(false);
		m_accountSelectionModel.setAllCheck(true);
		break;
	default:
		Q_ASSERT(0);
		break;
	}

	/* Reconnect actions. */
	connect(&m_accountSelectionModel,
	    SIGNAL(dataChanged(QModelIndex, QModelIndex)),
	    this, SLOT(modifiedAccountSelection()));

	m_ui->checkMsgsButton->setEnabled(m_accountSelectionModel.numChecked() > 0);
}

void DlgRecordsManagementCheckMsgs::filterAccount(const QString &text)
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
	m_accountListProxyModel.setFilterRegularExpression(
	    QRegularExpression(QRegularExpression::escape(text),
	        QRegularExpression::CaseInsensitiveOption));
#else /* < Qt-5.15 */
	m_accountListProxyModel.setFilterRegExp(QRegExp(text,
	    Qt::CaseInsensitive, QRegExp::FixedString));
#endif /* >= Qt-5.15 */
	/* Set filter field background colour. */
	if (text.isEmpty()) {
		m_ui->accountFilterLine->setStyleSheet(
		    SortFilterProxyModel::blankFilterEditStyle);
	} else if (m_accountListProxyModel.rowCount() != 0) {
		m_ui->accountFilterLine->setStyleSheet(
		    SortFilterProxyModel::foundFilterEditStyle);
	} else {
		m_ui->accountFilterLine->setStyleSheet(
		    SortFilterProxyModel::notFoundFilterEditStyle);
	}
}

void DlgRecordsManagementCheckMsgs::filterMessage(const QString &text)
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
	m_messageListProxyModel.setFilterRegularExpression(
	    QRegularExpression(QRegularExpression::escape(text),
	        QRegularExpression::CaseInsensitiveOption));
#else /* < Qt-5.15 */
	m_messageListProxyModel.setFilterRegExp(QRegExp(text,
	    Qt::CaseInsensitive, QRegExp::FixedString));
#endif /* >= Qt-5.15 */
	/* Set filter field background colour. */
	if (text.isEmpty()) {
		m_ui->messageFilterLine->setStyleSheet(
		    SortFilterProxyModel::blankFilterEditStyle);
	} else if (m_messageListProxyModel.rowCount() != 0) {
		m_ui->messageFilterLine->setStyleSheet(
		    SortFilterProxyModel::foundFilterEditStyle);
	} else {
		m_ui->messageFilterLine->setStyleSheet(
		    SortFilterProxyModel::notFoundFilterEditStyle);
	}
}

void DlgRecordsManagementCheckMsgs::modifiedAccountSelection(void)
{
	enum Qt::CheckState state = Qt::PartiallyChecked;

	if ((m_accountSelectionModel.rowCount() == 0) ||
	     (m_accountSelectionModel.numChecked() == m_accountSelectionModel.rowCount())) {
		state = Qt::Checked;
	} else if (m_accountSelectionModel.numChecked() == 0) {
		state = Qt::Unchecked;
	}

	m_ui->allCheckBox->setTristate(state == Qt::PartiallyChecked);
	m_ui->allCheckBox->setCheckState(state);

	m_ui->checkMsgsButton->setEnabled(m_accountSelectionModel.numChecked() > 0);
}

void DlgRecordsManagementCheckMsgs::modifiedMessageDownloadStatus(void)
{
	const bool attachmentsMissing =
	    m_foundTableModel.listDownloaded(false).size() > 0;
	m_ui->downloadMissingAttachCheckBox->setVisible(attachmentsMissing);
}

/*!
 * @brief Obtain list of selected rows.
 *
 * @param[in] view Message table view.
 * @param[in] proxyModel Filter proxy model.
 */
static
QList<int> selectedRows(const QTableView &view,
    const SortFilterProxyModel &proxyModel)
{
	QList<int> rowList;

	QModelIndexList selIdxs =
	    view.selectionModel()->selectedRows(MsgFoundModel::COL_ACCOUNT_ID);

	/* Translate to underlying model. */
	foreach (const QModelIndex &idx, selIdxs) {
		QModelIndex srcIdx = proxyModel.mapToSource(idx);
		if (Q_UNLIKELY(!srcIdx.isValid())) {
			Q_ASSERT(0);
			continue;
		}
		rowList.append(srcIdx.row());
	}

	return rowList;
}

void DlgRecordsManagementCheckMsgs::updateMessageSelection(
    const QItemSelection &selected, const QItemSelection &deselected)
{
	Q_UNUSED(selected);
	Q_UNUSED(deselected);

	m_msgModelRowSelection = selectedRows(*(m_ui->msgTableView),
	    m_messageListProxyModel);

	updateActionActivation();
}

void DlgRecordsManagementCheckMsgs::messageItemDoubleClicked(
    const QModelIndex &index)
{
	if (Q_UNLIKELY(!index.isValid())) {
		return;
	}

	const QModelIndex srcIdx = m_messageListProxyModel.mapToSource(index);
	if (Q_UNLIKELY(!srcIdx.isValid())) {
		Q_ASSERT(0);
		return;
	}

	const MsgFoundModel::MsgIdentification &identif =
	    m_foundTableModel.msgIdentification(srcIdx.row());
	if (Q_UNLIKELY(!identif.acntIdDb.isValid())) {
		Q_ASSERT(0);
		return;
	}

	emit focusSelectedMsg(identif.acntIdDb, identif.msgId.dmId(),
	    MessageDbSet::yearFromDateTime(identif.msgId.deliveryTime()),
	    (int)identif.msgType);
	/* Don't close the dialogue. */
}

void DlgRecordsManagementCheckMsgs::viewMessageListContextMenu(const QPoint &point)
{
	/*
	 * The slot updateMessageSelection() is called before this one
	 * if the selection changes with the right click.
	 */

	if (!m_ui->msgTableView->indexAt(point).isValid()) {
		/* Do nothing. */
		return;
	}

	QMenu *menu = new (::std::nothrow) QMenu(m_ui->msgTableView);
	if (Q_UNLIKELY(menu == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	menu->addAction(m_actionDownloadMessage);
	menu->addSeparator();
	menu->addAction(m_actionSendToRecordsManagement);

	menu->exec(QCursor::pos());
	menu->deleteLater();
}

/*!
 * @brief Get list of uploaded messages from records management database.
 *
 * @return List of messages from records management database.
 */
static
QSet<qint64> getAllRmUploadedMsgIds(void)
{
	if (Q_UNLIKELY(Q_NULLPTR == GlobInstcs::recMgmtDbPtr)) {
		Q_ASSERT(0);
		return QSet<qint64>();
	}

#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
	QList<qint64> allDmIds = GlobInstcs::recMgmtDbPtr->getAllDmIds();
	return QSet<qint64>(allDmIds.begin(), allDmIds.end());
#else /* < Qt-5.14.0 */
	return GlobInstcs::recMgmtDbPtr->getAllDmIds().toSet();
#endif /* >= Qt-5.14.0 */
}

static
QList<DlgRecordsManagementCheckMsgs::LocAcntData> selectedAccounts(
    const BackupSelectionModel &model,
    const QList<DlgRecordsManagementCheckMsgs::LocAcntData> &allAccounts)
{
	QList<DlgRecordsManagementCheckMsgs::LocAcntData> checked;

	for (int row = 0; row < model.rowCount(); ++row) {
		QModelIndex idx(
		    model.index(row, BackupSelectionModel::COL_MSGS_CHECKBOX));
		if (idx.data(Qt::CheckStateRole) == Qt::Unchecked) {
			continue;
		}
		const QString accountName =
		    model.index(row, BackupSelectionModel::COL_ACCOUNT_NAME)
		        .data(Qt::DisplayRole).toString();
		const QString username =
		    model.index(row, BackupSelectionModel::COL_USERNAME)
		        .data(Qt::DisplayRole).toString();
		bool testing = model.index(row, BackupSelectionModel::COL_TESTING)
		        .data(Qt::DisplayRole).toBool();

		/* Collect matching checked. */
		for (const DlgRecordsManagementCheckMsgs::LocAcntData &acntData : allAccounts) {
			if ((accountName == acntData.accountName) &&
			    (AcntId(username, testing) == acntData.acntId)) {
				checked.append(acntData);
			}
		}
	}

	return checked;
}

/*!
 * @brief Convert enum MessageDb::MessageType to enum MessageDirection.
 *
 * @param[in]  type Message type.
 * @param[out] ok Set to true on success.
 * @return Message direction.
 */
static
enum MessageDirection messageType2messageDirection(
    enum MessageDb::MessageType type, bool *ok = Q_NULLPTR)
{
	enum MessageDirection direct = MSG_ALL;

	switch (type) {
	case MessageDb::TYPE_RECEIVED:
		direct = MSG_RECEIVED;
		break;
	case MessageDb::TYPE_SENT:
		direct = MSG_SENT;
		break;
	default:
		Q_ASSERT(0);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return direct;
		break;
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return direct;
}

/*!
 * @brief Construct exhaustive message selection list.
 *
 * @param[in] msgSelection Message selection status.
 * @param[in] model Found table model.
 * @return Origin list of selected messages.
 */
static
QList<MsgOrigin> messageOriginList(const QList<int> &modelRowSelection,
    const MsgFoundModel &model)
{
	QList<MsgOrigin> originList;

	foreach (int row, modelRowSelection) {
		if (Q_UNLIKELY(row < 0)) {
			Q_ASSERT(0);
			continue;
		}
		const MsgFoundModel::MsgIdentification &identif =
		    model.msgIdentification(row);

		originList.append(MsgOrigin(identif.acntIdDb, identif.msgId,
		    messageType2messageDirection(identif.msgType),
		    identif.downloaded));
	}

	return originList;
}

void DlgRecordsManagementCheckMsgs::watchMessageInserted(const AcntId &acntId,
    const MsgId &msgId, int direction)
{
	Q_UNUSED(direction);

	if (Q_UNLIKELY((!acntId.isValid()) || (msgId.dmId() < 0))) {
		Q_ASSERT(0);
		return;
	}

	m_foundTableModel.overrideDownloaded(acntId, msgId, true);
}

void DlgRecordsManagementCheckMsgs::watchMessageDataDeleted(
    const AcntId &acntId, const MsgId &msgId)
{
	if (Q_UNLIKELY((!acntId.isValid()) || (msgId.dmId() < 0))) {
		Q_ASSERT(0);
		return;
	}

	m_foundTableModel.remove(acntId, msgId);
}

void DlgRecordsManagementCheckMsgs::watchRmMsgEntryUpdated(qint64 dmId,
    const QStringList &locations)
{
	if (Q_UNLIKELY(locations.isEmpty())) {
		return;
	}

	/* Remove entries as they have been uploaded. */
	for (const MsgFoundModel::MsgIdentification &identif :
	     m_foundTableModel.listIdentifications(dmId)) {
		m_foundTableModel.remove(identif.acntIdDb, identif.msgId);
	}
}

void DlgRecordsManagementCheckMsgs::viewSendSelectedMessageToRecordsManagementDlg(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_msgModelRowSelection,
	    m_foundTableModel);

	bool aborted = false;

	for (const MsgOrigin &origin : originList) {
		/* Ask whether to cancel the entire operation. */
		if (Q_UNLIKELY(aborted)) {
			 QMessageBox::StandardButton reply = QMessageBox::question(
			    this, tr("Cancel Entire Operation?"),
			    tr("Previous message upload has been cancelled. Should the remaining uploads be also cancelled?"),
			    QMessageBox::Yes | QMessageBox::No);
			if (reply == QMessageBox::Yes) {
				break;
			}
		}

		const AcntIdDb &acntIdDb = origin.acntIdDb;
		const MsgId &msgId = origin.msgId;
		MessageDbSet *dbSet = acntIdDb.messageDbSet();
		if (Q_UNLIKELY(!acntIdDb.isValid() || (msgId.dmId() < 0))) {
			Q_ASSERT(0);
			continue;
		}
		if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
			Q_ASSERT(0);
			continue;
		}

		if (Q_UNLIKELY(!origin.downloaded)) {
			int dlgRet = DlgMsgBox::message(this, QMessageBox::Warning,
			    tr("Missing Message Content"),
			    tr("Complete message '%1' is missing.").arg(msgId.dmId()),
			    tr("First you must download the complete message to continue with the action.") +
			    "\n\n" +
			    tr("Do you want to download the complete message now?"),
			    QString(), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

			if (QMessageBox::Yes == dlgRet) {
				DlgDownloadMessages::downloadAll(
				    MsgOriginAndUpladTarget::fromMsgOriginList(
				        QList<MsgOrigin>({origin})),
				    m_mw, this);
			} else {
				continue;
			}
		}

		MessageDb *messageDb = dbSet->accessMessageDb(msgId.deliveryTime(),
		    false);
		if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
			Q_ASSERT(0);
			continue;
		}

		QByteArray msgRaw = messageDb->getCompleteMessageRaw(msgId.dmId());
		if (Q_UNLIKELY(msgRaw.isEmpty())) {
			Q_ASSERT(0);
			continue;
		}

		/* Show send to records management dialogue. */
		const enum DlgRecordsManagementUpload::CommunicationResult res =
		    DlgRecordsManagementUpload::uploadMessage(
		        *GlobInstcs::recMgmtSetPtr, msgId.dmId(),
		        QString("%1_%2.zfo").arg(dzPrefix(messageDb, msgId.dmId())).arg(msgId.dmId()),
		        msgRaw, QStringList(), this);
		aborted = (res == DlgRecordsManagementUpload::COMM_CANCELLED);
	}
}

void DlgRecordsManagementCheckMsgs::downloadSelectedMessageAttachments(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_msgModelRowSelection,
	    m_foundTableModel);
	DlgDownloadMessages::downloadAll(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);
}

void DlgRecordsManagementCheckMsgs::checkUploadedMsgs(void)
{
	m_ui->accountGroupBox->setEnabled(false);

	m_ui->progressBar->setVisible(true);

	QCoreApplication::processEvents();

	const QList<LocAcntData> checkedAccounts =
	    selectedAccounts(m_accountSelectionModel, m_accounts);

	/* Update messages RM database from records management if needed. */
	if (m_ui->updateUploadedMsgListCheckBox->isChecked()) {
		updateUploadedMsgRmList(checkedAccounts);
		m_ui->updateUploadedMsgListCheckBox->setCheckState(Qt::Unchecked);
	}

	/* Get list of messages from records management database. */
	m_ui->msgGroupBox->setTitle(tr("Collecting messages from records management."));
	const QSet<qint64> rmUploadedMsgs = getAllRmUploadedMsgIds();

	/* Get of messages to be uploaded into records management. */
	fillMsgRmUploadList(checkedAccounts, rmUploadedMsgs);

	m_ui->progressBar->setVisible(false);

	m_ui->accountGroupBox->setEnabled(true);

	modifiedMessageDownloadStatus();
	m_ui->msgGroupBox->setEnabled(m_foundTableModel.rowCount() > 0);
}

void DlgRecordsManagementCheckMsgs::uploadMsgsToRm(void)
{
	m_ui->msgGroupBox->setEnabled(false);

	m_ui->progressBar->setVisible(true);
	m_ui->progressBar->setMinimum(0);
	m_ui->progressBar->setMaximum(m_foundTableModel.rowCount() * 3); /* 1/3 collect data, 2/3 upload */
	int pbValue = m_ui->progressBar->minimum();
	QCoreApplication::processEvents();

	/* Get records management upload hierarchy if model is not empty. */
	m_recMgmtTargets = getRmHierarchy();

	QList< QPair<MsgFoundModel::MsgIdentification, QString> > toBeUploadedComplete; /* Complete messages. */
	QList< QPair<MsgFoundModel::MsgIdentification, QString> > toBeUploadedMissing; /* Missing content. */

	/*
	 * Upload all messages if needed.
	 * Iterates in reverse as rows can be removed.
	 */
	for (int row = m_foundTableModel.rowCount() - 1; row >= 0; --row) {

		if (Q_UNLIKELY(m_abortUpload)) {
			break;
		}

		const MsgFoundModel::MsgIdentification &identif =
		    m_foundTableModel.msgIdentification(row);

		m_ui->progressBar->setValue(pbValue++);

		/* Get message envelope. */
		MessageDbSet *dbSet = identif.acntIdDb.messageDbSet();
		if (Q_UNLIKELY(dbSet == Q_NULLPTR)) {
			Q_ASSERT(0);
			continue;
		}
		MessageDb *messageDb = dbSet->accessMessageDb(
		    identif.msgId.deliveryTime(), false);
		if (Q_UNLIKELY(messageDb == Q_NULLPTR)) {
			Q_ASSERT(0);
			continue;
		}
		Isds::Envelope env = messageDb->getMessageEnvelope(
		    identif.msgId.dmId());

		/* Check for matching upload hierarchy location for message. */
		bool ambiguous = false;
		QString recMgmtHierarchyId = RecMgmt::uniqueUploadReceived(
		    env, m_recMgmtTargets, &ambiguous);

		/* No hierarchy ID match. */
		if (Q_UNLIKELY(recMgmtHierarchyId.isEmpty())) {
			m_foundTableModel.overrideCustomColumnVal(
			    identif.acntIdDb, identif.msgId, m_uploadInfoCol,
			    tr("No hierarchy match."));
			continue;
		}

		/* Multiple possible targets have been detected. */
		if (ambiguous) {
			m_foundTableModel.overrideCustomColumnVal(
			    identif.acntIdDb, identif.msgId, m_uploadInfoCol,
			    tr("Multiple possible targets have been detected."));
			continue;
		}

		if (messageDb->isCompleteMessageInDb(identif.msgId.dmId())) {
			toBeUploadedComplete.append({identif, recMgmtHierarchyId});
		} else {
			/*
			 * Download missing attachments only when the message
			 * is going to be uploaded into records management.
			 */
			toBeUploadedMissing.append({identif, recMgmtHierarchyId});
		}

	}

	/* Download missing. */
	if ((m_ui->downloadMissingAttachCheckBox->checkState() == Qt::Checked) &&
	    (!toBeUploadedMissing.empty())) {
		QList<MsgOrigin> originList;
		for (const QPair<MsgFoundModel::MsgIdentification, QString> &pair : toBeUploadedMissing) {
			const MsgFoundModel::MsgIdentification &identif = pair.first;
			originList.append(MsgOrigin(identif.acntIdDb,
			    identif.msgId,
			    (identif.msgType == MessageDb::TYPE_RECEIVED) ? MSG_RECEIVED : MSG_SENT,
			    identif.downloaded));
		}
		if (!originList.isEmpty()) {
			DlgDownloadMessages::downloadAll(
			    MsgOriginAndUpladTarget::fromMsgOriginList(originList),
			    m_mw, this);

			/* Copy newly downloaded into complete list. */
			for (const QPair<MsgFoundModel::MsgIdentification, QString> &pair : toBeUploadedMissing) {
				const MsgFoundModel::MsgIdentification &identif = pair.first;
				MessageDbSet *dbSet = identif.acntIdDb.messageDbSet();
				if (Q_UNLIKELY(dbSet == Q_NULLPTR)) {
					Q_ASSERT(0);
					continue;
				}
				MessageDb *messageDb = dbSet->accessMessageDb(
				    identif.msgId.deliveryTime(), false);
				if (Q_UNLIKELY(messageDb == Q_NULLPTR)) {
					Q_ASSERT(0);
					continue;
				}
				if (messageDb->isCompleteMessageInDb(identif.msgId.dmId())) {
					toBeUploadedComplete.append(pair);
				} else {
					m_foundTableModel.overrideCustomColumnVal(
					    identif.acntIdDb, identif.msgId, m_uploadInfoCol,
					    tr("No message data."));
				}
			}
		}
	}

	/* Recompute progress bar increment. */
	int pbIncr = (toBeUploadedComplete.size() > 0) ?
	    ((m_ui->progressBar->maximum() - pbValue) / toBeUploadedComplete.size()) : 0;

	for (const QPair<MsgFoundModel::MsgIdentification, QString> &pair : toBeUploadedComplete) {
		if (Q_UNLIKELY(m_abortUpload)) {
			break;
		}

		const MsgFoundModel::MsgIdentification &identif = pair.first;
		const QString &recMgmtHierarchyId = pair.second;

		pbValue += pbIncr;
		m_ui->progressBar->setValue(pbValue);

		MessageDbSet *dbSet = identif.acntIdDb.messageDbSet();
		if (Q_UNLIKELY(dbSet == Q_NULLPTR)) {
			Q_ASSERT(0);
			continue;
		}
		MessageDb *messageDb = dbSet->accessMessageDb(
		    identif.msgId.deliveryTime(), false);
		if (Q_UNLIKELY(messageDb == Q_NULLPTR)) {
			Q_ASSERT(0);
			continue;
		}

		const QByteArray msgRaw(messageDb->getCompleteMessageRaw(
		     identif.msgId.dmId()));
		if (Q_UNLIKELY(msgRaw.isEmpty())) {
			/* Should be downloaded. */
			m_foundTableModel.overrideCustomColumnVal(
			    identif.acntIdDb, identif.msgId, m_uploadInfoCol,
			    tr("No message data."));
			continue;
		}

		if (Q_UNLIKELY(m_abortUpload)) {
			break;
		}

		/* Show send dialogue and upload message to records management. */
		const enum DlgRecordsManagementUpload::CommunicationResult res =
		    DlgRecordsManagementUpload::uploadMessage(
		        *GlobInstcs::recMgmtSetPtr, identif.msgId.dmId(),
		        QString("%1_%2.zfo").arg(dzPrefix(messageDb,
		            identif.msgId.dmId())).arg(identif.msgId.dmId()),
		        msgRaw,
		        recMgmtHierarchyId.isEmpty() ?
		            QStringList() : QStringList(recMgmtHierarchyId),
		        this);
		if (res == DlgRecordsManagementUpload::COMM_CANCELLED) {
			/* Cancel entire loop. */
			break;
		}

		/*
		 * The uploaded message is removed from the model in slot
		 * watchRmMsgEntryUpdated().
		 */
	}

	m_ui->progressBar->setValue(m_ui->progressBar->maximum());
	m_ui->progressBar->setVisible(false);

	modifiedMessageDownloadStatus();
	m_ui->msgGroupBox->setEnabled(m_foundTableModel.rowCount() > 0);
}

void DlgRecordsManagementCheckMsgs::abortUpload(void)
{
	m_abortUpload = true;
}

void DlgRecordsManagementCheckMsgs::setupActions(void)
{
	m_actionDownloadMessage = new (::std::nothrow) QAction(this);
	if (m_actionDownloadMessage != Q_NULLPTR) {
		m_actionDownloadMessage->setObjectName(QString::fromUtf8("actionDownloadMessage"));
		m_actionDownloadMessage->setIcon(IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE_DOWNLOAD));
		m_actionDownloadMessage->setMenuRole(QAction::NoRole);
		m_actionDownloadMessage->setText(tr("Download Signed Message"));
		m_actionDownloadMessage->setToolTip(tr("Download the complete message, i.e. including attachments, and verify its signature."));
	}

	m_actionSendToRecordsManagement = new (::std::nothrow) QAction(this);
	if (m_actionSendToRecordsManagement != Q_NULLPTR) {
		m_actionSendToRecordsManagement->setObjectName(QString::fromUtf8("actionSendToRecordsManagement"));
		m_actionSendToRecordsManagement->setIcon(IconContainer::construcIcon(IconContainer::ICON_BRIEFCASE));
		m_actionSendToRecordsManagement->setMenuRole(QAction::NoRole);
		m_actionSendToRecordsManagement->setText(tr("Send to Records Management"));
		m_actionSendToRecordsManagement->setToolTip(tr("Send message to records management service."));
	}

	connect(m_actionDownloadMessage, SIGNAL(triggered()),
	    this, SLOT(downloadSelectedMessageAttachments()));
	connect(m_actionSendToRecordsManagement, SIGNAL(triggered()),
	    this, SLOT(viewSendSelectedMessageToRecordsManagementDlg()));
}

void DlgRecordsManagementCheckMsgs::databaseConnectActions(void)
{
	connect(GlobInstcs::msgDbsPtr, SIGNAL(messageInserted(AcntId, MsgId, int)),
	    this, SLOT(watchMessageInserted(AcntId, MsgId, int)));
	connect(GlobInstcs::msgDbsPtr, SIGNAL(messageDataDeleted(AcntId, MsgId)),
	    this, SLOT(watchMessageDataDeleted(AcntId, MsgId)));

	connect(GlobInstcs::recMgmtDbPtr, SIGNAL(msgEntryUpdated(qint64, QStringList)),
	    this, SLOT(watchRmMsgEntryUpdated(qint64, QStringList)));
}

void DlgRecordsManagementCheckMsgs::updateUploadedMsgRmList(
    const QList<LocAcntData> &selectedAccounts)
{
	QList<DlgRecordsManagementStored::LocAcntData> accounts;
	for (const LocAcntData &acntData : selectedAccounts) {
		DlgRecordsManagementStored::LocAcntData data(
		    acntData.accountName, acntData.acntId, acntData.dbSet);
		accounts.append(data);
	}

	DlgRecordsManagementStored::updateStoredInformation(m_recMgmtSettings,
	    accounts, this);
}

const RecMgmt::AutomaticUploadTarget
DlgRecordsManagementCheckMsgs::getRmHierarchy(void)
{
	return DlgRecordsManagementUpload::uploadHierarchyTargets(
	    m_recMgmtSettings, this);
}

void DlgRecordsManagementCheckMsgs::fillMsgRmUploadList(
    const QList<LocAcntData> &selectedAccounts,
    const QSet<qint64> &rmUploadedMsgs)
{
	QSet<qint64> toUploadMsgIds;
	m_ui->progressBar->setMinimum(0);
	m_ui->progressBar->setMaximum(selectedAccounts.count());
	int pbValue = m_ui->progressBar->minimum();

	m_foundTableModel.removeAllRows();

	/* Get list of all messages missing in the records management. */
	for (const LocAcntData &acntData : selectedAccounts) {

		if (Q_UNLIKELY(Q_NULLPTR == acntData.dbSet)) {
			Q_ASSERT(0);
			continue;
		}

		m_ui->progressBar->setValue(pbValue++);
		m_ui->msgGroupBox->setTitle(
		    tr("Collecting messages for account '%1'.").arg(acntData.acntId.username()));
		QCoreApplication::processEvents();

		QList<MessageDb::SoughtMsg> msgData;
		const AcntIdDb ai(acntData.acntId.username(),
		    acntData.acntId.testing(), acntData.dbSet);

		/* Get all messages from account databases. */
		for (const MsgId &msgId : acntData.dbSet->getAllMsgIds(MessageDb::TYPE_RECEIVED)) {
			/* Message is not in records management nor in upload list. */
			if (!rmUploadedMsgs.contains(msgId.dmId()) &&
			    !toUploadMsgIds.contains(msgId.dmId())) {
				toUploadMsgIds.insert(msgId.dmId());

				msgData.append(acntData.dbSet->msgsGetMsgDataFromId(msgId.dmId()));
			}
		}
		for (const MsgId &msgId : acntData.dbSet->getAllMsgIds(MessageDb::TYPE_SENT)) {
			/* Message is not in records management nor in upload list. */
			if (!rmUploadedMsgs.contains(msgId.dmId()) &&
			    !toUploadMsgIds.contains(msgId.dmId())) {
				toUploadMsgIds.insert(msgId.dmId());

				msgData.append(acntData.dbSet->msgsGetMsgDataFromId(msgId.dmId()));
			}
		}

		m_foundTableModel.appendData(ai, msgData);
	}

	m_ui->progressBar->setValue(m_ui->progressBar->maximum());

	int msgCnt = m_foundTableModel.rowCount();
	if (Q_UNLIKELY(msgCnt == 0)) {
		m_ui->msgGroupBox->setTitle(
		    tr("There are no messages to be uploaded."));
		return;
	}

	m_ui->uploadPushButton->setVisible(true);
	m_ui->msgGroupBox->setTitle(
	    tr("Number of missing messages in the records management: %1").arg(msgCnt));
}

void DlgRecordsManagementCheckMsgs::updateActionActivation(void)
{
	const int numSelected = m_msgModelRowSelection.size();

	m_actionDownloadMessage->setEnabled(numSelected > 0);
	m_actionSendToRecordsManagement->setEnabled(numSelected == 1);
}
