/*
 * Copyright (C) 2014-2021 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 <QDir>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QMimeDatabase>
#include <QStringBuilder>

#include "src/cli/cmd_compose.h"
#include "src/datovka_shared/isds/box_interface.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/strings.h"
#include "src/datovka_shared/worker/pool.h"
#include "src/delegates/combobox_delegate.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_informative.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/isds_sessions.h"
#include "src/io/message_db.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_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"

#ifdef Q_COMPILER_RVALUE_REFS
#  define macroStdMove(x) ::std::move(x)
#else /* Q_COMPILER_RVALUE_REFS */
#  define macroStdMove(x) (x)
#endif /* Q_COMPILER_RVALUE_REFS */

const QString &dzPrefix(MessageDb *messageDb, qint64 dmId)
{
	const static QString unspecified(QLatin1String("DZ"));
	const static QString received(QLatin1String("DDZ"));
	const static QString sent(QLatin1String("ODZ"));

	if (Q_UNLIKELY(Q_NULLPTR == messageDb || dmId < 0)) {
		return unspecified;
	}

	switch (messageDb->getMessageType(dmId)) {
	case MessageDb::TYPE_RECEIVED:
		return received;
		break;
	case MessageDb::TYPE_SENT:
		return sent;
		break;
	default:
		return unspecified;
		break;
	}
}

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_lastAttAddPath(),
    m_pdzCredit(),
    m_dbSet(Q_NULLPTR),
    m_recipTableModel(this),
    m_attachModel(true, this),
    m_recMgmtHierarchyId(),
    m_transactIds(),
    m_sentMsgResultList(),
    m_dmType(Isds::Type::MT_UNKNOWN),
    m_dmSenderRefNumber(),
    m_pdzInfos(),
    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();

	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);
	}

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

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

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.
 *
 * @param[in]     view Table view to use for determining the selected lines.
 * @param[in,out] model Model to delete the rows from.
 */
static
void removeSelectedEntries(const QTableView *view, QAbstractItemModel *model)
{
	if (Q_UNLIKELY((view == Q_NULLPTR) || (model == Q_NULLPTR))) {
		Q_ASSERT(0);
		return;
	}

	QList<int> rows;
	{
		foreach (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) {
		model->removeRow(rows.at(i));
	}
}

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

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(
		    "QLabel { color: black }");
		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(1);
	}
}

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, &m_attachModel);
}

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

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

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);
	GlobInstcs::workPoolPtr->runSingle(task);
	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));

	sendMessageISDS(recipEntries);
}

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

	Q_UNUSED(acntId);
	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;

	foreach (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) {
		DlgMsgBox::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 = DlgMsgBox::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::initContent(enum Action action, const QList<MsgId> &msgIds)
{
	m_recipTableModel.setHeader();
	m_ui->recipTableView->setModel(&m_recipTableModel);

	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_ui->attachTableView->setModel(&m_attachModel);

	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());

	foreach (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->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(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->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, qint64, int, QString)), this,
	    SLOT(collectSendMessageStatus(AcntId, QString, int, QString,
	        QString, QString, bool, qint64, int, QString )));

	m_keepAliveTimer.start(DLG_ISDS_KEEPALIVE_MS);

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

	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();
}

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. */
	foreach (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(dzPrefix(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());

	foreach (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)
{
	foreach (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)
{
	qint64 aSize = m_attachModel.totalAttachmentSize();

	if (m_attachModel.rowCount() == 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 }");

	if (m_attachModel.rowCount() > MAX_ATTACHMENT_FILES) {
		m_ui->attachmentSizeInfo->setText(
		    tr("The permitted number of (%1) attachments has been exceeded.")
		        .arg(MAX_ATTACHMENT_FILES));
		return false;
	}

	if (aSize >= MAX_OVM_ATTACHMENT_SIZE_BYTES) {
		m_ui->attachmentSizeInfo->setText(
		    tr("Total size of attachments exceeds %1 MB.")
		        .arg(MAX_OVM_ATTACHMENT_SIZE_MB));
		return false;
	}

	if (aSize >= MAX_ATTACHMENT_SIZE_BYTES) {
		m_ui->attachmentSizeInfo->setText(
		    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(MAX_ATTACHMENT_SIZE_MB).arg(MAX_OVM_ATTACHMENT_SIZE_MB));
		return true;
	}

	m_ui->attachmentSizeInfo->setStyleSheet("QLabel { color: black }");
	if (aSize >= 1024) {
		m_ui->attachmentSizeInfo->setText(tr("Total size of attachments is ~%1 KB.").arg(aSize/1024));
	} else {
		m_ui->attachmentSizeInfo->setText(tr("Total size of attachments is ~%1 B.").arg(aSize));
	}
	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 */
	foreach (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);
		GlobInstcs::workPoolPtr->runSingle(task);
		result = task->m_result;
		errMsg = task->m_isdsError;
		longErrMsg = task->m_isdsLongError;
		foundBoxes = task->m_foundBoxes;
		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()) {
			DlgMsgBox::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. */
			DlgMsgBox::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 = DlgMsgBox::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);
			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.
		 */
		DlgMsgBox::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.
		 */
		DlgMsgBox::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.
		 */
		DlgMsgBox::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. */
		DlgMsgBox::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_recipTableModel.index(addedRow, BoxContactsModel::PAYMENT_COL));
		} else {
			m_ui->recipTableView->closePersistentEditor(
			    m_recipTableModel.index(addedRow, BoxContactsModel::PAYMENT_COL));
		}
	}
}

void DlgSendMessage::addRecipientBoxes(const QStringList &boxIds)
{
	foreach (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);
	GlobInstcs::workPoolPtr->runSingle(task);

	qint64 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);

	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"))) {
				/*
				 * 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 is not 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;
}

/*!
 * @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");

	foreach (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);
}

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


	QString infoText;
	QStringList pdzRecipients;
	int pdzCnt = 0;

	/* Compute number of messages which the sender has to pay for. */
	foreach (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;
		}
	}

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

	/* Attach envelope and 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));
	}

	if (!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.
	 */
	foreach (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;
		}

		TaskSendMessage *task = new (::std::nothrow) TaskSendMessage(
		    AcntIdDb(m_acntId, m_dbSet), taskIdentifiers.at(i), message,
		    e.name, e.address, e.pdz, processFlags,
		    m_recMgmtHierarchyId);
		if (task != Q_NULLPTR) {
			task->setAutoDelete(true);
			GlobInstcs::workPoolPtr->assignHi(task);
		}
	}

	return;

finish:
	infoText += "\n\n" + tr("The message will be discarded.");
	DlgMsgBox::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_PLUS));
		m_ui->addRecipButton->setIcon(ico);
		m_ui->addAttachButton->setIcon(ico);
	}

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

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

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

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

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 */
	foreach (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);
}
