/*
 * 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 <algorithm> /* ::std::sort */
#include <QDateTime>
#include <QDesktopServices>
#include <QDir>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QMimeDatabase>
#include <QPrinter>
#include <QPrintPreviewDialog>
#include <QStringBuilder>

#include "src/cli/cmd_compose.h"
#include "src/datovka_shared/compat/compiler.h" /* macroStdMove */
#include "src/datovka_shared/isds/box_interface.h"
#include "src/datovka_shared/isds/message_interface_vodz.h"
#include "src/datovka_shared/isds/type_conversion.h"
#include "src/datovka_shared/isds/types.h"
#include "src/datovka_shared/localisation/localisation.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/settings/prefs.h"
#include "src/datovka_shared/settings/records_management.h"
#include "src/datovka_shared/utility/pdf_printer.h"
#include "src/datovka_shared/utility/strings.h"
#include "src/datovka_shared/worker/pool.h"
#include "src/delegates/combobox_delegate.h"
#include "src/dimensions/dimensions.h"
#include "src/global.h"
#include "src/gui/datovka.h"
#include "src/gui/dlg_contacts.h"
#include "src/gui/dlg_ds_search.h"
#include "src/gui/dlg_msg_box_detail.h"
#include "src/gui/dlg_send_message.h"
#include "src/gui/dlg_yes_no_checkbox.h"
#include "src/gui/helper.h"
#include "src/gui/icon_container.h"
#include "src/model_interaction/attachment_interaction.h"
#include "src/identifiers/account_id_db.h"
#include "src/identifiers/message_id.h"
#include "src/io/account_db.h"
#include "src/io/dbs.h"
#include "src/io/exports.h"
#include "src/io/filesystem.h"
#include "src/io/isds_sessions.h"
#include "src/io/message_db.h"
#include "src/isds/qt_signal_slot.h"
#include "src/settings/accounts.h"
#include "src/settings/prefs_specific.h"
#include "src/views/attachment_table_view.h"
#include "src/views/table_home_end_filter.h"
#include "src/views/table_tab_ignore_filter.h"
#include "src/worker/message_emitter.h"
#include "src/worker/task_upload_attachment.h"
#include "src/worker/task_download_credit_info.h"
#include "src/worker/task_pdz_info.h"
#include "src/worker/task_pdz_send_info.h"
#include "src/worker/task_send_message.h"
#include "src/worker/task_search_owner.h"
#include "src/worker/task_search_owner_fulltext.h"
#include "src/worker/task_search_owner.h"
#include "ui_dlg_send_message.h"

#define SHORT_TEXT_MESSAGE_PDF_NAME "textova-zprava.pdf"

#define TAB_BASIC 0
#define TAB_OPTIONAL 1
#define TAB_AUTHOR_INFO 2
#define TAB_TEXT_MESSAGE 3

static const QString dlgName("send_message");
static const QString recipTableName("recipient_list");
static const QString attachmentTableName("attachment_list");

DlgSendMessage::DlgSendMessage(const QList<AcntIdDb> &acntIdDbList,
    enum Action action, const QList<MsgId> &msgIds, const AcntId &acntId,
    const QString &composeSerialised, class MainWindow *mw, QWidget *parent)
    : QDialog(parent),
    m_ui(new (::std::nothrow) Ui::DlgSendMessage),
    m_keepAliveTimer(),
    m_acntIdDbList(acntIdDbList),
    m_acntId(acntId),
    m_boxId(),
    m_senderName(),
    m_dbType(),
    m_dbEffectiveOVM(false),
    m_dbOpenAddressing(false),
    m_isLoggedIn(false),
    m_isVodz(false),
    m_lastAttAddPath(),
    m_pdzCredit(),
    m_dbSet(Q_NULLPTR),
    m_recipTableModel(this),
    m_attachModel(true, this),
    m_recipProgressProxyModel(this),
    m_recipProgressDelegate(this),
    m_attachProgressProxyModel(this),
    m_attachProgressDelegate(this),
    m_recMgmtHierarchyId(),
    m_transactIds(),
    m_sentMsgResultList(),
    m_attachsToBeUploaded(),
    m_attachsUploaded(),
    m_attachsFailed(),
    m_attachsSucceeded(),
    m_msgsToBeSent(),
    m_msgsFailed(),
    m_msgsSucceeded(),
    m_dmType(Isds::Type::MT_UNKNOWN),
    m_dmSenderRefNumber(),
    m_pdzInfos(),
    m_tmpDirPath(),
    m_dfltSize(),
    m_mw(mw)
{
	m_ui->setupUi(this);
	/*
	 * Tab order is defined in UI file.
	 * Nevertheless, it behaves strangely. Therefore it is set for some
	 * important elements once more. This seems to mitigate the problem.
	 */
	QWidget::setTabOrder(m_ui->subjectLine, m_ui->addRecipButton);
	QWidget::setTabOrder(m_ui->addRecipButton, m_ui->addAttachButton);
	QWidget::setTabOrder(m_ui->addAttachButton, m_ui->sendButton);
	m_ui->subjectLine->setFocus();

	/* Generate random directory where files can be created. */
	m_tmpDirPath = createTemporarySubdir(TMP_DIR_NAME);

	setIcons();

	/* Set default line height for table views/widgets. */
	m_ui->recipTableView->setNarrowedLineHeight();
	m_ui->recipTableView->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
	m_ui->recipTableView->setSelectionBehavior(
	    QAbstractItemView::SelectRows);

	m_ui->attachTableView->setNarrowedLineHeight();
	m_ui->attachTableView->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
	m_ui->attachTableView->setSelectionBehavior(
	    QAbstractItemView::SelectRows);

	m_recipTableModel.setColouring(true);

	initContent(action, msgIds);
	if ((action == ACT_NEW) && !composeSerialised.isEmpty()) {
		fillContentCompose(composeSerialised);
	}

	m_dfltSize = this->size();
	{
		const QSize newSize = Dimensions::dialogueSize(this,
		    PrefsSpecific::dlgSize(*GlobInstcs::prefsPtr, dlgName),
		    m_dfltSize);
		if (newSize.isValid()) {
			this->resize(newSize);
		}
	}

	Q_ASSERT(!m_boxId.isEmpty());
	Q_ASSERT(Q_NULLPTR != m_dbSet);
}

DlgSendMessage::~DlgSendMessage(void)
{
	PrefsSpecific::setDlgSize(*GlobInstcs::prefsPtr,
	    dlgName, this->size(), m_dfltSize);

	PrefsSpecific::setDlgTblRelColWidths(*GlobInstcs::prefsPtr,
	    dlgName, recipTableName,
	    Dimensions::relativeTableColumnWidths(m_ui->recipTableView));
	PrefsSpecific::setDlgTblColSortOrder(*GlobInstcs::prefsPtr,
	    dlgName, recipTableName,
	    Dimensions::tableColumnSortOrder(m_ui->recipTableView));

	PrefsSpecific::setDlgTblRelColWidths(*GlobInstcs::prefsPtr,
	    dlgName, attachmentTableName,
	    Dimensions::relativeTableColumnWidths(m_ui->attachTableView));
	PrefsSpecific::setDlgTblColSortOrder(*GlobInstcs::prefsPtr,
	    dlgName, attachmentTableName,
	    Dimensions::tableColumnSortOrder(m_ui->attachTableView));

	delete m_ui;
}

void DlgSendMessage::showEvent(QShowEvent *event)
{
	QDialog::showEvent(event);

	Dimensions::setRelativeTableColumnWidths(m_ui->recipTableView,
	    PrefsSpecific::dlgTblRelColWidths(*GlobInstcs::prefsPtr,
	        dlgName, recipTableName));
	Dimensions::setTableColumnSortOrder(m_ui->recipTableView,
	    PrefsSpecific::dlgTblColSortOrder(*GlobInstcs::prefsPtr,
	    dlgName, recipTableName));

	Dimensions::setRelativeTableColumnWidths(m_ui->attachTableView,
	    PrefsSpecific::dlgTblRelColWidths(*GlobInstcs::prefsPtr,
	        dlgName, attachmentTableName));
	Dimensions::setTableColumnSortOrder(m_ui->attachTableView,
	    PrefsSpecific::dlgTblColSortOrder(*GlobInstcs::prefsPtr,
	    dlgName, attachmentTableName));
}

void DlgSendMessage::dmPublishOwnIdStateChanged(int state)
{
	m_ui->tabWidget->setTabEnabled(TAB_AUTHOR_INFO, state == Qt::Checked);
}

void DlgSendMessage::immDownloadStateChanged(int state)
{
	if (state == Qt::Unchecked) {
		m_ui->immRecMgmtUploadCheckBox->setCheckState(Qt::Unchecked);
	}
}

void DlgSendMessage::immRecMgmtUploadStateChanged(int state)
{
	if (state == Qt::Checked) {
		m_ui->immDownloadCheckBox->setCheckState(Qt::Checked);
	}
}

void DlgSendMessage::checkInputFields(void)
{
	bool enable = calculateAndShowTotalAttachSize() &&
	    !m_ui->subjectLine->text().isEmpty() &&
	    (m_recipTableModel.rowCount() > 0) &&
	    (m_attachModel.rowCount() > 0);

	if (m_isLoggedIn) {
		m_ui->sendButton->setEnabled(enable);
	} else {
		/* cannot send message when not logged in. */
		m_ui->sendButton->setEnabled(false);
	}

	/* Enable only when sending a message to a single recipient. */
	m_ui->immRecMgmtUploadCheckBox->setEnabled(m_recipTableModel.rowCount() <= 1);
}

void DlgSendMessage::addRecipientFromLocalContact(void)
{
	QStringList dbIDs;
	DlgContacts::selectContacts(*m_dbSet, &dbIDs, this);
	addRecipientBoxes(dbIDs);
}

void DlgSendMessage::addRecipientFromISDSSearch(void)
{
	QStringList dbIDs(DlgDsSearch::search(m_acntId, m_dbType,
	    m_dbEffectiveOVM, m_dbOpenAddressing, this));
	addRecipientBoxes(dbIDs);
}

void DlgSendMessage::addRecipientManually(void)
{
	bool ok = false;

	QString dbID = QInputDialog::getText(this, tr("Data box ID"),
	    tr("Enter data box ID (7 characters):"), QLineEdit::Normal,
	    QString(), &ok, Qt::WindowStaysOnTopHint);

	if (!ok) {
		return;
	}

	addRecipientBox(dbID, false);
}

void DlgSendMessage::recipientSelectionChanged(const QItemSelection &selected,
    const QItemSelection &deselected)
{
	Q_UNUSED(selected);
	Q_UNUSED(deselected);

	m_ui->removeRecipButton->setEnabled(
	    (m_dmType != Isds::Type::MT_I) && (m_dmType != Isds::Type::MT_A) &&
	    (m_ui->recipTableView->selectionModel()->selectedRows(0).size() > 0));
}

/*!
 * @brief Used for sorting lists of integers.
 */
class RowLess {
public:
	bool operator()(int a, int b) const
	{
		return a < b;
	}
};

/*!
 * @brief Remove selected lines from model related to supplied view.
 *
 * @param[in,out] view Table view to use for determining the selected lines.
 */
static
void removeSelectedEntries(const QTableView *view)
{
	if (Q_UNLIKELY((view == Q_NULLPTR) || (view->model() == Q_NULLPTR))) {
		Q_ASSERT(0);
		return;
	}

	QList<int> rows;
	{
		for (const QModelIndex &idx :
		         view->selectionModel()->selectedRows(0)) {
			rows.append(idx.row());
		}

		::std::sort(rows.begin(), rows.end(), RowLess());
	}

	/* In reverse order. */
	for (int i = rows.size() - 1; i >= 0; --i) {
		view->model()->removeRow(rows.at(i));
	}
}

void DlgSendMessage::deleteRecipientEntries(void)
{
	removeSelectedEntries(m_ui->recipTableView);
}

void DlgSendMessage::showOptionalFormElements(void)
{
	if (!m_ui->dmSenderRefNumber->text().isEmpty()) {
		return;
	}

	bool pdzInit = false;
	for (int i = 0; i < m_recipTableModel.rowCount(); i++) {
		if (Isds::Type::MT_I == m_recipTableModel.index(i,
		        BoxContactsModel::PAYMENT_COL).data(Qt::EditRole)) {
			pdzInit = true;
			break;
		}
	}

	if (!pdzInit) {
		m_ui->dmSenderRefNumberLabel->setStyleSheet(QString());
		m_ui->dmSenderRefNumberLabel->setText(
		    tr("Our reference number:"));
	} else {
		m_ui->dmSenderRefNumberLabel->setStyleSheet(
		    "QLabel { color: red }");
		m_ui->dmSenderRefNumberLabel->setText(
		    tr("Enter reference number:"));
		m_ui->dmSenderRefNumber->setFocus();
		m_ui->tabWidget->setCurrentIndex(TAB_OPTIONAL);
	}
}

void DlgSendMessage::addAttachmentFile(void)
{
	QFileDialog dialog(this);
	dialog.setDirectory(m_lastAttAddPath);
	dialog.setFileMode(QFileDialog::ExistingFiles);
	QStringList fileNames;

	if (dialog.exec()) {
		fileNames = dialog.selectedFiles();
		if (!PrefsSpecific::useGlobalPaths(*GlobInstcs::prefsPtr)) {
			m_lastAttAddPath = dialog.directory().absolutePath();
			PrefsSpecific::setAnctLoadAttachDir(
			    *GlobInstcs::prefsPtr, m_acntId, m_lastAttAddPath);
		}
	}

	insertAttachmentFiles(fileNames);
}

void DlgSendMessage::attachmentSelectionChanged(const QItemSelection &selected,
    const QItemSelection &deselected)
{
	Q_UNUSED(selected);
	Q_UNUSED(deselected);

	int selectionSize =
	    m_ui->attachTableView->selectionModel()->selectedRows(0).size();

	m_ui->removeAttachButton->setEnabled(selectionSize > 0);
	m_ui->openAttachButton->setEnabled(selectionSize == 1);
}

void DlgSendMessage::deleteSelectedAttachmentFiles(void)
{
	removeSelectedEntries(m_ui->attachTableView);
}

void DlgSendMessage::openSelectedAttachment(const QModelIndex &index)
{
	debugSlotCall();

	AttachmentInteraction::openAttachment(this, *m_ui->attachTableView,
	    index);
}

void DlgSendMessage::selectTabTextMessage(void)
{
	m_ui->tabWidget->setCurrentIndex(TAB_TEXT_MESSAGE);
}

void DlgSendMessage::pingIsdsServer(void) const
{
	GuiHelper::pingIsdsServer(m_acntId);
}

/*!
 * @brief Queries ISDS for PDZ info.
 *
 * @param[in] acntId Account identifier.
 * @param[in] dbId Data box identifier.
 * @return PDZ payment info list.
 */
static
QList<Isds::PDZInfoRec> getPdzInfoFromIsds(const AcntId &acntId,
    const QString &dbId)
{
	TaskPDZInfo *task =
	    new (::std::nothrow) TaskPDZInfo(acntId, dbId);
	if (Q_UNLIKELY(Q_NULLPTR == task)) {
		return QList<Isds::PDZInfoRec>();
	}

	task->setAutoDelete(false);
	if (Q_UNLIKELY(!GlobInstcs::workPoolPtr->runSingle(task))) {
		delete task; task = Q_NULLPTR;
		return QList<Isds::PDZInfoRec>();
	}
	QList<Isds::PDZInfoRec> pdzInfos;

	if (Q_UNLIKELY(!task->m_success)) {
		logWarningNL("PDZ info: '%s'.",
		    task->m_isdsLongError.toUtf8().constData());
	} else {
		pdzInfos = ::std::move(task->m_pdzInfoRecs);
	}

	delete task; task = Q_NULLPTR;

	return pdzInfos;
}

void DlgSendMessage::setAccountInfo(int fromComboIdx)
{
	debugSlotCall();

	/* Get user name for selected account. */
	AcntId acntId;
	{
		QVariant itemData = m_ui->fromComboBox->itemData(fromComboIdx);
		if (Q_UNLIKELY(!itemData.canConvert<AcntId>())) {
			Q_ASSERT(0);
			return;
		}
		acntId = qvariant_cast<AcntId>(itemData);
	}
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}

	/* Remove all recipients if account was changed. */
	if (m_acntId != acntId) {
		m_recipTableModel.removeRows(0, m_recipTableModel.rowCount());
		m_acntId = acntId;
	}

	m_isLoggedIn = GuiHelper::isLoggedIn(m_keepAliveTimer, m_mw,
	    m_acntId);

	m_dbSet = GuiHelper::getDbSet(m_acntIdDbList, m_acntId);
	if (Q_UNLIKELY(Q_NULLPTR == m_dbSet)) {
		Q_ASSERT(0);
		return;
	}

	const QString acntDbKey(AccountDb::keyFromLogin(m_acntId.username()));
	m_boxId = GlobInstcs::accntDbPtr->dbId(acntDbKey);
	Q_ASSERT(!m_boxId.isEmpty());
	m_senderName = GlobInstcs::accntDbPtr->senderNameGuess(acntDbKey);
	const Isds::DbOwnerInfo dbOwnerInfo(
	    GlobInstcs::accntDbPtr->getOwnerInfo(acntDbKey));
	if (!dbOwnerInfo.isNull()) {
		m_dbType = Isds::dbType2Str(dbOwnerInfo.dbType());
		m_dbEffectiveOVM = (dbOwnerInfo.dbEffectiveOVM() == Isds::Type::BOOL_TRUE);
		m_dbOpenAddressing = (dbOwnerInfo.dbOpenAddressing() == Isds::Type::BOOL_TRUE);
		m_recipTableModel.setSenderEffectiveOVM(m_dbEffectiveOVM);
	} else {
		m_recipTableModel.setSenderEffectiveOVM(false);
	}
	if (PrefsSpecific::useGlobalPaths(*GlobInstcs::prefsPtr)) {
		m_lastAttAddPath = PrefsSpecific::loadAttachDir(
		    *GlobInstcs::prefsPtr);
	} else {
		m_lastAttAddPath = PrefsSpecific::anctLoadAttachDir(
		    *GlobInstcs::prefsPtr, acntId);
	}

	m_ui->databoxInfo->setToolTip(QString());
	m_ui->dmAllowSubstDelivery->setEnabled(false);
	m_ui->dmAllowSubstDelivery->hide();
	if (Isds::str2DbType(m_dbType) < Isds::Type::BT_OVM_REQ) {
		m_ui->dmAllowSubstDelivery->setEnabled(true);
		m_ui->dmAllowSubstDelivery->show();
	}

	m_ui->databoxInfo->setText("ID: " + m_boxId + " (" + m_dbType + ")");

	/* PDZ info and payment methods. */
	m_pdzInfos.clear();

	if (!m_dbEffectiveOVM) {
		setPDZInfos(getPdzInfoFromIsds(m_acntId, m_boxId));
	}
}

void DlgSendMessage::sendMessage(void)
{
	debugSlotCall();

	const QList<BoxContactsModel::PartialEntry> recipEntries(
	    m_recipTableModel.partialBoxEntries(BoxContactsModel::ANY));

	if (!askPermissionToSendPDZ(recipEntries)) {
		/* Permission not granted, nothing more to do. */
		return;
	}

	if (m_isVodz) {
		if (startUploadAttachments()) {
			this->setCursor(Qt::WaitCursor);
			this->setEnabled(false);
		}
		/* Continue within watchUploadProgressFinished(). */
	} else {
		sendMessageISDS(recipEntries, m_isVodz);
	}
}

void DlgSendMessage::collectSendMessageStatus(const AcntId &acntId,
    const QString &transactId, int result, const QString &resultDesc,
    const QString &dbIDRecipient, const QString &recipientName, bool isPDZ,
    bool isVodz, qint64 dmId, int processFlags, const QString &recMgmtHierarchyId)
{
	debugSlotCall();

	Q_UNUSED(acntId);
	Q_UNUSED(isVodz);
	Q_UNUSED(processFlags);
	Q_UNUSED(recMgmtHierarchyId);

	if (m_transactIds.end() == m_transactIds.find(transactId)) {
		/* Nothing found. */
		return;
	}

	/* Gather data. */
	m_sentMsgResultList.append(TaskSendMessage::ResultData(
	    (enum TaskSendMessage::Result) result, resultDesc,
	    dbIDRecipient, recipientName, isPDZ, dmId));

	if (!m_transactIds.remove(transactId)) {
		logErrorNL("%s",
		    "Was not able to remove a transaction identifier from list of unfinished transactions.");
	}

	if (!m_transactIds.isEmpty()) {
		/* Still has some pending transactions. */
		return;
	}

	/* All transactions finished. */

	this->setCursor(Qt::ArrowCursor);
	this->setEnabled(true);

	int successfullySentCnt = 0;
	QString infoText;

	for (const TaskSendMessage::ResultData &resultData :
	         m_sentMsgResultList) {
		if (TaskSendMessage::SM_SUCCESS == resultData.result) {
			++successfullySentCnt;

			if (resultData.isPDZ) {
				infoText += tr(
				    "Message was successfully sent to "
				    "<i>%1 (%2)</i> as PDZ with number "
				    "<i>%3</i>.").
				    arg(resultData.recipientName).
				    arg(resultData.dbIDRecipient).
				    arg(resultData.dmId) + "<br/>";
			} else {
				infoText += tr(
				    "Message was successfully sent to "
				    "<i>%1 (%2)</i> as message number "
				    "<i>%3</i>.").
				    arg(resultData.recipientName).
				    arg(resultData.dbIDRecipient).
				    arg(resultData.dmId) + "<br/>";
			}
		} else {
			infoText += tr("Message was NOT sent to "
			    "<i>%1 (%2)</i>. Server says: %3").
			    arg(resultData.recipientName).
			    arg(resultData.dbIDRecipient).
			    arg(resultData.errInfo) + "<br/>";
		}
	}
	m_sentMsgResultList.clear();

	if (m_recipTableModel.rowCount() == successfullySentCnt) {
		DlgMsgBoxDetail::message(this, QMessageBox::Information,
		    tr("Message sent"),
		    "<b>" + tr("Message was successfully sent to all recipients.") + "</b>",
		    infoText, QString(), QMessageBox::Ok, QMessageBox::Ok);
		this->accept(); /* Set return code to accepted. */
	} else {
		infoText += "<br/><br/><b>" +
		    tr("Do you want to close the Send message form?") + "</b>";
		int ret = DlgMsgBoxDetail::message(this, QMessageBox::Warning,
		    tr("Message sending error"),
		    "<b>" + tr("Message was NOT sent to all recipients.") + "</b>",
		    infoText, QString(), QMessageBox::Yes | QMessageBox::No,
		    QMessageBox::No);
		if (ret == QMessageBox::Yes) {
			this->close(); /* Set return code to closed. */
		}
	}

	if (!PrefsSpecific::useGlobalPaths(*GlobInstcs::prefsPtr)) {
		PrefsSpecific::setAnctLoadAttachDir(*GlobInstcs::prefsPtr,
		    m_acntId, m_lastAttAddPath);
	}
}

void DlgSendMessage::appendPdf(void)
{
	QString filePath(createPdf());

	m_ui->warningText->setVisible(false);
	m_ui->warningText->setStyleSheet(QString());
	m_ui->warningText->setText(QString());

	if (!filePath.isEmpty()) {
		m_attachModel.insertAttachmentFile(filePath,
		    m_attachModel.rowCount());
		m_ui->tabWidget->setCurrentIndex(TAB_BASIC);
	}
}

void DlgSendMessage::watchAppendedPdfModification(void)
{
	const bool alreadyAttached = m_attachModel.holdsAttachmentFile(createdPdfPath());
	const bool sourcesEmpty = m_ui->textMessageEdit->toPlainText().isEmpty();

	QString warningtext;
	if (alreadyAttached) {
		warningtext =
		    tr("The source text of the text message has been modified since it has been appended to the attachments as a PDF file.")
		    + "\n" +
		    tr("Append the text message using the '%1' button on the '%2' tab in order to apply all modifications to the attachment.")
		        .arg(m_ui->appendPdfButton->text())
		        .arg(m_ui->tabWidget->tabText(TAB_TEXT_MESSAGE));
	} else {
		warningtext =
		    tr("The source text of the text message has been edited but it isn't attached as a PDF file.")
		    + "\n" +
		    tr("Append the text message using the '%1' button on the '%2' tab after you've finished editing.")
		        .arg(m_ui->appendPdfButton->text())
		        .arg(m_ui->tabWidget->tabText(TAB_TEXT_MESSAGE));
	}

	m_ui->warningText->setVisible(alreadyAttached || !sourcesEmpty);
	m_ui->warningText->setStyleSheet("QLabel { color: red }");
	m_ui->warningText->setText(warningtext);
}

void DlgSendMessage::preview(void)
{
	/* Setup taken from Utility::printPDF(). */
	QPrinter printer; /* QPrinter::ScreenResolution */
	printer.setFullPage(true);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
	printer.setPageMargins(QMarginsF(0, 0, 0, 0));
#else /* < Qt-5.3 */
	printer.setPageMargins(0, 0, 0, 0, QPrinter::Millimeter);
#endif /* >= Qt-5.3 */
	/*
	 * Don't specify a format as this limits the available printers in the
	 * print dialogue.
	 * printer.setOutputFormat(QPrinter::PdfFormat)
	 */

	QPrintPreviewDialog previewDlg(&printer, this);
	connect(&previewDlg, SIGNAL(paintRequested(QPrinter *)), this,
	    SLOT(previewGenerate(QPrinter *)));
	{
		/* Resize and centre on screen. */
		const QRect rect = Dimensions::availableScreenSize(this);
		int width = rect.width() * 9 / 10;
		int height = rect.height() * 9 / 10;
		if (width > (height * 8 / 10)) {
			width = height * 8 / 10;
		}
		previewDlg.resize(width, height);
		int x = (rect.width() - width) / 2;
		int y = (rect.height() - height) / 2;
		previewDlg.move(x, y);
	}
	previewDlg.exec();
}

/*!
 * @brief Convert integer to enum Utility::TextFormat.
 *
 * @patam[in] val Integer value.
 * @return text format enumeration value. Unknown values default
 *     to Utility::PLAIN_TEXT.
 */
static
enum Utility::TextFormat int2TextFormat(int val)
{
	switch (val) {
	case Utility::PLAIN_TEXT:
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
	case Utility::MARKDOWN_TEXT:
#endif /* >= Qt-5.14 */
		return (enum Utility::TextFormat)val;
		break;
	default:
		return Utility::PLAIN_TEXT;
		break;
	}
}

void DlgSendMessage::previewGenerate(QPrinter *printer)
{
	enum Utility::TextFormat format =
	    int2TextFormat(m_ui->textFormatComboBox->currentData().toInt());

	Utility::printText(printer, m_ui->textMessageEdit->toPlainText(), format);
}

void DlgSendMessage::viewPdf(void)
{
	QString filePath(createPdf());

	if (!filePath.isEmpty()) {
		QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
	}
}

void DlgSendMessage::textMessageChanged(void)
{
	const bool enabled = !m_ui->textMessageEdit->toPlainText().isEmpty();
	m_ui->previewButton->setEnabled(enabled);
	m_ui->viewPdfButton->setEnabled(enabled);
	m_ui->appendPdfButton->setEnabled(enabled);
}

#define RECIP_PROGRESS_COL BoxContactsModel::BOX_ID_COL
#define ATTACH_PROGRESS_COL AttachmentTblModel::FNAME_COL

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

	int row = m_attachsToBeUploaded.indexOf(transactId);
	if (row >= 0) {
		/* Update attachment progress. */
		m_attachProgressProxyModel.setProgress(
		    row, ATTACH_PROGRESS_COL,
		    0, uploadTotal, uploadCurrent);
		return;
	}

	row = m_msgsToBeSent.value(transactId, -1);
	if (row >= 0) {
		/* Update message progress. */
		m_recipProgressProxyModel.setProgress(
		    row, RECIP_PROGRESS_COL,
		    0, uploadTotal, uploadCurrent);
		return;
	}
}

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

	int row = m_attachsToBeUploaded.indexOf(transactId);
	if (row >= 0) {
		/* Deal with one uploaded attachment. */

		m_attachProgressProxyModel.clearProgress(row, ATTACH_PROGRESS_COL);

		if ((TaskUploadAttachment::UPLOAD_SUCCESS == result) &&
		    resultVal.canConvert<Isds::DmAtt>()) {
			Isds::DmAtt dmAtt = qvariant_cast<Isds::DmAtt>(resultVal);

			/* Collect and store hashes. */
			Isds::DmExtFile dmExtFile = m_attachsUploaded[transactId];
			dmExtFile.setDmAtt(dmAtt);
			m_attachsUploaded[transactId] = macroStdMove(dmExtFile);
			m_attachsSucceeded.insert(transactId);
		} else {
			/* Remember failed. */
			m_attachsFailed[transactId] = resultDesc;
		}

		/*
		 * m_attachsToBeUploaded.size() > 0 is true because
		 * m_attachsToBeUploaded.contains(transactId)
		 */
		if ((m_attachsSucceeded.size() + m_attachsFailed.size()) < m_attachsToBeUploaded.size()) {
			/* Still waiting for some uploads. */
			return;
		}

		/* Everything uploaded. */
		m_attachProgressProxyModel.clearAllProgress();

		if (Q_UNLIKELY(m_attachsFailed.size() > 0)) {
			this->setCursor(Qt::ArrowCursor);
			this->setEnabled(true);

			/* Notify the user that some data couldn't be uploaded. */
			QString infoText;
			QMap<QString, QString>::const_iterator it = m_attachsFailed.constBegin();
			while (it != m_attachsFailed.constEnd()) {
				const int row = m_attachsToBeUploaded.indexOf(it.key());
				if (row >= 0) {
					const QModelIndex index = m_attachModel.index(row, AttachmentTblModel::FNAME_COL);
					const QString fileName = index.data().toString();
					infoText += tr("Attachment <i>%1</i> was NOT uploaded to server. Server says: %2")
					    .arg(fileName)
					    .arg(it.value()) + "<br/>";
				} else {
					Q_ASSERT(0);
				}
				++it;
			}
			infoText += "<br/><br/><b>" +
			    tr("Do you want to close the Send message form?") + "</b>";
			int ret = DlgMsgBoxDetail::message(this, QMessageBox::Warning,
			    tr("Attachment uploading error"),
			    "<b>" + tr("Attachments couldn't be uploaded.") + "</b>",
			    infoText, QString(), QMessageBox::Yes | QMessageBox::No,
			    QMessageBox::No);
			if (ret == QMessageBox::Yes) {
				this->close(); /* Set return code to closed. */
			}

			return;
		}

		/* Here m_attachsSucceeded.size() == m_attachsToBeUploaded.size() is true. */

		/* Continue with message sending. */
		const QList<BoxContactsModel::PartialEntry> recipEntries(
		    m_recipTableModel.partialBoxEntries(BoxContactsModel::ANY));
		sendMessageISDS(recipEntries, true);
		return;
	}

	row = m_msgsToBeSent.value(transactId, -1);
	if (row >= 0) {
		/* Deal with uploaded message. */

		m_recipProgressProxyModel.clearProgress(row, RECIP_PROGRESS_COL);

		if (TaskSendMessage::SM_SUCCESS == result) {
			/* No data are processed here. */

			m_msgsSucceeded.insert(transactId);
		} else {
			/* Remember failed. */
			m_msgsFailed[transactId] = resultDesc;
		}

		/*
		 * m_msgsToBeSent.size() > 0 is true because
		 * m_msgsToBeSent.contains(transactId)
		 */
		if ((m_msgsSucceeded.size() + m_msgsFailed.size()) < m_msgsToBeSent.size()) {
			/* Still waiting for some uploads. */
			return;
		}

		/* Everything uploaded. */
		m_recipProgressProxyModel.clearAllProgress();

		/* Status is collected elsewhere. */
		return;
	}
}

void DlgSendMessage::initContent(enum Action action, const QList<MsgId> &msgIds)
{
	m_ui->warningText->setVisible(false);
	m_ui->warningText->setStyleSheet(QString());
	m_ui->warningText->setText(QString());

	m_recipTableModel.setHeader();
	m_recipProgressProxyModel.setSourceModel(&m_recipTableModel);
	m_ui->recipTableView->setModel(&m_recipProgressProxyModel);
	m_ui->recipTableView->setItemDelegateForColumn(RECIP_PROGRESS_COL, &m_recipProgressDelegate);

	m_ui->recipTableView->setColumnWidth(BoxContactsModel::BOX_ID_COL, 60);
	m_ui->recipTableView->setColumnWidth(BoxContactsModel::BOX_TYPE_COL, 70);
	m_ui->recipTableView->setColumnWidth(BoxContactsModel::BOX_NAME_COL, 120);
	m_ui->recipTableView->setColumnWidth(BoxContactsModel::ADDRESS_COL, 100);
	m_ui->recipTableView->setColumnWidth(BoxContactsModel::PDZ_COL, 50);

	m_ui->recipTableView->setColumnHidden(BoxContactsModel::CHECKBOX_COL, true);
	m_ui->recipTableView->setColumnHidden(BoxContactsModel::POST_CODE_COL, true);
	m_ui->recipTableView->setColumnHidden(BoxContactsModel::PAYMENTS_COL, true);
	m_ui->recipTableView->setColumnHidden(BoxContactsModel::PUBLIC_COL, true);
	m_ui->recipTableView->setColumnHidden(BoxContactsModel::PDZ_COL, true);
	m_ui->recipTableView->setColumnHidden(BoxContactsModel::PAYMENTS_COL, true);

	m_attachModel.setHeader();
	m_attachProgressProxyModel.setSourceModel(&m_attachModel);
	m_ui->attachTableView->setModel(&m_attachProgressProxyModel);
	m_ui->attachTableView->setItemDelegateForColumn(ATTACH_PROGRESS_COL, &m_attachProgressDelegate);

	m_ui->attachTableView->setColumnWidth(AttachmentTblModel::FNAME_COL, 150);
	m_ui->attachTableView->setColumnWidth(AttachmentTblModel::MIME_COL, 120);

	m_ui->attachTableView->setColumnHidden(AttachmentTblModel::ATTACHID_COL, true);
	m_ui->attachTableView->setColumnHidden(AttachmentTblModel::MSGID_COL, true);
	m_ui->attachTableView->setColumnHidden(AttachmentTblModel::BINARY_CONTENT_COL, true);

	/* Enable drag and drop on attachment table. */
	m_ui->attachTableView->setAcceptDrops(true);
	m_ui->attachTableView->setDragEnabled(true);
	m_ui->attachTableView->setDragDropOverwriteMode(false);
	m_ui->attachTableView->setDropIndicatorShown(true);
	m_ui->attachTableView->setDragDropMode(QAbstractItemView::DragDrop);
	m_ui->attachTableView->setDefaultDropAction(Qt::CopyAction);

	m_ui->immDownloadCheckBox->setCheckState(Qt::Unchecked);
	m_ui->immRecMgmtUploadCheckBox->setCheckState(Qt::Unchecked);
	m_ui->immRecMgmtUploadCheckBox->setVisible(GlobInstcs::recMgmtSetPtr->isValid());

	Q_ASSERT(m_acntId.isValid());

	for (const AcntIdDb &acntIdDb : m_acntIdDbList) {
		const AcntId acntId(acntIdDb);
		const QString accountName =
		    GlobInstcs::acntMapPtr->acntData(acntId).accountName() +
		    " (" + acntIdDb.username() + ")";
		m_ui->fromComboBox->addItem(accountName, QVariant::fromValue(acntId));
		if (m_acntId == acntId) {
			int i = m_ui->fromComboBox->count() - 1;
			Q_ASSERT(0 <= i);
			m_ui->fromComboBox->setCurrentIndex(i);
			setAccountInfo(i);
		}
	}

	connect(m_ui->fromComboBox, SIGNAL(currentIndexChanged(int)),
	    this, SLOT(setAccountInfo(int)));

	connect(&m_recipTableModel, SIGNAL(rowsInserted(QModelIndex, int, int)),
	    this, SLOT(checkInputFields()));
	connect(&m_recipTableModel, SIGNAL(rowsRemoved(QModelIndex, int, int)),
	    this, SLOT(checkInputFields()));
	connect(&m_recipTableModel, SIGNAL(dataChanged(QModelIndex, QModelIndex,
	    QVector<int>)), this, SLOT(showOptionalFormElements()));

	connect(m_ui->addRecipButton, SIGNAL(clicked()),
	    this, SLOT(addRecipientFromLocalContact()));
	connect(m_ui->removeRecipButton, SIGNAL(clicked()),
	    this, SLOT(deleteRecipientEntries()));
	connect(m_ui->findRecipButton, SIGNAL(clicked()),
	    this, SLOT(addRecipientFromISDSSearch()));
	connect(m_ui->enterBoxIdButton, SIGNAL(clicked()),
	    this, SLOT(addRecipientManually()));

	connect(m_ui->addAttachButton, SIGNAL(clicked()), this,
	    SLOT(addAttachmentFile()));
	connect(m_ui->removeAttachButton, SIGNAL(clicked()), this,
	    SLOT(deleteSelectedAttachmentFiles()));
	connect(m_ui->openAttachButton, SIGNAL(clicked()), this,
	    SLOT(openSelectedAttachment()));
	connect(m_ui->addTextButton, SIGNAL(clicked()), this,
	    SLOT(selectTabTextMessage()));

	connect(m_ui->recipTableView->selectionModel(),
	    SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this,
	    SLOT(recipientSelectionChanged(QItemSelection, QItemSelection)));

	connect(m_ui->attachTableView, SIGNAL(doubleClicked(QModelIndex)),
	    this, SLOT(openSelectedAttachment(QModelIndex)));

	connect(m_ui->subjectLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));

	connect(&m_attachModel, SIGNAL(rowsInserted(QModelIndex, int, int)),
	    this, SLOT(checkInputFields()));
	connect(&m_attachModel, SIGNAL(rowsRemoved(QModelIndex, int, int)),
	    this, SLOT(checkInputFields()));
	connect(&m_attachModel, SIGNAL(rowsRemoved(QModelIndex, int, int)),
	    this, SLOT(watchAppendedPdfModification()));
	connect(&m_attachModel,
	    SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector<int>)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->attachTableView->selectionModel(),
	    SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this,
	    SLOT(attachmentSelectionChanged(QItemSelection, QItemSelection)));

	/*
	 * Leave default edit triggers for m_ui->recipTableView but disable all
	 * for m_ui->attachTableView.
	 */
	m_ui->attachTableView->setEditTriggers(
	    QAbstractItemView::NoEditTriggers);

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

	m_ui->recipTableView->setItemDelegateForColumn(
	    BoxContactsModel::PAYMENT_COL,
	    new (::std::nothrow) ComboBoxItemDelegate(this));

	connect(m_ui->dmPublishOwnId, SIGNAL(stateChanged(int)),
	    this, SLOT(dmPublishOwnIdStateChanged(int)));
	m_ui->dmPublishOwnId->setChecked(true);

	connect(m_ui->immDownloadCheckBox, SIGNAL(stateChanged(int)),
	    this, SLOT(immDownloadStateChanged(int)));
	connect(m_ui->immRecMgmtUploadCheckBox, SIGNAL(stateChanged(int)),
	    this, SLOT(immRecMgmtUploadStateChanged(int)));

	connect(m_ui->sendButton, SIGNAL(clicked()), this, SLOT(sendMessage()));
	connect(m_ui->cancelButton, SIGNAL(clicked()), this, SLOT(close()));
	connect(GlobInstcs::msgProcEmitterPtr,
	    SIGNAL(sendMessageFinished(AcntId, QString, int, QString,
	        QString, QString, bool, bool, qint64, int, QString)), this,
	    SLOT(collectSendMessageStatus(AcntId, QString, int, QString,
	        QString, QString, bool, bool, qint64, int, 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. */

	m_keepAliveTimer.start(DLG_ISDS_KEEPALIVE_MS);

	connect(&m_keepAliveTimer, SIGNAL(timeout()), this, SLOT(pingIsdsServer()));

	{
		int item = 0;
		/* Always have plain text at disposal. */
		m_ui->textFormatComboBox->addItem(tr("Plain text"), Utility::PLAIN_TEXT);
		item = m_ui->textFormatComboBox->count() - 1;
		m_ui->textFormatComboBox->setItemData(item,
		    tr("Text will be written to the output as it is."),
		    Qt::ToolTipRole);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
		m_ui->textFormatComboBox->addItem(tr("Markdown"), Utility::MARKDOWN_TEXT);
		item = m_ui->textFormatComboBox->count() - 1;
		m_ui->textFormatComboBox->setItemData(item,
		    tr("You can use Markdown syntax to to specify how the resulting formatted text should look like."
		        "\n"
		        "Only a subset of the Markdown language is supported."
		        "\n"
		        "Use the preview functionality to check the content of the resulting document."),
		    Qt::ToolTipRole);
#else /* < Qt-5.14 */
#  warning "Compiling against version < Qt-5.14 which does not have Markdown support."
		/* Hide the combo box. */
		m_ui->textFormatLabel->setVisible(false);
		m_ui->textFormatComboBox->setVisible(false);
#endif /* >= Qt-5.14 */
	}

#if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
	m_ui->textMessageEdit->setPlaceholderText(tr("Enter a short text message."));
#endif /* >= Qt-5.3 */

	connect(m_ui->textMessageEdit, SIGNAL(textChanged()),
	    this, SLOT(textMessageChanged()));
	connect(m_ui->textMessageEdit, SIGNAL(textChanged()),
	    this, SLOT(watchAppendedPdfModification()));
	connect(m_ui->previewButton, SIGNAL(clicked()),
	    this, SLOT(preview()));
	connect(m_ui->viewPdfButton, SIGNAL(clicked()),
	    this, SLOT(viewPdf()));
	connect(m_ui->appendPdfButton, SIGNAL(clicked()),
	    this, SLOT(appendPdf()));
	textMessageChanged();

	calculateAndShowTotalAttachSize();

	if (Isds::str2DbType(m_dbType) > Isds::Type::BT_OVM_REQ) {
		m_ui->dmAllowSubstDelivery->setEnabled(false);
		m_ui->dmAllowSubstDelivery->hide();
	}

	if (ACT_REPLY == action) {
		fillContentAsReply(msgIds);
	} else {
		if (ACT_NEW_FROM_TMP == action) {
			fillContentFromTemplate(msgIds);
		} else if (ACT_FORWARD == action) {
			fillContentAsForward(msgIds);
		}
	}

	this->adjustSize();
}

#undef ATTACH_PROGRESS_COL
#undef RECIP_PROGRESS_COL

void DlgSendMessage::fillContentAsForward(const QList<MsgId> &msgIds)
{
	debugFuncCall();

	if (msgIds.size() == 0) {
		logWarningNL("%s",
		    "Expected at least one message to generate message from.");
		return;
	}

	/* Fill attachments with messages. */
	for (const MsgId &msgId : msgIds) {
		MessageDb *messageDb =
		    m_dbSet->accessMessageDb(msgId.deliveryTime(), false);
		if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
			Q_ASSERT(0);
			continue;
		}

		/* If only a single message if forwarded. */
		if (msgIds.size() == 1) {
			Isds::Envelope envData(
			    messageDb->getMessageEnvelope(msgId.dmId()));

			m_ui->subjectLine->setText("Fwd: " + envData.dmAnnotation());
		}

		QByteArray msgBinary(messageDb->getCompleteMessageRaw(msgId.dmId()));
		if (msgBinary.isEmpty()) {
			continue;
		}

		m_attachModel.appendBinaryAttachment(msgBinary,
		    QString("%1_%2.zfo")
		        .arg(Exports::dmTypePrefix(messageDb, msgId.dmId()))
		        .arg(msgId.dmId()));
	}
}

void DlgSendMessage::fillContentAsReply(const QList<MsgId> &msgIds)
{
	debugFuncCall();

	if (msgIds.size() != 1) {
		logWarningNL("%s",
		    "Expected single message to generate the reply from.");
		return;
	}
	const MsgId &msgId(msgIds.first());

	m_ui->fromComboBox->setEnabled(false);

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

	const Isds::Envelope envData =
	    messageDb->getMessageEnvelope(msgId.dmId());
	m_dmType = Isds::char2DmType(envData.dmType());
	m_dmSenderRefNumber = envData.dmRecipientRefNumber();

	m_ui->subjectLine->setText("Re: " + envData.dmAnnotation());

	if (!envData.dmSenderRefNumber().isEmpty()) {
		m_ui->dmRecipientRefNumber->setText(envData.dmSenderRefNumber());
	}
	if (!envData.dmSenderIdent().isEmpty()) {
		m_ui->dmRecipientIdent->setText(envData.dmSenderIdent());
	}
	if (!envData.dmRecipientRefNumber().isEmpty()) {
		m_ui->dmSenderRefNumber->setText(envData.dmRecipientRefNumber());
	}
	if (!envData.dmRecipientIdent().isEmpty()) {
		m_ui->dmSenderIdent->setText(envData.dmRecipientIdent());
	}

	bool canUseInitReply = false;
	if (m_dmType == Isds::Type::MT_I || m_dmType == Isds::Type::MT_A) {
		m_ui->addRecipButton->setEnabled(false);
		m_ui->removeRecipButton->setEnabled(false);
		m_ui->findRecipButton->setEnabled(false);
		m_ui->enterBoxIdButton->setEnabled(false);
		canUseInitReply = true;
	}

	addRecipientBox(envData.dbIDSender(), canUseInitReply);
}

void DlgSendMessage::fillContentFromTemplate(const QList<MsgId> &msgIds)
{
	debugFuncCall();

	if (msgIds.size() != 1) {
		logWarningNL("%s",
		    "Expected single message to generate the message from.");
		return;
	}
	const MsgId &msgId(msgIds.first());

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

	const Isds::Envelope envData =
	    messageDb->getMessageEnvelope(msgId.dmId());
	m_dmType = Isds::char2DmType(envData.dmType());
	m_dmSenderRefNumber = envData.dmRecipientRefNumber();

	m_ui->subjectLine->setText(envData.dmAnnotation());

	/* Fill in optional fields.  */
	if (!envData.dmSenderRefNumber().isEmpty()) {
		m_ui->dmSenderRefNumber->setText(envData.dmSenderRefNumber());
	}
	if (!envData.dmSenderIdent().isEmpty()) {
		m_ui->dmSenderIdent->setText(envData.dmSenderIdent());
	}
	if (!envData.dmRecipientRefNumber().isEmpty()) {
		m_ui->dmRecipientRefNumber->setText(envData.dmRecipientRefNumber());
	}
	if (!envData.dmRecipientIdent().isEmpty()) {
		m_ui->dmRecipientIdent->setText(envData.dmRecipientIdent());
	}
	if (!envData.dmToHands().isEmpty()) {
		m_ui->dmToHands->setText(envData.dmToHands());
	}
	/* set check boxes */
	m_ui->dmPersonalDelivery->setChecked(envData.dmPersonalDelivery());
	m_ui->dmAllowSubstDelivery->setChecked(envData.dmAllowSubstDelivery());
	/* fill optional LegalTitle - Law, year, ... */
	if (!envData.dmLegalTitleLawStr().isEmpty()) {
		m_ui->dmLegalTitleLaw->setText(envData.dmLegalTitleLawStr());
	}
	if (!envData.dmLegalTitleYearStr().isEmpty()) {
		m_ui->dmLegalTitleYear->setText(envData.dmLegalTitleYearStr());
	}
	if (!envData.dmLegalTitleSect().isEmpty()) {
		m_ui->dmLegalTitleSect->setText(envData.dmLegalTitleSect());
	}
	if (!envData.dmLegalTitlePar().isEmpty()) {
		m_ui->dmLegalTitlePar->setText(envData.dmLegalTitlePar());
	}
	if (!envData.dmLegalTitlePoint().isEmpty()) {
		m_ui->dmLegalTitlePoint->setText(envData.dmLegalTitlePoint());
	}

	/* message is received -> recipient == sender */
	if (m_boxId != envData.dbIDRecipient()) {
		addRecipientBox(envData.dbIDRecipient(), false);
	}

	/* fill attachments from template message */
	QList<Isds::Document> msgFileList =
	    messageDb->getMessageAttachments(msgId.dmId());

	for (const Isds::Document &file : msgFileList) {
		m_attachModel.appendBinaryAttachment(file.binaryContent(),
		    file.fileDescr());
	}
}

void DlgSendMessage::fillContentCompose(const QString &composeSerialised)
{
	debugFuncCall();

	const CLI::CmdCompose composeCmd(
	    CLI::CmdCompose::deserialise(composeSerialised));
	if (composeCmd.isNull()) {
		logErrorNL("%s", "Could not deserialise compose data.");
		return;
	}

	if (!composeCmd.dbIDRecipient().isEmpty()) {
		addRecipientBoxes(composeCmd.dbIDRecipient());
	}
	if (!composeCmd.dmAnnotation().isEmpty()) {
		m_ui->subjectLine->setText(composeCmd.dmAnnotation());
	}
	if (!composeCmd.dmToHands().isEmpty()) {
		m_ui->dmToHands->setText(composeCmd.dmToHands());
	}
	if (!composeCmd.dmRecipientRefNumber().isEmpty()) {
		m_ui->dmRecipientRefNumber->setText(composeCmd.dmRecipientRefNumber());
	}
	if (!composeCmd.dmSenderRefNumber().isEmpty()) {
		m_ui->dmSenderRefNumber->setText(composeCmd.dmSenderRefNumber());
	}
	if (!composeCmd.dmRecipientIdent().isEmpty()) {
		m_ui->dmRecipientIdent->setText(composeCmd.dmRecipientIdent());
	}
	if (!composeCmd.dmSenderIdent().isEmpty()) {
		m_ui->dmSenderIdent->setText(composeCmd.dmSenderIdent());
	}
	if (!composeCmd.dmLegalTitleLawStr().isEmpty()) {
		m_ui->dmLegalTitleLaw->setText(composeCmd.dmLegalTitleLawStr());
	}
	if (!composeCmd.dmLegalTitleYearStr().isEmpty()) {
		m_ui->dmLegalTitleYear->setText(composeCmd.dmLegalTitleYearStr());
	}
	if (!composeCmd.dmLegalTitleSect().isEmpty()) {
		m_ui->dmLegalTitleSect->setText(composeCmd.dmLegalTitleSect());
	}
	if (!composeCmd.dmLegalTitlePar().isEmpty()) {
		m_ui->dmLegalTitlePar->setText(composeCmd.dmLegalTitlePar());
	}
	if (!composeCmd.dmLegalTitlePoint().isEmpty()) {
		m_ui->dmLegalTitlePoint->setText(composeCmd.dmLegalTitlePoint());
	}
	if (composeCmd.dmPersonalDelivery() != Isds::Type::BOOL_NULL) {
		m_ui->dmPersonalDelivery->setCheckState(
		    (composeCmd.dmPersonalDelivery() == Isds::Type::BOOL_TRUE) ?
		        Qt::Checked : Qt::Unchecked);
	}
	if (composeCmd.dmAllowSubstDelivery() != Isds::Type::BOOL_NULL) {
		m_ui->dmAllowSubstDelivery->setCheckState(
		    (composeCmd.dmAllowSubstDelivery() == Isds::Type::BOOL_TRUE) ?
		        Qt::Checked : Qt::Unchecked);
	}
	if (composeCmd.dmPublishOwnID() != Isds::Type::BOOL_NULL) {
		m_ui->dmPublishOwnId->setCheckState(
		    (composeCmd.dmPublishOwnID() == Isds::Type::BOOL_TRUE) ?
		        Qt::Checked : Qt::Unchecked);
	}
	if (!composeCmd.dmAttachment().isEmpty()) {
		insertAttachmentFiles(composeCmd.dmAttachment());
	}
	if (composeCmd.recMgmtUpload() == Isds::Type::BOOL_TRUE) {
		if (GlobInstcs::recMgmtSetPtr->isValid()) {
			m_ui->immRecMgmtUploadCheckBox->setCheckState(Qt::Checked);
		}
	}
	if (!composeCmd.recMgmtHierarchyId().isEmpty()) {
		if (GlobInstcs::recMgmtSetPtr->isValid()) {
			m_recMgmtHierarchyId = composeCmd.recMgmtHierarchyId();
		}
	}
}

void DlgSendMessage::insertAttachmentFiles(const QStringList &filePaths)
{
	for (const QString &filePath : filePaths) {
		int fileSize = m_attachModel.insertAttachmentFile(filePath,
		    m_attachModel.rowCount());
		switch (fileSize) {
		case AttachmentTblModel::FILE_NOT_EXISTENT:
			QMessageBox::warning(this, tr("Non-existent file"),
			    tr("Cannot add non-existent file '%1' to attachments.").arg(filePath),
			    QMessageBox::Ok, QMessageBox::Ok);
			break;
		case AttachmentTblModel::FILE_NOT_READABLE:
			QMessageBox::warning(this, tr("File not readable"),
			    tr("Cannot add file '%1' without readable permissions to attachments.").arg(filePath),
			    QMessageBox::Ok, QMessageBox::Ok);
			break;
		case AttachmentTblModel::FILE_ALREADY_PRESENT:
			QMessageBox::warning(this, tr("File already present"),
			    tr("Cannot add file '%1' because the file is already present in the attachments.").arg(filePath),
			    QMessageBox::Ok, QMessageBox::Ok);
			break;
		case AttachmentTblModel::FILE_ZERO_SIZE:
			QMessageBox::warning(this, tr("Empty file"),
			    tr("Cannot add empty file '%1' to attachments.").arg(filePath),
			    QMessageBox::Ok, QMessageBox::Ok);
			break;
		default:
			break;
		}
	}
}

bool DlgSendMessage::calculateAndShowTotalAttachSize(void)
{
	const qint64 MByteBytes =
	    PrefsSpecific::bytesInMByte(*GlobInstcs::prefsPtr);
	const int basicMaxAttachmmentMBytes =
	    PrefsSpecific::maxAttachmentMBytes(*GlobInstcs::prefsPtr);
	const int ovmMaxAttachmentMBytes =
	    PrefsSpecific::maxOVMAttachmentMBytes(*GlobInstcs::prefsPtr);
	const int vodzAttachmentMbytes =
	    PrefsSpecific::maxVoDZAttachmentMBytes(*GlobInstcs::prefsPtr);
	const int attachmentLimit =
	    PrefsSpecific::maxAttachmentNum(*GlobInstcs::prefsPtr);
	const bool enableVodz =
	    PrefsSpecific::enableVodzSending(*GlobInstcs::prefsPtr);

	m_isVodz = false;
	const qint64 aSize = m_attachModel.totalAttachmentSize();
	const int aNum = m_attachModel.rowCount();

	if (aNum == 0) {
		Q_ASSERT(aSize == 0);
		m_ui->attachmentSizeInfo->setEnabled(false);
		m_ui->attachmentSizeInfo->setStyleSheet(QString());
		m_ui->attachmentSizeInfo->setText(
		    tr("Total size of attachments is %1 B.").arg(0));
		return false;
	} else {
		m_ui->attachmentSizeInfo->setEnabled(true);
	}

	m_ui->attachmentSizeInfo->setStyleSheet("QLabel { color: red }");

	QString text = tr("%n file(s) is/are being attached.", "", aNum);

	if (aNum > attachmentLimit) {
		text += QStringLiteral(" ");
		text += tr("The permitted number of %n attachment(s) has been exceeded.",
		    "", attachmentLimit);
		m_ui->attachmentSizeInfo->setText(text);
		return false;
	}

	{
		const int maxSize = enableVodz ? (vodzAttachmentMbytes * MByteBytes) : (ovmMaxAttachmentMBytes * MByteBytes);
		const int maxMB = enableVodz ? vodzAttachmentMbytes : ovmMaxAttachmentMBytes;
		if (aSize >= maxSize) {
			text += QStringLiteral(" ");
			text += tr("Total size of attachments exceeds %1 MB.")
			    .arg(maxMB);
			m_ui->attachmentSizeInfo->setText(text);
			return false;
		}
	}

	if (aSize >= (basicMaxAttachmmentMBytes * MByteBytes)) {
		if (enableVodz) {
			text += QStringLiteral(" ");
			text += tr("Total size of attachments exceeds %1 MB. Message will be sent as a high-volume message (VoDZ).")
			    .arg(basicMaxAttachmmentMBytes);
			m_ui->attachmentSizeInfo->setStyleSheet(QString());
			m_ui->attachmentSizeInfo->setText(text);
			m_isVodz = true;
		} else {
			text += QStringLiteral(" ");
			text += tr("Total size of attachments exceeds %1 MB. Most of the data boxes cannot receive messages larger than %1 MB. However, some OVM data boxes can receive message up to %2 MB.")
			    .arg(basicMaxAttachmmentMBytes).arg(ovmMaxAttachmentMBytes);
			m_ui->attachmentSizeInfo->setText(text);
		}
		return true;
	}

	if (aSize >= 1024) {
		text += QStringLiteral(" ");
		text += tr("Total size of attachments is ~%1 KB.").arg(aSize / 1024);
	} else {
		text += QStringLiteral(" ");
		text += tr("Total size of attachments is ~%1 B.").arg(aSize);
	}
	m_ui->attachmentSizeInfo->setStyleSheet(QString());
	m_ui->attachmentSizeInfo->setText(text);
	return true;
}

/*!
 * @brief Set possible PDZ payment methods based on PDZinfos of data box.
 *
 * @param[in] pdzInfos List of available PDZ payment methods.
 * @param[in] canSendPdz True if can receive a commercial messages.
 * @param[in] canSendInitiatory True if can receive initiatory commercial messages.
 * @param[in] canUseInitReply True if can use prepaid reply commercial messages.
 * @return List of possible PDZ payment methods.
 */
static
QList<Isds::Type::DmType> setPossiblePdzPaymentMethods(
    const QList<Isds::PDZInfoRec> &pdzInfos, enum Isds::Type::NilBool canSendPdz,
    bool canSendInitiatory, bool canUseInitReply)
{
	QList<Isds::Type::DmType> dmPaymentTypes;

	/* Warning: Do not change order of append and prepend of items. */
	/* Priority: O->G->K->E */
	for (const Isds::PDZInfoRec &info : pdzInfos) {
		if (info.pdzType() == Isds::Type::PPT_G) {
			/*
			 * The payment type G may be present only once.
			 * If it is active, it will always be used.
			 * Cannot be changed to another payment type
			 * but can be used type I.
			 */
			dmPaymentTypes.clear();
			dmPaymentTypes.append(Isds::Type::MT_G);
			if (canSendInitiatory) {
				dmPaymentTypes.append(Isds::Type::MT_I);
			}
			break;
		} else if (info.pdzType() == Isds::Type::PPT_K) {
			/* Can be selected also I when K is available. */
			if (canSendInitiatory) {
				dmPaymentTypes.append(Isds::Type::MT_I);
			}
			dmPaymentTypes.prepend(Isds::Type::MT_K);
		} else if (info.pdzType() == Isds::Type::PPT_E) {
			dmPaymentTypes.append(Isds::Type::MT_E);
		} else if (info.pdzType() == Isds::Type::PPT_O) {
			/* Not used now. */
		}
	}

	/* Use PDZSendInfo results if PDZInfo is empty or unknown. */
	if (pdzInfos.isEmpty()) {
		if (canSendPdz == Isds::Type::BOOL_TRUE) {
			dmPaymentTypes.append(Isds::Type::MT_K);
			dmPaymentTypes.append(Isds::Type::MT_E);
		}
		if (canSendInitiatory) {
			dmPaymentTypes.append(Isds::Type::MT_I);
		}
	}

	/* Add prepaid reply payment method if it is available. */
	if (canUseInitReply) {
		dmPaymentTypes.prepend(Isds::Type::MT_O);
	}

	return dmPaymentTypes;
}

void DlgSendMessage::addRecipientBox(const QString &boxId, bool canUseInitReply)
{
	if (boxId.isEmpty() || boxId.length() != 7) {
		QMessageBox::critical(this, tr("Wrong data box ID"),
		    tr("Wrong data box ID '%1'!").arg(boxId),
		    QMessageBox::Ok, QMessageBox::Ok);
		return;
	}

	/* Ignore existent entry. */
	if (m_recipTableModel.containsBoxId(boxId)) {
		return;
	}

	/*
	 * If we are manually adding the recipient then we may not be able to
	 * download information about the data box.
	 *
	 * TODO -- If we have been searching for the data box then we are
	 * downloading the information about the data box for the second time.
	 * This is not optimal.
	 */

	/* Search for box information according to supplied identifier. */
	enum TaskSearchOwnerFulltext::Result result =
	    TaskSearchOwnerFulltext::SOF_ERROR;
	QString errMsg, longErrMsg;
	QList<Isds::FulltextResult> foundBoxes;

	TaskSearchOwnerFulltext *task =
	    new (::std::nothrow) TaskSearchOwnerFulltext(m_acntId, boxId,
	        Isds::Type::FST_BOX_ID, TaskSearchOwnerFulltext::BT_ALL);
	if (task != Q_NULLPTR) {
		task->setAutoDelete(false);
		if (GlobInstcs::workPoolPtr->runSingle(task)) {
			result = task->m_result;
			errMsg = task->m_isdsError;
			longErrMsg = task->m_isdsLongError;
			foundBoxes = task->m_foundBoxes;
		} else {
			errMsg = tr("Cannot execute task.");
		}
		delete task; task = Q_NULLPTR;
	}

	enum Isds::Type::NilBool recipDbEffectiveOVM = Isds::Type::BOOL_NULL;
	QString recipName = tr("Unknown");
	QString recipAddress = tr("Unknown");
	enum Isds::Type::DbType recipBoxType = Isds::Type::BT_NULL; /* Unknown type. */

	if (foundBoxes.size() == 1) {

		const Isds::FulltextResult &entry(foundBoxes.first());

		/* Recipient data box is not active. */
		if (!entry.active()) {
			DlgMsgBoxDetail::message(this, QMessageBox::Warning,
			    tr("Data box is not active"),
			    tr("Recipient with data box ID '%1' does not have active data box.")
			        .arg(boxId),
			    tr("The message cannot be delivered."), QString(),
			    QMessageBox::Ok, QMessageBox::Ok);
			return;
		}

		/* Set current recipient name and address, data box type. */
		recipName = entry.dbName();
		recipAddress = entry.dbAddress();
		recipBoxType = entry.dbType();
		recipDbEffectiveOVM = entry.dbEffectiveOVM() ? Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE;

	} else if (foundBoxes.isEmpty()) {

		if (result == TaskSearchOwnerFulltext::SOF_SUCCESS) {
			/* No data box found. */
			DlgMsgBoxDetail::message(this, QMessageBox::Critical,
			    tr("Wrong Recipient"),
			    tr("Recipient with data box ID '%1' does not exist.").arg(boxId),
			    QString(), QString(), QMessageBox::Ok, QMessageBox::Ok);
			return;
		} else {
			/* Search error. */
			int reply = DlgMsgBoxDetail::message(this,
			    QMessageBox::Question, tr("Recipient Search Failed"),
			    tr("Information about recipient data box '%1' could not be obtained.").arg(boxId),
			    tr("Do you still want to add the box '%1' into the recipient list?").arg(boxId),
			    !longErrMsg.isEmpty() ? tr("Obtained ISDS error") + QStringLiteral(": ") + longErrMsg : QString(),
			     QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
			if (reply == QMessageBox::No) {
				return;
			}
		}

	} else {
		Q_ASSERT(0);
		return;
	}

	const bool sendPublic = m_dbEffectiveOVM || (recipDbEffectiveOVM == Isds::Type::BOOL_TRUE);
	enum Isds::Type::NilBool canSendNormalPDZ = sendPublic ? Isds::Type::BOOL_FALSE : Isds::Type::BOOL_NULL;
	bool canSendInitiatoryPDZ = false;
	QList<Isds::Type::DmType> dmTypes;

	/* Sender is non-OVM, recipient is non-OVM so get PDZ send info. */
	if (!sendPublic) {

		TaskPDZSendInfo *task2 =
		    new (::std::nothrow) TaskPDZSendInfo(m_acntId, boxId);
		if (task2 != Q_NULLPTR) {
			task2->setAutoDelete(false);
			if (GlobInstcs::workPoolPtr->runSingle(task2)) {
				if (task2->m_success) {
					canSendNormalPDZ = task2->m_canSendNormal ?
					    Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE;
					canSendInitiatoryPDZ = task2->m_canSendInitiatory;
				}
			}
			delete task2; task2 = Q_NULLPTR;
		}

		dmTypes = setPossiblePdzPaymentMethods(m_pdzInfos,
		    canSendNormalPDZ, canSendInitiatoryPDZ, canUseInitReply);
	}

	if (m_dbEffectiveOVM) {
		/* I'm always sending public messages because I'm OVM. */
	} else if ((recipDbEffectiveOVM == Isds::Type::BOOL_NULL) &&
	    (canSendNormalPDZ == Isds::Type::BOOL_NULL)) {
		/*
		 * I'm not OVM and I could not download any information about
		 * the recipient.
		 */
		DlgMsgBoxDetail::message(this, QMessageBox::Warning,
		    tr("Unknown message type"),
		    tr("No information about the recipient data box '%1' could be obtained.").arg(boxId),
		    tr("It's unknown whether public or commercial messages can be sent to this recipient."),
		    QString(), QMessageBox::Ok, QMessageBox::Ok);
	} else if ((recipDbEffectiveOVM == Isds::Type::BOOL_NULL) &&
	    (canSendNormalPDZ == Isds::Type::BOOL_FALSE)) {
		/*
		 * Don't know whether I can send public and I cannot send PDZs.
		 */
		DlgMsgBoxDetail::message(this, QMessageBox::Warning,
		    tr("Unknown message type"),
		    tr("No commercial message to the recipient data box '%1' can be sent.").arg(boxId),
		    tr("It's unknown whether a public messages can be sent to this recipient."),
		    QString(), QMessageBox::Ok, QMessageBox::Ok);
	} else if ((recipDbEffectiveOVM == Isds::Type::BOOL_FALSE) &&
	    (canSendNormalPDZ == Isds::Type::BOOL_NULL)) {
		/*
		 * Cannot send public message and don't know whether I can send
		 * PDZs.
		 */
		DlgMsgBoxDetail::message(this, QMessageBox::Warning,
		    tr("Unknown message type"),
		    tr("No public message to the recipient data box '%1' can be sent.").arg(boxId),
		    tr("It's unknown whether a commercial messages can be sent to this recipient."),
		    QString(), QMessageBox::Ok, QMessageBox::Ok);
	} else if ((recipDbEffectiveOVM == Isds::Type::BOOL_FALSE) &&
	    (canSendNormalPDZ == Isds::Type::BOOL_FALSE)) {
		/* I cannot send a public nor a commercial message. */
		DlgMsgBoxDetail::message(this, QMessageBox::Critical,
		    tr("Cannot send message to data box"),
		    tr("No public data message nor a commercial data message (PDZ) can be sent to the recipient data box '%1'.").arg(boxId),
		    tr("Receiving of PDZs has been disabled in the recipient data box or there are no available payment methods for PDZs."),
		    QString(), QMessageBox::Ok, QMessageBox::Ok);
	}

	/* Add recipient information into recipient model.*/
	int addedRow = m_recipTableModel.rowCount();
	m_recipTableModel.appendData(boxId, recipBoxType, recipName,
	    recipAddress, QString(),
	    m_dbEffectiveOVM ? Isds::Type::BOOL_TRUE : recipDbEffectiveOVM,
	    canSendNormalPDZ, dmTypes);
	if ((addedRow + 1) == m_recipTableModel.rowCount()) {
		/*
		 * If a row was added and if there are several choices how
		 * to pay for a PDZ the open a persistent editor.
		 */
		if (dmTypes.size() > 1) {
			m_ui->recipTableView->openPersistentEditor(
			    m_ui->recipTableView->model()->index(addedRow, BoxContactsModel::PAYMENT_COL));
		} else {
			m_ui->recipTableView->closePersistentEditor(
			    m_ui->recipTableView->model()->index(addedRow, BoxContactsModel::PAYMENT_COL));
		}
	}
}

void DlgSendMessage::addRecipientBoxes(const QStringList &boxIds)
{
	for (const QString &boxId : boxIds) {
		addRecipientBox(boxId, false);
	}
}

QString DlgSendMessage::getPDZCreditFromISDS(const AcntId &acntId,
    const QString &dbId)
{
	debugFuncCall();

	TaskDownloadCreditInfo *task =
	    new (::std::nothrow) TaskDownloadCreditInfo(acntId, dbId);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		return QString();
	}
	task->setAutoDelete(false);
	qint64 credit = 0;
	if (GlobInstcs::workPoolPtr->runSingle(task)) {
		credit = task->m_heller;
	}
	delete task; task = Q_NULLPTR;

	if (credit <= 0) {
		return QString();
	}

	return Localisation::programLocale.toString((float)credit / 100, 'f', 2);
}

bool DlgSendMessage::buildEnvelope(Isds::Envelope &envelope) const
{
	/* Set mandatory fields of envelope. */
	envelope.setDmAnnotation(m_ui->subjectLine->text());

	/* Set optional fields. */
	envelope.setDmSenderIdent(m_ui->dmSenderIdent->text());
	envelope.setDmRecipientIdent(m_ui->dmRecipientIdent->text());
	envelope.setDmSenderRefNumber(m_ui->dmSenderRefNumber->text());
	envelope.setDmRecipientRefNumber(m_ui->dmRecipientRefNumber->text());
	if (!m_ui->dmLegalTitleLaw->text().isEmpty()) {
		envelope.setDmLegalTitleLaw(m_ui->dmLegalTitleLaw->text().toLongLong());
	}
	if (!m_ui->dmLegalTitleYear->text().isEmpty()) {
		envelope.setDmLegalTitleYear(m_ui->dmLegalTitleYear->text().toLongLong());
	}
	envelope.setDmLegalTitleSect(m_ui->dmLegalTitleSect->text());
	envelope.setDmLegalTitlePar(m_ui->dmLegalTitlePar->text());
	envelope.setDmLegalTitlePoint(m_ui->dmLegalTitlePoint->text());
	envelope.setDmPersonalDelivery(m_ui->dmPersonalDelivery->isChecked() ?
	    Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE);

	envelope.setDmToHands(m_ui->dmToHands->text());

	/* Only OVM can change. */
	if (Isds::str2DbType(m_dbType) > Isds::Type::BT_OVM_REQ) {
		envelope.setDmAllowSubstDelivery(Isds::Type::BOOL_TRUE);
	} else {
		envelope.setDmAllowSubstDelivery(
		   m_ui->dmAllowSubstDelivery->isChecked() ?
		   Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE);
	}

	envelope.setDmOVM(m_dbEffectiveOVM ?
	    Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE);
	envelope.setDmPublishOwnID((m_ui->dmPublishOwnId->isChecked()) ?
	    Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE);
	if (m_ui->dmPublishOwnId->isChecked()) {
		int idLevel = Isds::Type::IDLEV_PUBLISH_USERTYPE;
		if (m_ui->aiFullNameCheckBox->isChecked()) {
			idLevel |= Isds::Type::IDLEV_PUBLISH_PERSONNAME;
		}
		if (m_ui->aiBirthDateCheckBox->isChecked()) {
			idLevel |= Isds::Type::IDLEV_PUBLISH_BIDATE;
		}
		if (m_ui->aiBirthCityCheckBox->isChecked()) {
			idLevel |= Isds::Type::IDLEV_PUBLISH_BICITY;
		}
		if (m_ui->aiBirthCountyCheckBox->isChecked()) {
			idLevel |= Isds::Type::IDLEV_PUBLISH_BICOUNTY;
		}
		if (m_ui->aiRuianCodeCheckBox->isChecked()) {
			idLevel |= Isds::Type::IDLEV_PUBLISH_ADCODE;
		}
		if (m_ui->aiFullAddressCheckBox->isChecked()) {
			idLevel |= Isds::Type::IDLEV_PUBLISH_FULLADDRESS;
		}
		if (m_ui->aiRobIdentCheckBox->isChecked()) {
			idLevel |= Isds::Type::IDLEV_PUBLISH_ROBIDENT;
		}
		envelope.setIdLevel(idLevel);
	}

	return true;
}

bool DlgSendMessage::buildDocuments(QList<Isds::Document> &documents) const
{
	/* Load attachments. */
	for (int row = 0; row < m_attachModel.rowCount(); ++row) {
		Isds::Document document;
		QModelIndex index;

		index = m_attachModel.index(row, AttachmentTblModel::FNAME_COL);
		if (Q_UNLIKELY(!index.isValid())) {
			Q_ASSERT(0);
			continue;
		}
		document.setFileDescr(index.data().toString());

		/*
		 * First document must have dmFileMetaType set to
		 * FILEMETATYPE_MAIN. Remaining documents have
		 * FILEMETATYPE_ENCLOSURE.
		 */
		document.setFileMetaType((row == 0) ?
		    Isds::Type::FMT_MAIN : Isds::Type::FMT_ENCLOSURE);

		/*
		 * Since 2011 Mime Type can be empty and MIME type will
		 * be filled up on the ISDS server. It allows sending files
		 * with special mime types without recognition by application.
		 */
		index = m_attachModel.index(row, AttachmentTblModel::MIME_COL);
		{
			QString mimeName(index.data().toString());
			if ((mimeName == QStringLiteral("application/xml")) ||
			    (mimeName == QStringLiteral("text/xml")) ||
			    (mimeName == QStringLiteral("application/zip"))) {
				/*
				 * When sending XML requests to the eGov portals
				 * then the robots handling the requests are
				 * likely to discard them if the MIME type for
				 * the XML documents or ZIP archives isn't set.
				 */
				logDebugLv1NL("Setting '%s' mime type for document '%s'.",
				    mimeName.toUtf8().constData(),
				    document.fileDescr().toUtf8().constData());
				document.setMimeType(macroStdMove(mimeName));
			} else {
				/* Must be empty non-null string. */
				document.setMimeType(QStringLiteral(""));
			}
		}

		index = m_attachModel.index(row,
		    AttachmentTblModel::BINARY_CONTENT_COL);
		if (Q_UNLIKELY(!index.isValid())) {
			Q_ASSERT(0);
			continue;
		}
		document.setBinaryContent(
		    index.data(Qt::DisplayRole).toByteArray());

		documents.append(document);
	}

	return true;
}

bool DlgSendMessage::startUploadAttachments(void)
{
	const QString timeStr = QDateTime::currentDateTimeUtc().toString();

	m_attachsToBeUploaded.clear();
	m_attachsUploaded.clear();
	m_attachsFailed.clear();
	m_attachsSucceeded.clear();

	m_msgsToBeSent.clear();
	m_msgsFailed.clear();
	m_msgsSucceeded.clear();

	if (Q_UNLIKELY(m_attachModel.rowCount() == 0)) {
		return false;
	}

	bool allFailed = true;

	/* Load and upload attachments to the ISDS server. */
	for (int row = 0; row < m_attachModel.rowCount(); ++row) {

		Isds::DmExtFile dmExtFile;
		const QString transactionId = QString("%1_%2_%3_%4")
		    .arg(m_acntId.username()).arg(timeStr).arg(row)
		    .arg(Utility::generateRandomString(6));

		/* Remember order and parts of data for envelope. */
		m_attachsToBeUploaded.append(transactionId);

		/*
		 * First document must have dmFileMetaType set to
		 * FILEMETATYPE_MAIN. Remaining documents have
		 * FILEMETATYPE_ENCLOSURE.
		 */
		dmExtFile.setFileMetaType((row == 0) ?
		    Isds::Type::FMT_MAIN : Isds::Type::FMT_ENCLOSURE);

		{
			Isds::DmFile dmFile;
			TaskUploadAttachment *task = Q_NULLPTR;

			/* Prepare dmFile content. */
			{
				QModelIndex index;

				index = m_attachModel.index(row, AttachmentTblModel::FNAME_COL);
				if (Q_UNLIKELY(!index.isValid())) {
					Q_ASSERT(0);
					goto fail_loop;
				}
				dmFile.setFileDescr(index.data().toString());

				/*
				 * The FileMetaType is ignored when uploading separate
				 * attachments. But a value must be provided to pass it
				 * successfully to the underlying library.
				 */
				dmFile.setFileMetaType((row == 0) ?
				    Isds::Type::FMT_MAIN : Isds::Type::FMT_ENCLOSURE);

				index = m_attachModel.index(row, AttachmentTblModel::MIME_COL);
				if (Q_UNLIKELY(!index.isValid())) {
					Q_ASSERT(0);
					goto fail_loop;
				}
				dmFile.setMimeType(index.data().toString());

				index = m_attachModel.index(row,
				    AttachmentTblModel::BINARY_CONTENT_COL);
				if (Q_UNLIKELY(!index.isValid())) {
					Q_ASSERT(0);
					goto fail_loop;
				}
				dmFile.setBinaryContent(index.data(Qt::DisplayRole).toByteArray());
			}

			/* Create task, pass input data and execute. */
			task = new (::std::nothrow) TaskUploadAttachment(
			    m_acntId, macroStdMove(dmFile), transactionId);
			if (task != Q_NULLPTR) {
				task->setAutoDelete(true);
				GlobInstcs::workPoolPtr->assignHi(task);
			} else {
				goto fail_loop;
			}

			m_attachsUploaded[transactionId] = macroStdMove(dmExtFile);
			allFailed = false;

			continue;

fail_loop:
			m_attachsFailed[transactionId] = tr("Couldn't create a task to upload the attachment.");
		}

	}

	return !allFailed;
}

/*!
 * @brief Creates a notification informing the user about
 *     the number of created commercial messages.
 *
 * @param[in] pdzRecipients List of PDZ recipients ant its payment method.
 * @param[in] creditStr String containing the credit value.
 * @param[in] parent Window parent.
 * @return True if the commercial messages should be sent.
 */
static
bool sendPdzNotification(const QStringList &pdzRecipients,
    const QString &creditStr, QWidget *parent)
{
	/* The send PDZ notification is disabled in the specific preferences. */
	if (!PrefsSpecific::showPdzNotification(*GlobInstcs::prefsPtr)) {
		/* Send message without this notification. */
		return true;
	}

	QString info = DlgSendMessage::tr(
	    "Your message contains one or more non-OVM recipients. "
	    "To these recipients the message will be sent as a commercial data message (PDZ). "
	    "Number of PDZ recipients is: %1").arg(pdzRecipients.count());
	info += QStringLiteral("\n");

	for (const QString &pr : pdzRecipients) {
		info += QStringLiteral("\n") + pr;
	}

	if (!creditStr.isNull()) {
		info += QStringLiteral("\n\n") +
		    DlgSendMessage::tr("Your remaining credit is %1 Kč.").arg(creditStr);
	}

	DlgYesNoCheckbox questionDlg(
	    DlgSendMessage::tr("Message contains non-OVM recipients."), info,
	    DlgSendMessage::tr("Don't show this notification again."),
	    DlgSendMessage::tr("Do you want to send all messages?"), parent);

	const int retval = questionDlg.exec();

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

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

bool DlgSendMessage::collectUploadAttachments(QList<Isds::DmExtFile> &dmExtFiles) const
{
	dmExtFiles.clear();

	/* Here m_attachsUploaded.size() == m_attachsToBeUploaded.size() is true. */
	if (Q_UNLIKELY((m_attachsSucceeded.size() == 0) ||
	        (m_attachsSucceeded.size() != m_attachsToBeUploaded.size()) ||
	        (m_attachsUploaded.size() != m_attachsToBeUploaded.size()))) {
		Q_ASSERT(0);
		return false;
	}
	for (const QString &transactId : m_attachsToBeUploaded) {
		dmExtFiles.append(m_attachsUploaded[transactId]);
	}
	return true;
}

bool DlgSendMessage::askPermissionToSendPDZ(
    const QList<BoxContactsModel::PartialEntry> &recipEntries)
{
	QStringList pdzRecipients;
	int pdzCnt = 0;

	/* Compute number of messages which the sender has to pay for. */
	for (const BoxContactsModel::PartialEntry &e : recipEntries) {
		if (e.pdz) {
			QString text = tr("Recipient ID: %1, Payment method: %2").arg(e.id, BoxContactsModel::descrDmType(e.dmType));
			pdzRecipients.append(text);
			++pdzCnt;
		}
	}

	if (pdzCnt > 0) {
		if (!sendPdzNotification(pdzRecipients, m_pdzCredit, this)) {
			return false;
		}
	}

	return true;
}

void DlgSendMessage::sendMessageISDS(
    const QList<BoxContactsModel::PartialEntry> &recipEntries, bool isVodz)
{
	/* List of unique identifiers. */
	QList<QString> taskIdentifiers;
	const QDateTime currentTime(QDateTime::currentDateTimeUtc());

	QString infoText;

	Isds::Envelope envelope;
	Isds::Message message;

	/* Upload and attach external files to message structure. */
	if (isVodz) {
		QList<Isds::DmExtFile> dmExtFiles;
		if (!collectUploadAttachments(dmExtFiles)) {
			infoText = tr("An error occurred while loading external attachments into message.");
			goto finish;
		}
		message.setExtFiles(macroStdMove(dmExtFiles));
	} else {
		/* Attach attachment files to message structure. */
		QList<Isds::Document> documents;
		if (!buildDocuments(documents)) {
			infoText = tr("An error occurred while loading attachments into message.");
			goto finish;
		}
		message.setDocuments(macroStdMove(documents));
	}

	/* Attach message envelope to message structure. */
	if (Q_UNLIKELY(!buildEnvelope(envelope))) {
		infoText = tr("An error occurred during message envelope creation.");
		goto finish;
	}

	this->setCursor(Qt::WaitCursor);
	this->setEnabled(false);

	/*
	 * Generate unique identifiers.
	 * These must be complete before creating first task.
	 */
	for (const BoxContactsModel::PartialEntry &e : recipEntries) {
		taskIdentifiers.append(m_acntId.username() + "_" + e.id + "_" +
		    currentTime.toString() + "_" +
		    Utility::generateRandomString(6));
	}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
	m_transactIds = QSet<QString>(taskIdentifiers.begin(), taskIdentifiers.end());
#else /* < Qt-5.14.0 */
	m_transactIds = taskIdentifiers.toSet();
#endif /* >= Qt-5.14.0 */
	m_sentMsgResultList.clear();

	/* Send message to all recipients. */
	for (int i = 0; i < recipEntries.size(); ++i) {
		const BoxContactsModel::PartialEntry &e(recipEntries.at(i));

		/* Set recipient dbID and dmType. */
		envelope.setDbIDRecipient(e.id);
		envelope.setDmType(Isds::Envelope::dmType2Char(e.dmType));

		if (e.dmType == Isds::Type::MT_I &&
		        envelope.dmSenderRefNumber().isEmpty()) {
			QString sndrRefNum;
			do {
				bool ok;
				sndrRefNum = QInputDialog::getText(this,
				    tr("Commercial message send error"),
				    tr("The initiatory commercial message for the recipient '%1' does not have a sender reference number filled in.\nIt is a mandatory information for this type of commercial message. Please provide one.").arg(e.id),
				    QLineEdit::Normal, QString(), &ok);
				if (ok && !sndrRefNum.isEmpty()) {
					envelope.setDmSenderRefNumber(sndrRefNum);
				}
			} while (sndrRefNum.isEmpty());
		}

		if (e.dmType == Isds::Type::MT_O) {
			if (!m_dmSenderRefNumber.isEmpty()) {
				envelope.setDmRecipientRefNumber(m_dmSenderRefNumber);
			}
		}

		message.setEnvelope(envelope); /* Cannot use move here. */

		int processFlags = Task::PROC_NOTHING;
		if (m_ui->immDownloadCheckBox->checkState() == Qt::Checked) {
			processFlags |= Task::PROC_IMM_DOWNLOAD;
		}
		if (m_ui->immRecMgmtUploadCheckBox->checkState() == Qt::Checked) {
			processFlags |= Task::PROC_IMM_RM_UPLOAD;
		}

#define STORE_RAW_INTO_DB (!isVodz) /* Set to false if to store as files -- determined according to VoDZ. */
		TaskSendMessage *task = new (::std::nothrow) TaskSendMessage(
		    AcntIdDb(m_acntId, m_dbSet), taskIdentifiers.at(i), message,
		    e.name, e.address, e.pdz, isVodz, STORE_RAW_INTO_DB, processFlags,
		    m_recMgmtHierarchyId);
		if (task != Q_NULLPTR) {
			task->setAutoDelete(true);
			GlobInstcs::workPoolPtr->assignHi(task);
			m_msgsToBeSent[taskIdentifiers.at(i)] = i;
		} else {
			m_msgsFailed[taskIdentifiers.at(i)] = tr("Couldn't create a task to send the message.");
		}
#undef STORE_RAW_INTO_DB
	}

	return;

finish:
	infoText += "\n\n" + tr("The message will be discarded.");
	DlgMsgBoxDetail::message(this, QMessageBox::Critical,
	    tr("Send message error"),
	    tr("It has not been possible to send a message to the ISDS server."),
	    infoText, QString(), QMessageBox::Ok);
	this->close();
}

void DlgSendMessage::setIcons(void)
{
	{
		const QIcon ico(
		    IconContainer::construcIcon(IconContainer::ICON_DELETE));
		m_ui->removeRecipButton->setIcon(ico);
		m_ui->removeAttachButton->setIcon(ico);
	}

	m_ui->addRecipButton->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_ADD_USER));

	m_ui->findRecipButton->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_SEARCH_USER));

	m_ui->enterBoxIdButton->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_PENCIL));

	m_ui->addAttachButton->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_ADD_ATTACHMENT));

	m_ui->openAttachButton->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_FOLDER));

	m_ui->addTextButton->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DOCUMENT));
}

void DlgSendMessage::setPDZInfos(const QList<Isds::PDZInfoRec> &pdzInfos)
{
	m_pdzInfos = pdzInfos;

	if (m_pdzInfos.count() == 0) {
		m_ui->databoxInfo->setToolTip(
		    tr("Cannot send commercial messages from this data box. The sender data box hasn't got any active payment methods for commercial messages."));
		m_ui->databoxInfo->setText(m_ui->databoxInfo->text() %
		    QStringLiteral("; ") % tr("PDZ sending: unavailable"));
		return;
	}

	QString pdzSending = QStringLiteral("; ") % tr("PDZ sending: active");
	QString pdzSendingTooltip =
	    tr("The sender data box can send commercial messages.") %
	    QStringLiteral("\n") % tr("Available payment methods are:");

	/* Payment priority: O->G->K->E */
	for (const Isds::PDZInfoRec &info : m_pdzInfos) {
		if (info.pdzType() == Isds::Type::PPT_O) {
			/* Not used now. */
		} else if (info.pdzType() == Isds::Type::PPT_G) {
			pdzSendingTooltip += QStringLiteral("\n") %
			    tr("The sender data box is subsidised by the data box with ID '%1'.").arg(info.pdzPayer());
			if (info.pdzCnt() >= 0) {
				pdzSendingTooltip += QStringLiteral("\n- ") %
				    tr("Up to %1 subsidised commercial messages can be sent.").arg(info.pdzCnt());
			} else {
				pdzSendingTooltip += QStringLiteral("\n- ") %
				    tr("The number of subsidised commercial messages is unlimited.");
			}
			if (!info.pdzExpire().isValid()) {
				pdzSendingTooltip += QStringLiteral("\n- ") %
				    tr("Sending of subsidised commercial messages is not limited in time.");
			}
			pdzSendingTooltip += QStringLiteral("\n- ") %
			    tr("Initiatory commercial messages can also be sent.");
		} else if (info.pdzType() == Isds::Type::PPT_K) {
			pdzSendingTooltip += QStringLiteral("\n") % tr("Contract with Czech post.");
			pdzSendingTooltip += QStringLiteral("\n- ") %
			    tr("Initiatory commercial messages can also be sent.");
		} else if (info.pdzType() == Isds::Type::PPT_E) {
			m_pdzCredit = getPDZCreditFromISDS(m_acntId, m_boxId);
			pdzSending += QStringLiteral("; ") % tr("Remaining credit: %1 Kč").arg(m_pdzCredit);
			pdzSendingTooltip += QStringLiteral("\n") % tr("Credit.");
			if (info.pdzCnt() >= 1) {
				pdzSendingTooltip += QStringLiteral("\n- ") %
				    tr("The remaining credit is %1 Kč.").arg(m_pdzCredit);
				pdzSendingTooltip += QStringLiteral("\n- ") %
				    tr("The credit suffices for %n commercial message(s).", "", info.pdzCnt());
			} else {
				pdzSendingTooltip += QStringLiteral("\n") % QStringLiteral("- ") %
				    tr("The credit is too low or it has expired.");
			}
		}
	}

	m_ui->databoxInfo->setToolTip(pdzSendingTooltip);

	m_ui->databoxInfo->setText(m_ui->databoxInfo->text() + pdzSending);
}

QString DlgSendMessage::createdPdfPath(void) const
{
	return QDir::fromNativeSeparators(
	    m_tmpDirPath % QDir::separator() % SHORT_TEXT_MESSAGE_PDF_NAME);
}

QString DlgSendMessage::createPdf(void)
{
	QString fileName(createdPdfPath());

	enum Utility::TextFormat format =
	    int2TextFormat(m_ui->textFormatComboBox->currentData().toInt());

	if (Utility::printPDF(fileName, m_ui->textMessageEdit->toPlainText(),
	        format)) {
		return fileName;
	}

	return QString();
}
