/*
 * 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 <cstdlib>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QMimeDatabase>
#include <QMimeType>
#include <QTextStream>

#include "src/cli/cli.h"
#include "src/cli/cli_login.h"
#include "src/datovka_shared/compat/compiler.h" /* macroStdMove */
#include "src/datovka_shared/compat_qt/misc.h" /* _Qt_endl */
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/isds/message_interface_vodz.h"
#include "src/datovka_shared/isds/type_conversion.h"
#include "src/datovka_shared/utility/strings.h"
#include "src/datovka_shared/worker/pool.h"
#include "src/global.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_helper.h"
#include "src/isds/to_text_conversion.h"
#include "src/model_interaction/account_interaction.h"
#include "src/settings/prefs_specific.h"
#include "src/worker/task_download_message.h"
#include "src/worker/task_download_message_list.h"
#include "src/worker/task_import_zfo.h"
#include "src/worker/task_search_owner.h"
#include "src/worker/task_send_message.h"
#include "src/worker/task_upload_attachment.h"

const QSet<QString> serviceSet = QSet<QString>() << SER_LOGIN <<
SER_GET_MSG_LIST << SER_SEND_MSG << SER_GET_MSG << SER_GET_DEL_INFO <<
SER_GET_USER_INFO << SER_GET_OWNER_INFO << SER_CHECK_ATTACHMENT <<
SER_GET_MSG_IDS << SER_FIND_DATABOX << SER_EXPORT_MSG << SER_EXPORT_MSGS <<
SER_IMPORT_MSG << SER_IMPORT_MSGS;

// Known attributes definition
static const QStringList connectAttrs = QStringList()
    << "username" << "password" << "certificate" << "otpcode";
static const QStringList getMsgListAttrs = QStringList()
    << "dmType" << "dmStatusFilter" << "dmLimit" << "dmFromTime" << "dmToTime"
    << "complete";
static const QStringList sendMsgAttrs = QStringList()
    << "dbIDRecipient" << "dmAnnotation" << "dmToHands"
    << "dmRecipientRefNumber" << "dmSenderRefNumber" << "dmRecipientIdent"
    << "dmSenderIdent" << "dmLegalTitleLaw" << "dmLegalTitleYear"
    << "dmLegalTitleSect" << "dmLegalTitlePar" << "dmLegalTitlePoint"
    << "dmPersonalDelivery" << "dmAllowSubstDelivery" << "dmType" << "dmOVM"
    << "dmPublishOwnID" << "dmAttachment";
static const QStringList getMsgAttrs = QStringList()
    << "dmID" << "dmType" << "zfoFile" << "download" << "markDownload"
    << "attachmentDir";
static const QStringList getDelInfoAttrs = QStringList() << "dmID" << "zfoFile"
    << "download";
static const QStringList getMsgIdsAttrs = QStringList() << "dmType";
static const QStringList findDataboxAttrs = QStringList() << "dbType" << "dbID"
    << "ic" << "firmName" << "pnFirstName" << "pnLastName" << "adZipCode";
static const QStringList exportMsgAttrs = QStringList() << "dmID" << "exportPath"
     << "exportMsgType" << "exportDelInfo" << "msgName" << "infoName";
static const QStringList exportMsgListAttrs = QStringList()
    << "dmType" << "exportPath" << "exportMsgType" << "exportDelInfo"
    << "msgName" << "infoName" << "fromDate" << "toDate";
static const QStringList importMsgAttrs = QStringList()
    << "importFile" << "verifyIsds";
static const QStringList importMsgListAttrs = QStringList()
    << "importDir" << "includeSubDirs" << "verifyIsds";

/* ========================================================================= */
static
void printDataToStdOut(const QStringList &data)
/* ========================================================================= */
{
	QTextStream cout(stdout);

	for (int i = 0; i < data.count(); ++i) {
		if (i == (data.count() - 1)) {
			cout << data.at(i) << _Qt_endl << _Qt_endl;
		} else {
			cout << data.at(i) << " ";
		}
	}
}

static
void printDataToStdOut(const QList<qint64> &data)
{
	QTextStream cout(stdout);

	for (int i = 0; i < data.count(); ++i) {
		if (i == (data.count() - 1)) {
			cout << data.at(i) << _Qt_endl << _Qt_endl;
		} else {
			cout << data.at(i) << " ";
		}
	}
}

/* ========================================================================= */
static
void printErrToStdErr(const cli_error err, const QString &errmsg)
/* ========================================================================= */
{
	/* TODO - print error code and error message */

	QTextStream cout(stderr);
	cout << CLI_PREFIX << " error(" << err << ") : " << errmsg << _Qt_endl;
}

/* ========================================================================= */
static
const QString createErrorMsg(const QString &msg)
/* ========================================================================= */
{
	return QString(CLI_PREFIX) + QString(PARSER_PREFIX) + msg;
}

static
bool checkAttributeIfExists(const QString &service, const QString &attribute)
{
	if (service == SER_LOGIN) {
		return connectAttrs.contains(attribute);
	} else if (service == SER_GET_MSG_LIST) {
		return getMsgListAttrs.contains(attribute);
	} else if (service == SER_SEND_MSG) {
		return sendMsgAttrs.contains(attribute);
	} else if (service == SER_GET_MSG) {
		return getMsgAttrs.contains(attribute);
	} else if (service == SER_GET_DEL_INFO) {
		return getDelInfoAttrs.contains(attribute);
	} else if (service == SER_GET_MSG_IDS) {
		return getMsgIdsAttrs.contains(attribute);
	} else if (service == SER_FIND_DATABOX) {
		return findDataboxAttrs.contains(attribute);
	} else if (service == SER_EXPORT_MSG) {
		return exportMsgAttrs.contains(attribute);
	} else if (service == SER_EXPORT_MSGS) {
		return exportMsgListAttrs.contains(attribute);
	} else if (service == SER_IMPORT_MSG) {
		return importMsgAttrs.contains(attribute);
	} else if (service == SER_IMPORT_MSGS) {
		return importMsgListAttrs.contains(attribute);
	}
	return false;
}

/* ========================================================================= */
static
const QStringList parseAttachment(const QString &files)
/* ========================================================================= */
{
	if (files.isEmpty()) {
		return QStringList();
	}
	return files.split(";");
}

/* ========================================================================= */
static
const QStringList parseDbIDRecipient(const QString &dbIDRecipient)
/* ========================================================================= */
{
	if (dbIDRecipient.isEmpty()) {
		return QStringList();
	}
	return dbIDRecipient.split(";");
}

static
cli_error exportMsg(const QMap<QString, QVariant> &map, MessageDbSet *msgDbSet,
    QString &errmsg)
{
	qDebug() << CLI_PREFIX << "Export message" << map["dmID"].toString();

	const QString userName = map["username"].toString();
	MsgId msgId = msgDbSet->msgsMsgId(map["dmID"].toLongLong());

	if (Q_UNLIKELY(msgId.dmId() < 0)) {
		errmsg = QString("Message doesn't exist in the database for user '%1'.").arg(userName);
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ERROR;
	}
	MessageDb *messageDb =
	    msgDbSet->accessMessageDb(msgId.deliveryTime(), false);
	if (Q_UNLIKELY(messageDb == Q_NULLPTR)) {
		errmsg = "Database doesn't exists for user " + userName;
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ERROR;
	}

	/* Mandatory */
	if (!map.contains("exportPath") ||
	        map["exportPath"].toString().isEmpty()) {
		errmsg = "Parameter exportPath was not specified.";
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ATR_VAL_ERR;
	}

	/* Optional */
	QString exportMsgType("zfo");
	if (map.contains("exportMsgType") &&
	        !map["exportMsgType"].toString().isEmpty()) {
		exportMsgType = map["exportMsgType"].toString();
	}

	/* Optional */
	QString msgName;
	if (map.contains("msgName") && !map["msgName"].toString().isEmpty()) {
		msgName = map["msgName"].toString();
	}

	QString errStr;
	const QFileInfo fi(map["exportPath"].toString());
	const QString path = fi.path();
	const QString accountName = GlobInstcs::acntMapPtr->acntData(
	    GlobInstcs::acntMapPtr->acntIdFromUsername(userName)).accountName();
	const QString dbId = GlobInstcs::accntDbPtr->dbId(
	    AccountDb::keyFromLogin(userName));

	if (exportMsgType == "zfo" || exportMsgType == "all") {
		if (Q_UNLIKELY(Exports::EXP_SUCCESS != Exports::exportAs(
		        *msgDbSet, Exports::ZFO_MESSAGE, path,
		        msgName, userName, accountName, dbId, msgId,
		        errStr))) {
			errmsg = "Cannot export complete message to ZFO.";
			qDebug() << CLI_PREFIX << errmsg << errStr;
			return CLI_ERROR;
		}
	}

	if (exportMsgType == "pdf" || exportMsgType == "all") {
		if (Q_UNLIKELY(Exports::EXP_SUCCESS != Exports::exportAs(
		        *msgDbSet, Exports::PDF_ENVELOPE, path,
		        msgName, userName, accountName, dbId, msgId,
		        errStr))) {
			errmsg = "Cannot export message envelope to PDF.";
			qDebug() << CLI_PREFIX << errmsg << errStr;
			return CLI_ERROR;
		}
	}

	/* Optional */
	QString exportDelInfo("zfo");
	if (map.contains("exportDelInfo") &&
	        !map["exportDelInfo"].toString().isEmpty()) {
		exportDelInfo = map["exportDelInfo"].toString();
	}

	/* Optional */
	QString infoName;
	if (map.contains("infoName") && !map["infoName"].toString().isEmpty()) {
		infoName = map["infoName"].toString();
	}

	if (exportDelInfo == "zfo" || exportDelInfo == "all") {
		if (Q_UNLIKELY(Exports::EXP_SUCCESS != Exports::exportAs(
		        *msgDbSet, Exports::ZFO_DELIVERY, path,
		        infoName, userName, accountName, dbId, msgId,
		        errStr))) {
			errmsg = "Cannot export delivery info to ZFO.";
			qDebug() << CLI_PREFIX << errmsg << errStr;
			return CLI_ERROR;
		}
	}

	if (exportDelInfo == "pdf" || exportDelInfo == "all") {
		if (Q_UNLIKELY(Exports::EXP_SUCCESS != Exports::exportAs(
		        *msgDbSet, Exports::PDF_DELIVERY, path,
		        infoName, userName, accountName, dbId, msgId,
		        errStr))) {
			errmsg = "Cannot export delivery info to PDF.";
			qDebug() << CLI_PREFIX << errmsg << errStr;
			return CLI_ERROR;
		}
	}

	errmsg = "Export of message was successful.";
	qDebug() << CLI_PREFIX << errmsg;

	return CLI_SUCCESS;
}

static
cli_error exportMsg(qint64 msgId, QMap<QString, QVariant> &map,
    MessageDbSet *msgDbSet, QString &errmsg)
{
	map["dmID"] = msgId;
	return exportMsg(map, msgDbSet, errmsg);
}

static
cli_error exportMsgs(QMap<QString, QVariant> &map, MessageDbSet *msgDbSet,
    QString &errmsg)
{
	qDebug() << CLI_PREFIX << "Export messages for username" <<
	    map["username"].toString();

	if ((map["dmType"].toString() != MT_RECEIVED) &&
	    (map["dmType"].toString() != MT_SENT) &&
	    (map["dmType"].toString() != MT_SENT_RECEIVED)) {
		errmsg = "Wrong dmType value: " + map["dmType"].toString();
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ATR_VAL_ERR;
	}

	QList<qint64> msgList;
	QList<qint64> msgListOk;
	QList<qint64> msgListErr;

	if (!map.contains("fromDate") && !map.contains("toDate")) {

		if (map["dmType"].toString() == MT_RECEIVED) {
			msgList = msgDbSet->getAllMessageIDs(
			    MessageDb::TYPE_RECEIVED);
		} else if (map["dmType"].toString() == MT_SENT) {
			msgList = msgDbSet->getAllMessageIDs(
			    MessageDb::TYPE_SENT);
		} else {
			msgList = msgDbSet->getAllMessageIDs(
			    MessageDb::TYPE_RECEIVED);
			msgList += msgDbSet->getAllMessageIDs(
			    MessageDb::TYPE_SENT);
		}
		for (qint64 msgId : msgList) {
			if (CLI_SUCCESS == exportMsg(msgId, map,
			        msgDbSet, errmsg)) {
				msgListOk.append(msgId);
			} else  {
				msgListErr.append(msgId);
			}
		}

	} else {

		/* Optional */
		QDate fromDate(2009,7,1); /* First start of ISDS. */
		if (map.contains("fromDate") &&
		        !map["fromDate"].toString().isEmpty()) {
			const QDate date = map["fromDate"].toDate();
			if (date.isValid()) {
				fromDate = date;
			} else {
				errmsg = "Wrong fromDate value: " +
				    map["fromDate"].toString();
				qDebug() << CLI_PREFIX << errmsg;
				return CLI_ATR_VAL_ERR;
			}
		}

		/* Optional */
		QDate toDate(QDate::currentDate());
		if (map.contains("toDate") &&
		        !map["toDate"].toString().isEmpty()) {
			const QDate date = map["toDate"].toDate();
			if (date.isValid()) {
				toDate = date;
			} else {
				errmsg = "Wrong toDate value: " +
				    map["toDate"].toString();
				qDebug() << CLI_PREFIX << errmsg;
				return CLI_ATR_VAL_ERR;
			}
		}

		QList<MsgId> msgIdList;
		if (map["dmType"].toString() == MT_RECEIVED) {
			msgIdList = msgDbSet->msgsDateInterval(fromDate,
			     toDate, MessageDb::TYPE_RECEIVED);
		} else if (map["dmType"].toString() == MT_SENT) {
			msgIdList = msgDbSet->msgsDateInterval(fromDate,
			     toDate, MessageDb::TYPE_SENT);
		} else {
			msgIdList = msgDbSet->msgsDateInterval(fromDate,
			     toDate, MessageDb::TYPE_RECEIVED);
			msgIdList += msgDbSet->msgsDateInterval(fromDate,
			     toDate, MessageDb::TYPE_SENT);
		}

		for (MsgId msgId : msgIdList) {
			if (CLI_SUCCESS == exportMsg(msgId.dmId(), map,
			        msgDbSet, errmsg)) {
				msgListOk.append(msgId.dmId());
			} else  {
				msgListErr.append(msgId.dmId());
			}
		}
	}

	if (!msgListErr.isEmpty()) {
		errmsg = "Export of these messages have failed.";
		qDebug() << CLI_PREFIX << errmsg;
		printDataToStdOut(msgListErr);
	}

	errmsg = "Export of all messages was successful.";
	qDebug() << CLI_PREFIX << errmsg;

	printDataToStdOut(msgListOk);

	return CLI_SUCCESS;
}

static
cli_error importMsg(const QMap<QString, QVariant> &map, MessageDbSet *msgDbSet,
    QString &errmsg)
{
	qDebug() << CLI_PREFIX << "Import message";

	const AcntId acntId = GlobInstcs::acntMapPtr->acntIdFromUsername(
	    map["username"].toString());
	QList<AcntIdDb> acntDbList;
	acntDbList.append(AcntIdDb(acntId, msgDbSet));

	/* Mandatory */
	if (!map.contains("importFile") ||
	        map["importFile"].toString().isEmpty()) {
		errmsg = "Parameter importFile was not specified.";
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ATR_VAL_ERR;
	}

	QString importFile(map["importFile"].toString());

	/* Optional */
	bool authenticate = false;
	if (map.contains("verifyIsds") &&
	        !map["verifyIsds"].toString().isEmpty()) {
		authenticate = (map["verifyIsds"].toString() == "yes");
	}

	switch (TaskImportZfo::determineFileType(importFile)) {
	case TaskImportZfo::ZT_MESSAGE:
		{
			TaskImportZfo *task = new (::std::nothrow) TaskImportZfo(
			    acntDbList, importFile, TaskImportZfo::ZT_MESSAGE,
			    true, authenticate);
			if (Q_UNLIKELY(task == Q_NULLPTR)) {
				errmsg = "Cannot import ZFO message.";
				qDebug() << CLI_PREFIX << errmsg;
				return CLI_ERROR;
			}
			task->setAutoDelete(false);
			GlobInstcs::workPoolPtr->runSingle(task);
			if (TaskImportZfo::IMP_SUCCESS == task->m_result) {
				errmsg = "Import of message info was successful.";
			} else if (TaskImportZfo::IMP_DB_EXISTS == task->m_result) {
				errmsg = "Message already exists in the database.";
			} else {
				errmsg = "Cannot import ZFO message.";
				qDebug() << CLI_PREFIX << errmsg << task->m_resultDesc;
				delete task; task = Q_NULLPTR;
				return CLI_ERROR;
			}
			delete task; task = Q_NULLPTR;
		}
		break;
	case TaskImportZfo::ZT_DELIVERY_INFO:
		{
			TaskImportZfo *task = new (::std::nothrow) TaskImportZfo(
			    acntDbList, importFile, TaskImportZfo::ZT_DELIVERY_INFO,
			    true, authenticate);
			if (Q_UNLIKELY(task == Q_NULLPTR)) {
				errmsg = "Cannot import ZFO delivery info.";
				qDebug() << CLI_PREFIX << errmsg;
				return CLI_ERROR;
			}
			task->setAutoDelete(false);
			GlobInstcs::workPoolPtr->runSingle(task);
			if (TaskImportZfo::IMP_SUCCESS == task->m_result) {
				errmsg = "Import of delivery info was successful.";
			} else if (TaskImportZfo::IMP_DB_EXISTS == task->m_result) {
				errmsg = "Delivery info already exists in the database.";
			} else {
				errmsg = "Cannot import ZFO delivery info.";
				qDebug() << CLI_PREFIX << errmsg << task->m_resultDesc;
				delete task; task = Q_NULLPTR;
				return CLI_ERROR;
			}
			delete task; task = Q_NULLPTR;
		}
		break;
	default:
		errmsg = "Wrong ZFO format. File does not contain correct data for import.";
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ERROR;
		break;
	}

	qDebug() << CLI_PREFIX << errmsg;
	return CLI_SUCCESS;
}

static
cli_error importMsg(const QString &importFile, QMap<QString, QVariant> &map,
    MessageDbSet *msgDbSet, QString &errmsg)
{
	map["importFile"] = importFile;
	return importMsg(map, msgDbSet, errmsg);
}

static
cli_error importMsgs(QMap<QString, QVariant> &map, MessageDbSet *msgDbSet,
    QString &errmsg)
{
	qDebug() << CLI_PREFIX << "Import messages for username" <<
	    map["username"].toString();

	/* Mandatory */
	if (!map.contains("importDir") ||
	        map["importDir"].toString().isEmpty()) {
		errmsg = "Parameter importDir was not specified.";
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ATR_VAL_ERR;
	}

	QString importDir = map["importDir"].toString();

	/* Optional */
	bool includeSubDirs = false;
	if (map.contains("includeSubDirs") &&
	        !map["includeSubDirs"].toString().isEmpty()) {
		includeSubDirs = (map["includeSubDirs"].toString() == "yes");
	}

	QStringList fileList;
	const QStringList nameFilter("*.zfo");
	if (includeSubDirs) {
		/* Include subdirectories. */
		QDirIterator it(importDir, nameFilter, QDir::Files,
		    QDirIterator::Subdirectories);
		while (it.hasNext()) {
			fileList.append(it.next());
		}
	} else {
		QStringList files = QDir(importDir).entryList(nameFilter);
		for (int i = 0; i < files.size(); ++i) {
			fileList.append(
			    importDir + QDir::separator() + files.at(i));
		}
	}

	if (Q_UNLIKELY(fileList.isEmpty())) {
		errmsg = "No zfo files in the importDir.";
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ERROR;
	}

	/* Detect type of zfo content. */
	QStringList msgList;
	QStringList delInfoList;
	for (const QString &file : fileList) {
		enum TaskImportZfo::ZfoType ret =
		    TaskImportZfo::determineFileType(file);
		if (TaskImportZfo::ZT_MESSAGE == ret) {
			msgList.append(file);
		} else if (TaskImportZfo::ZT_DELIVERY_INFO == ret) {
			delInfoList.append(file);
		}
	}

	QStringList errList;
	/* First import messages. */
	for (const QString &file : msgList) {
		if (CLI_SUCCESS != importMsg(file, map, msgDbSet, errmsg)) {
			errList.append(file);
		}
	}
	/* Second delivery info. */
	for (const QString &file : delInfoList) {
		if (CLI_SUCCESS != importMsg(file, map, msgDbSet, errmsg)) {
			errList.append(file);
		}
	}

	if (Q_UNLIKELY(errList.isEmpty())) {
		errmsg = "Import of all messages was successful.";
		qDebug() << CLI_PREFIX << errmsg;
		printDataToStdOut(errList);
		return CLI_ERROR;
	}

	errmsg = "Import of all messages was successful.";
	qDebug() << CLI_PREFIX << errmsg;
	return CLI_SUCCESS;
}

/* ========================================================================= */
static
cli_error getMsgList(const QMap<QString, QVariant> &map, MessageDbSet *msgDbSet,
    QString &errmsg)
/* ========================================================================= */
{
	const QString username = map["username"].toString();
	const AcntId acntId = GlobInstcs::acntMapPtr->acntIdFromUsername(username);

	qDebug() << CLI_PREFIX << "Downloading of message list for username"
	    << username;

	/* messages counters */
	QList<qint64> newMsgIdList;
	bool complete = false;
	const bool checkDownloadedList = false;
	unsigned long dmLimit = 0;
	Isds::Type::DmFiltStates dmStatusFilter = Isds::Type::MFS_ANY;
	bool ok;

	if (map.contains("dmStatusFilter")) {
		uint number = map["dmStatusFilter"].toString().toUInt(&ok);
		if (!ok) {
			errmsg = "Wrong dmStatusFilter value: " +
			    map["dmStatusFilter"].toString();
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ATR_VAL_ERR;
		}
		switch (number) {
		case 1: dmStatusFilter = Isds::Type::MFS_POSTED; break;
		case 2: dmStatusFilter = Isds::Type::MFS_STAMPED; break;
		case 3: dmStatusFilter = Isds::Type::MFS_INFECTED; break;
		case 4: dmStatusFilter = Isds::Type::MFS_DELIVERED; break;
		case 5: dmStatusFilter = Isds::Type::MFS_ACCEPTED_FICT; break;
		case 6: dmStatusFilter = Isds::Type::MFS_ACCEPTED; break;
		case 7: dmStatusFilter = Isds::Type::MFS_READ; break;
		case 8: dmStatusFilter = Isds::Type::MFS_UNDELIVERABLE; break;
		case 9: dmStatusFilter = Isds::Type::MFS_REMOVED; break;
		case 10: dmStatusFilter = Isds::Type::MFS_IN_VAULT; break;
		default: dmStatusFilter = Isds::Type::MFS_ANY; break;
		}
	}

	if (map.contains("complete")) {
		QString compValue = map.value("complete").toString();
		if (!(compValue == "no") && !(compValue == "yes")) {
			errmsg = "complete attribute has wrong value "
			    "(no,yes is required)";
			qDebug() << createErrorMsg(errmsg);
			return CLI_ATR_VAL_ERR;
		}
		complete = (compValue == "yes") ? true : false;
	}

	if (map.contains("dmLimit")) {
		dmLimit = map["dmLimit"].toString().toULong(&ok);
		if (!ok) {
			errmsg = "Wrong dmLimit value: " +
			    map["dmLimit"].toString();
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ATR_VAL_ERR;
		}
	}

	if (0 == dmLimit) {
		/* Increase limit. */
		dmLimit = MESSAGE_LIST_LIMIT;
	}

	if ((map["dmType"].toString() != MT_RECEIVED) &&
	    (map["dmType"].toString() != MT_SENT) &&
	    (map["dmType"].toString() != MT_SENT_RECEIVED)) {
		errmsg = "Wrong dmType value: " + map["dmType"].toString();
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ATR_VAL_ERR;
	}

	QString err, longErr;
	if ((map["dmType"].toString() == MT_RECEIVED) ||
	    (map["dmType"].toString() == MT_SENT_RECEIVED)) {
		TaskDownloadMessageList *task;

		task = new (::std::nothrow) TaskDownloadMessageList(
		    AcntIdDb(acntId, msgDbSet), AcntId(), MSG_RECEIVED,
		    complete, checkDownloadedList, dmLimit, dmStatusFilter);
		if (Q_UNLIKELY(task == Q_NULLPTR)) {
			return CLI_ERROR;
		}
		task->setAutoDelete(false);
		if (Q_UNLIKELY(!GlobInstcs::workPoolPtr->runSingle(task))) {
			delete task; task = Q_NULLPTR;
			return CLI_ERROR;
		}

		bool success =
		   TaskDownloadMessageList::DL_SUCCESS == task->m_result;

		if (success) {
			newMsgIdList += task->m_newMsgIdList;
			qDebug() << CLI_PREFIX <<
			    "Received message list has been downloaded";
		} else {
			errmsg =
			    "Error while downloading received message list";
			qDebug() << CLI_PREFIX << errmsg << "Error code:" <<
			    task->m_result << task->m_isdsError <<
			    task->m_isdsLongError;
		}

		delete task; task = Q_NULLPTR;

		if (!success) {
			/* Stop pending jobs. */
			GlobInstcs::workPoolPtr->stop();
			GlobInstcs::workPoolPtr->clear();
			return CLI_ERROR;
		}
	}

	if ((map["dmType"].toString() == MT_SENT) ||
	    (map["dmType"].toString() == MT_SENT_RECEIVED)) {
		TaskDownloadMessageList *task;

		task = new (::std::nothrow) TaskDownloadMessageList(
		    AcntIdDb(acntId, msgDbSet), AcntId(), MSG_SENT, complete,
		    checkDownloadedList, dmLimit, dmStatusFilter);
		if (Q_UNLIKELY(task == Q_NULLPTR)) {
			return CLI_ERROR;
		}
		task->setAutoDelete(false);
		if (Q_UNLIKELY(!GlobInstcs::workPoolPtr->runSingle(task))) {
			delete task; task = Q_NULLPTR;
			return CLI_ERROR;
		}

		bool success =
		   TaskDownloadMessageList::DL_SUCCESS == task->m_result;

		if (success) {
			newMsgIdList += task->m_newMsgIdList;
			qDebug() << CLI_PREFIX <<
			    "Sent message list has been downloaded";
		} else {
			errmsg = "Error while downloading sent message list";
			qDebug() << CLI_PREFIX << errmsg << "Error code:" <<
			    task->m_result << task->m_isdsError <<
			    task->m_isdsLongError;
		}

		delete task; task = Q_NULLPTR;

		if (!success) {
			/* Stop pending jobs. */
			GlobInstcs::workPoolPtr->stop();
			GlobInstcs::workPoolPtr->clear();
			return CLI_ERROR;
		}
	}

	/* Wait for possible pending jobs. */
	GlobInstcs::workPoolPtr->wait();

	printDataToStdOut(newMsgIdList);
	return CLI_SUCCESS;
}

/* ========================================================================= */
static
cli_error getMsg(const QMap<QString, QVariant> &map, MessageDbSet *msgDbSet,
    bool needsISDS, QString &errmsg)
/* ========================================================================= */
{
#define STORE_RAW_INTO_DB true /* Set to false if to store as files. TODO -- determine according to VoDZ. */
	qDebug() << CLI_PREFIX << "Downloading of message" <<
	    map["dmID"].toString();

	QString err, longErr;
	TaskDownloadMessage::Result ret;
	const QString username = map["username"].toString();
	const AcntId acntId = GlobInstcs::acntMapPtr->acntIdFromUsername(username);
	const qint64 msgID = map["dmID"].toLongLong();
	MsgId msgId(msgID, QDateTime());

	if (needsISDS) {
		/*
		 * Need to know the delivery time before attempting to download
		 * message data.
		 */
		msgId = msgDbSet->msgsMsgId(msgID);
		if (Q_UNLIKELY(msgId.dmId() < 0)) {
			errmsg = QString("Message envelope doesn't exist in the database for user '%1'. Try downloading message list first.").arg(username);
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ERROR;
		}

		if (map["dmType"].toString() == MT_RECEIVED) {

			ret = TaskDownloadMessage::downloadAndStoreSignedMessage(
			    AcntIdDb(acntId, msgDbSet), msgId,
			    MSG_RECEIVED, STORE_RAW_INTO_DB, err, longErr, QString());

			if (TaskDownloadMessage::DM_SUCCESS == ret) {
				qDebug() << CLI_PREFIX << "Received message" <<
				    map["dmID"].toString() << "has been "
				    "downloaded";
			} else {
				errmsg = "Error while downloading received "
				    "message";
				qDebug() << CLI_PREFIX << errmsg <<
				    "Error code:" << ret << err;
				return CLI_ERROR;
			}

		} else if (map["dmType"].toString() == MT_SENT) {

			ret = TaskDownloadMessage::downloadAndStoreSignedMessage(
			    AcntIdDb(acntId, msgDbSet), msgId,
			    MSG_SENT, STORE_RAW_INTO_DB, err, longErr, QString());

			if (TaskDownloadMessage::DM_SUCCESS == ret) {
				qDebug() << CLI_PREFIX << "Sent message" <<
				    map["dmID"].toString() << "has been "
				    "downloaded";
			} else {
				errmsg = "Error while downloading sent "
				    "message";
				qDebug() << CLI_PREFIX << errmsg <<
				    "Error code:" << ret << err << longErr;
				return CLI_ERROR;
			}
		} else {
			errmsg = "Wrong dmType value: " +
			    map["dmType"].toString();
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ATR_VAL_ERR;
		}
	}

	msgId = msgDbSet->msgsMsgId(msgID);
	if (Q_UNLIKELY(msgId.dmId() < 0)) {
		errmsg = QString("Message doesn't exist in the database for user '%1'.").arg(username);
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ERROR;
	}
	MessageDb *messageDb =
	    msgDbSet->accessMessageDb(msgId.deliveryTime(), false);
	if (Q_UNLIKELY(messageDb == Q_NULLPTR)) {
		errmsg = "Database doesn't exists for user " + username;
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ERROR;
	}

	if (map.contains("attachmentDir") &&
	    !map["attachmentDir"].toString().isEmpty()) {
		const QFileInfo fi(map["attachmentDir"].toString());
		const QString path = fi.path();
		if (!QDir(path).exists()) {
			errmsg = "Wrong path " + path + " for file saving";
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ERROR;
		}

		QList<Isds::Document> files =
		    messageDb->getMessageAttachments(msgID);

		foreach (const Isds::Document &file, files) {

			QString fileName = file.fileDescr();
			if (fileName.isEmpty()) {
				errmsg = "Cannot save file because name "
				    "of file missing";
				qDebug() << CLI_PREFIX << errmsg;
				return CLI_ERROR;
			}

			if (file.binaryContent().isEmpty()) {
				errmsg = "Cannot save file " + fileName +
				    "because file content missing";
				qDebug() << CLI_PREFIX << errmsg;
				return CLI_ERROR;
			}

			fileName = path + QDir::separator() + fileName;

			enum WriteFileState ret =
			    writeFile(fileName, file.binaryContent());
			if (WF_SUCCESS == ret) {
				qDebug() << CLI_PREFIX << "Save file" <<
				    fileName << "of message" <<
				    map["dmID"].toString();
			} else {
				errmsg = "Saving error of file " + fileName +
				    " of message " + map["dmID"].toString() +
				    " failed";
				qDebug() << CLI_PREFIX << errmsg;
				return CLI_ERROR;
			}
		}
	}

	if (map.contains("zfoFile") && !map["zfoFile"].toString().isEmpty()) {
		const QFileInfo fi(map["zfoFile"].toString());
		const QString path = fi.path();
		if (!QDir(path).exists()) {
			errmsg = "Wrong path " + path + " for file saving";
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ERROR;
		}

		const QByteArray data =
		    messageDb->getCompleteMessageRaw(msgID);

		if (Q_UNLIKELY(data.isEmpty())) {
			errmsg = "Cannot export complete message to ZFO";
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ERROR;
		}

		enum WriteFileState ret = writeFile(map["zfoFile"].toString(),
		    data);
		if (WF_SUCCESS == ret) {
			qDebug() << CLI_PREFIX << "Export of message" <<
			map["dmID"].toString() <<  "to ZFO was successful.";
		} else {
			errmsg = "Export of message " + map["dmID"].toString() +
			    " to ZFO was NOT successful";
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ERROR;
		}
	}

	if (map.contains("markDownload")) {
		if (map.value("markDownload").toString() == "yes") {
			messageDb->setMessagesLocallyRead(
			    QList<MsgId>() << msgId, true);
		}
	}

	return CLI_SUCCESS;
#undef STORE_RAW_INTO_DB
}

/* ========================================================================= */
static
cli_error getDeliveryInfo(const QMap<QString, QVariant> &map,
    MessageDbSet *msgDbSet, bool needsISDS, QString &errmsg)
/* ========================================================================= */
{
#define STORE_RAW_INTO_DB true /* Set to false if to store as files. TODO -- determine according to VoDZ. */
	qDebug() << CLI_PREFIX << "Downloading of delivery info for message" <<
	    map["dmID"].toString();

	const QString username = map["username"].toString();
	const AcntId acntId = GlobInstcs::acntMapPtr->acntIdFromUsername(username);

	MsgId msgId = msgDbSet->msgsMsgId(map["dmID"].toLongLong());
	if (msgId.dmId() < 0) {
		errmsg = "Message does not exist in the database "
		    "for user " + username;
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ERROR;
	}
	MessageDb *messageDb =
	    msgDbSet->accessMessageDb(msgId.deliveryTime(), false);
	if (Q_UNLIKELY(messageDb == Q_NULLPTR)) {
		errmsg = "Database doesn't exists for user " + username;
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ERROR;
	}

	if (needsISDS) {
		QString isdsError, isdsLongError;
		if (TaskDownloadMessage::DM_SUCCESS ==
		    TaskDownloadMessage::downloadAndStoreSignedDeliveryInfo(
		        AcntIdDb(acntId, msgDbSet), map["dmID"].toLongLong(),
		        STORE_RAW_INTO_DB, isdsError, isdsLongError)) {
			qDebug() << CLI_PREFIX << "Delivery info of message"
			    << map["dmID"].toString() << "has been downloaded."
			    << isdsError << isdsLongError;
		} else {
			errmsg = "Error while downloading delivery info";
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ERROR;
		}
	}

	if (map.contains("zfoFile") && !map["zfoFile"].toString().isEmpty()) {
		const QFileInfo fi(map["zfoFile"].toString());
		const QString path = fi.path();
		if (!QDir(path).exists()) {
			errmsg = "Wrong path " + path + " for file saving";
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ERROR;
		}

		const QByteArray data = messageDb->getDeliveryInfoRaw(
		    map["dmID"].toLongLong());

		if (Q_UNLIKELY(data.isEmpty())) {
			errmsg = "Cannot export delivery info to ZFO";
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ERROR;
		}

		enum WriteFileState ret = writeFile(map["zfoFile"].toString(),
		    data);
		if (WF_SUCCESS == ret) {
			qDebug() << CLI_PREFIX << "Export of delivery info" <<
			map["dmID"].toString() <<  "to ZFO was successful.";
		} else {
			errmsg = "Export of delivery info "
			     + map["dmID"].toString() +
			    " to ZFO was NOT successful";
			qDebug() << CLI_PREFIX << errmsg;
			return CLI_ERROR;
		}
	}

	return CLI_SUCCESS;
#undef STORE_RAW_INTO_DB
}

/* ========================================================================= */
static
cli_error checkAttachment(const QMap<QString, QVariant> &map,
    MessageDbSet *msgDbSet)
/* ========================================================================= */
{
	const QString username = map["username"].toString();

	qDebug() << CLI_PREFIX << "Checking of missing messages attachment for"
	    " username" <<  username;

	printDataToStdOut(msgDbSet->getAllMessageIDsWithoutAttach());

	return CLI_SUCCESS;
}

/* ========================================================================= */
static
cli_error getMsgIds(const QMap<QString, QVariant> &map,
    MessageDbSet *msgDbSet, QString &errmsg)
/* ========================================================================= */
{
	const QString username = map["username"].toString();

	qDebug() << CLI_PREFIX << "Get list of message IDs "
	    "from local database for username" <<  username;

	if ((map["dmType"].toString() != MT_RECEIVED) &&
	    (map["dmType"].toString() != MT_SENT) &&
	    (map["dmType"].toString() != MT_SENT_RECEIVED)) {
		errmsg = "Wrong dmType value: " + map["dmType"].toString();
		qDebug() << CLI_PREFIX << errmsg;
		return CLI_ATR_VAL_ERR;
	}

	if (map["dmType"].toString() == MT_RECEIVED) {
		printDataToStdOut(msgDbSet->getAllMessageIDs(MessageDb::TYPE_RECEIVED));
	} else if (map["dmType"].toString() == MT_SENT) {
		printDataToStdOut(msgDbSet->getAllMessageIDs(MessageDb::TYPE_SENT));
	} else {
		printDataToStdOut(msgDbSet->getAllMessageIDs(MessageDb::TYPE_RECEIVED));
		printDataToStdOut(msgDbSet->getAllMessageIDs(MessageDb::TYPE_SENT));
	}
	return CLI_SUCCESS;
}

/* ========================================================================= */
static
cli_error getUserInfo(const QMap<QString, QVariant> &map, QString &errmsg)
/* ========================================================================= */
{
	const AcntId acntId = GlobInstcs::acntMapPtr->acntIdFromUsername(
	    map["username"].toString());

	qDebug() << CLI_PREFIX << "Downloading info about username"
	    << acntId.username();

	if (IsdsHelper::getUserInfoFromLogin(acntId)) {
		return CLI_SUCCESS;
	}

	errmsg = "Cannot download user info";
	return CLI_ERROR;
}

/* ========================================================================= */
static
cli_error getOwnerInfo(const QMap<QString, QVariant> &map, QString &errmsg)
/* ========================================================================= */
{
	const AcntId acntId = GlobInstcs::acntMapPtr->acntIdFromUsername(
	    map["username"].toString());

	qDebug() << CLI_PREFIX
	    << "downloading info about owner and its databox for username"
	    << acntId.username();

	if (IsdsHelper::getOwnerInfoFromLogin(acntId)) {
		return CLI_SUCCESS;
	}

	errmsg = "Cannot download owner info";
	return CLI_ERROR;
}

/* ========================================================================= */
static
cli_error findDatabox(const QMap<QString, QVariant> &map, QString &errmsg)
/* ========================================================================= */
{
	qDebug() << CLI_PREFIX << "find info about databox from username"
	    <<  map["username"].toString();

	Isds::Address address;
	Isds::DbOwnerInfo dbOwnerInfo;
	Isds::PersonName personName;
	QList<Isds::DbOwnerInfo> foundBoxes;
	QString errMsg;
	QString longErrMsg;

	dbOwnerInfo.setDbID(map.contains("dbID") ?
	    map.value("dbID").toString() : QString());
	dbOwnerInfo.setDbType(Isds::strVariant2DbType(map.value("dbType")));
	dbOwnerInfo.setIc(map.contains("ic") ?
	    map.value("ic").toString() : QString());
	personName.setFirstName(map.contains("pnFirstName") ?
	    map.value("pnFirstName").toString() : QString());
	personName.setLastName(map.contains("pnLastName") ?
	    map.value("pnLastName").toString() : QString());
	dbOwnerInfo.setPersonName(macroStdMove(personName));
	dbOwnerInfo.setFirmName(map.contains("firmName") ?
	    map.value("firmName").toString() : QString());
	address.setZipCode(map.contains("adZipCode") ?
	    map.value("adZipCode").toString() : QString());
	dbOwnerInfo.setAddress(macroStdMove(address));

	const AcntId acntId = GlobInstcs::acntMapPtr->acntIdFromUsername(map["username"].toString());
	enum TaskSearchOwner::Result result = TaskSearchOwner::isdsSearch(
	    acntId, dbOwnerInfo, foundBoxes, errMsg, longErrMsg);

	if (TaskSearchOwner::SO_SUCCESS != result) {
		errmsg = macroStdMove(longErrMsg);
		return CLI_ERROR;
	}

	foreach (const Isds::DbOwnerInfo &box, foundBoxes) {
		QStringList contact;
		contact.append(box.dbID());
		contact.append("|");
		contact.append(Isds::textOwnerName(box));
		contact.append("|");
		contact.append(Isds::textAddressWithoutIc(box.address()));
		contact.append("|");
		contact.append(box.address().zipCode());
		printDataToStdOut(contact);
	}

	return CLI_SUCCESS;
}

/*!
 * @brief Get MIME type from file.
 *
 * @param[in] fi File info.
 * @return MIME type.
 */
static
const QString getMimeType(const QFileInfo &fi)
{
	if (fi.suffix().toLower() == "zfo") {
		return QStringLiteral("application/vnd.software602.filler.form-xml-zip");
	}

	QMimeDatabase db;
	QMimeType mimeType = db.mimeTypeForFile(fi);
	if (mimeType.isValid()) {
		return mimeType.name();
	}

	return QStringLiteral("application/octet-stream");
}

/*!
 * @brief Build DmExtFile list and upload attachments.
 *
 * @param[in] acntId Account id.
 * @param[in] filePaths File list.
 * @return DmExtFile list.
 */
static
QList<Isds::DmExtFile> buildExtFiles(const AcntId &acntId,
    const QStringList &filePaths)
{
	const QString timeStr = QDateTime::currentDateTimeUtc().toString();
	QList<Isds::DmExtFile> dmExtFiles;

	for (int i = 0; i < filePaths.size(); ++i) {
		const QString &filePath = filePaths.at(i);

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

		Isds::DmExtFile dmExtFile;

		const QFileInfo fi(filePath);
		if (!fi.isReadable() || !fi.isFile()) {
			logErrorNL(CLI_PREFIX "Wrong file name '%s' or file is missing.",
			    filePath.toUtf8().constData());
			goto fail;
		}

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

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

			/* Prepare dmFile content. */
			{
				dmFile.setFileDescr(fi.fileName());
				/*
				 * The FileMetaType is ignored when uploading separate
				 * attachments. But a value must be provided to pass it
				 * successfully to the underlying library.
				 */
				dmFile.setFileMetaType((i == 0) ?
				    Isds::Type::FMT_MAIN : Isds::Type::FMT_ENCLOSURE);
				dmFile.setMimeType(getMimeType(fi));

				QFile file(QDir::fromNativeSeparators(filePath));
				if (file.exists()) {
					if (!file.open(QIODevice::ReadOnly)) {
						logErrorNL(CLI_PREFIX "Couldn't open file '%s'.",
						    filePath.toUtf8().constData());
						goto fail;
					}
					dmFile.setBinaryContent(file.readAll());
				}
			}

			task = new (::std::nothrow) TaskUploadAttachment(
			    acntId, macroStdMove(dmFile), transactionId);
			if (Q_UNLIKELY(task == Q_NULLPTR)) {
				goto fail;
			}
			task->setAutoDelete(false);
			GlobInstcs::workPoolPtr->runSingle(task);
			Isds::DmAtt dmAtt = task->m_dmAtt;
			dmExtFile.setDmAtt(dmAtt);
			delete task; task = Q_NULLPTR;
			dmExtFiles.append(dmExtFile);
		}
	}

	return dmExtFiles;
fail:
	return QList<Isds::DmExtFile>();
}

/* ========================================================================= */
static
QList<Isds::Document> buildDocuments(const QStringList &filePaths)
/* ========================================================================= */
{
	QList<Isds::Document> documents;
	bool mainFile = true;

	foreach (const QString &filePath, filePaths) {

		if (Q_UNLIKELY(filePath.isEmpty())) {
			continue;
		}

		Isds::Document document;

		QFileInfo fi(filePath);
		if (!fi.isReadable() || !fi.isFile()) {
			logErrorNL(CLI_PREFIX "Wrong file name '%s' or file is missing.",
			    filePath.toUtf8().constData());
			goto fail;
		}

		document.setFileDescr(fi.fileName());
		document.setFileMetaType(mainFile ?
		    Isds::Type::FMT_MAIN : Isds::Type::FMT_ENCLOSURE);
		document.setMimeType(QStringLiteral(""));

		QFile file(QDir::fromNativeSeparators(filePath));
		if (file.exists()) {
			if (!file.open(QIODevice::ReadOnly)) {
				logErrorNL(CLI_PREFIX "Couldn't open file '%s'.",
				    filePath.toUtf8().constData());
				goto fail;
			}
		}

		document.setBinaryContent(file.readAll());
		documents.append(document);
		mainFile = false;
	}

	return documents;
fail:
	return QList<Isds::Document>();
}

/* ========================================================================= */
static
Isds::Envelope buildEnvelope(const QMap<QString, QVariant> &map)
/* ========================================================================= */
{
	Isds::Envelope envelope;

	envelope.setDmAnnotation(map["dmAnnotation"].toString());

	if (map.contains("dmSenderIdent")) {
		envelope.setDmSenderIdent(map["dmSenderIdent"].toString());
	}

	if (map.contains("dmRecipientIdent")) {
		envelope.setDmRecipientIdent(
		    map["dmRecipientIdent"].toString());
	}

	if (map.contains("dmSenderRefNumber")) {
		envelope.setDmSenderRefNumber(
		     map["dmSenderRefNumber"].toString());
	}

	if (map.contains("dmRecipientRefNumber")) {
		envelope.setDmRecipientRefNumber(
		    map["dmRecipientRefNumber"].toString());
	}

	if (map.contains("dmToHands")) {
		envelope.setDmToHands(map["dmToHands"].toString());
	}

	if (map.contains("dmLegalTitleLaw")) {
		envelope.setDmLegalTitleLaw(
		    map["dmLegalTitleLaw"].toLongLong());
	}

	if (map.contains("dmLegalTitleYear")) {
		envelope.setDmLegalTitleYear(
		    map["dmLegalTitleYear"].toLongLong());
	}

	if (map.contains("dmLegalTitleSect")) {
		envelope.setDmLegalTitleSect(
		    map["dmLegalTitleSect"].toString());
	}

	if (map.contains("dmLegalTitlePar")) {
		envelope.setDmLegalTitlePar(map["dmLegalTitlePar"].toString());
	}

	if (map.contains("dmLegalTitlePoint")) {
		envelope.setDmLegalTitlePoint(
		    map["dmLegalTitlePoint"].toString());
	}

	if (map.contains("dmPersonalDelivery")) {
		envelope.setDmPersonalDelivery(
		    map["dmPersonalDelivery"].toString() != "0" ?
		    Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE);
	}

	if (map.contains("dmAllowSubstDelivery")) {
		envelope.setDmAllowSubstDelivery(
		    map["dmAllowSubstDelivery"].toString() != "0" ?
		    Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE);
	}

	if (map.contains("dmType")) {
		envelope.setDmType(map["dmType"].toChar());
	}

	if (map.contains("dmOVM")) {
		envelope.setDmOVM(map["dmOVM"].toString() != "0" ?
		    Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE);
	}

	if (map.contains("dmPublishOwnID")) {
		envelope.setDmPublishOwnID(
		    map["dmPublishOwnID"].toString() != "0" ?
		    Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE);
	}

	return envelope;
}

/*!
 * @brief Test if message must be sent as big message.
 *
 * @param[in] files File list.
 * @return True if message will be sent as big message.
 */
static
bool sendAsVodz(const QStringList &files)
{
	if (!PrefsSpecific::enableVodzSending(*GlobInstcs::prefsPtr)) {
		return false;
	}

	const qint64 maxSizeBytes =
	    PrefsSpecific::bytesInMByte(*GlobInstcs::prefsPtr) *
	    PrefsSpecific::maxAttachmentMBytes(*GlobInstcs::prefsPtr);

	qint64 totalSizeBytes = 0;
	for (const QString &file : files) {

		if (Q_UNLIKELY(file.isEmpty())) {
			continue;
		}

		QFileInfo fi(file);
		if (!fi.isReadable() || !fi.isFile()) {
			logErrorNL(CLI_PREFIX "Wrong file name '%s' or file is missing.",
			    file.toUtf8().constData());
			continue;
		}

		int fileSizeBytes = fi.size();
		if (fileSizeBytes >= maxSizeBytes) {
			return true;
		}

		totalSizeBytes += fileSizeBytes;
	}

	return (totalSizeBytes >= maxSizeBytes);
}

/* ========================================================================= */
static
cli_error createAndSendMsg(const QMap<QString, QVariant> &map,
    MessageDbSet *msgDbSet, QString &errmsg)
/* ========================================================================= */
{
#define STORE_RAW_INTO_DB true /* Set to false if to store as files. TODO -- determine according to VoDZ. */
	qDebug() << CLI_PREFIX << "creating a new message...";

	cli_error ret = CLI_SUCCESS;
	QStringList sendID;
	Isds::Envelope evelope = buildEnvelope(map);
	Isds::Message message;
	const AcntId acntId = GlobInstcs::acntMapPtr->acntIdFromUsername(
	    map["username"].toString());
	const bool isVodz = sendAsVodz(map["dmAttachment"].toStringList());

	if (isVodz) {
		message.setExtFiles(buildExtFiles(acntId, map["dmAttachment"].toStringList()));
	} else {
		message.setDocuments(buildDocuments(map["dmAttachment"].toStringList()));
	}

	foreach (const QString &recipientId,
	    map.value("dbIDRecipient").toStringList()) {

		logInfo(CLI_PREFIX "Sending message to '%s'.\n",
		    recipientId.toUtf8().constData());

		evelope.setDbIDRecipient(recipientId);
		message.setEnvelope(evelope); /* Cannot use move here. */

		QString transactId = map["username"].toString() + "_" +
		    QDateTime::currentDateTimeUtc().toString() + "_" +
		    Utility::generateRandomString(6);

		TaskSendMessage *task = new (::std::nothrow) TaskSendMessage(
		    AcntIdDb(acntId, msgDbSet), transactId, message,
		    "Databox ID: " + recipientId, "unknown", false, isVodz,
		    STORE_RAW_INTO_DB);
		if (Q_UNLIKELY(task == Q_NULLPTR)) {
			ret = CLI_ERROR;
			continue;
		}
		task->setAutoDelete(false);
		if (Q_UNLIKELY(!GlobInstcs::workPoolPtr->runSingle(task))) {
			ret = CLI_ERROR;
			delete task; task = Q_NULLPTR;
			continue;
		}

		if (TaskSendMessage::SM_SUCCESS == task->m_resultData.result) {
			qDebug() << CLI_PREFIX << "message has been sent"
			    << task->m_resultData.dmId;
			sendID.append(QString::number(task->m_resultData.dmId));
			/* Don't set ret to CLI_SUCCESS here. */
		} else {
			errmsg = "Error while sending message! ISDS says: "
			    + task->m_resultData.errInfo;
			qDebug() << CLI_PREFIX << errmsg << "Error code:"
			    << task->m_resultData.result;
			/* Return CLI_ERROR if an error occurs. */
			ret = CLI_ERROR;
		}
		delete task; task = Q_NULLPTR;
	}

	printDataToStdOut(sendID);
	return ret; /* Returns CLI_SUCCESS only if all tasks succeed. */
#undef STORE_RAW_INTO_DB
}

/* ========================================================================= */
static
cli_error checkLoginMandatoryAttributes(const QMap<QString, QVariant> &map,
    QString &errmsg)
/* ========================================================================= */
{
	errmsg = "checking of mandatory parameters for login...";

	if (!map.contains("username") ||
	    map.value("username").toString().isEmpty() ||
	    map.value("username").toString().length() != 6) {
		errmsg = "Username attribute missing or contains wrong value. "
		"Username must be exactly 6 chars long.";
		qDebug() << createErrorMsg(errmsg);
		return CLI_REQ_ATR_ERR;
	}

	return CLI_SUCCESS;
}

/* ========================================================================= */
static
cli_error checkSendMsgMandatoryAttributes(const QMap<QString, QVariant> &map,
    QString &errmsg)
/* ========================================================================= */
{
	errmsg = "checking of mandatory parameters for send message...";
	//qDebug() << CLI_PREFIX << errmsg;

	if (!map.contains("dbIDRecipient") ||
	    map.value("dbIDRecipient").toStringList().isEmpty()) {
		errmsg = "dbIDRecipient attribute missing or "
		    "contains empty databox id list";
		qDebug() << createErrorMsg(errmsg);
		return CLI_REQ_ATR_ERR;
	}
	QStringList dbIds = map.value("dbIDRecipient").toStringList();
	for (int i = 0; i < dbIds.count(); ++i) {
		if (dbIds.at(i).length() != 7) {
			errmsg = (QString("dbIDRecipient "
			    "attribute contains wrong value at "
			    "position %1! dbIDRecipient must be exactly 7 "
			    "chars long.").arg(i+1));
			qDebug() << createErrorMsg(errmsg);
			return CLI_ATR_VAL_ERR;
		}
	}
	if (!map.contains("dmAnnotation") ||
	    map.value("dmAnnotation").toString().isEmpty()) {
		errmsg = "dmAnnotation attribute missing or "
		    "contains empty string";
		qDebug() << createErrorMsg(errmsg);
		return CLI_REQ_ATR_ERR;
	}
	if (!map.contains("dmAttachment") ||
	    map.value("dmAttachment").toStringList().isEmpty()) {
		errmsg = "dmAttachment attribute missing or "
		    "contains empty file path list";
		qDebug() << createErrorMsg(errmsg);
		return CLI_REQ_ATR_ERR;
	}

	return CLI_SUCCESS;
}

/* ========================================================================= */
static
cli_error checkGetMsgListMandatoryAttributes(const QMap<QString, QVariant> &map,
    QString &errmsg)
/* ========================================================================= */
{
	errmsg = "checking of mandatory parameters for "
	    "get message list...";
	//qDebug() << CLI_PREFIX << errmsg;

	if (!map.contains("dmType") ||
	    map.value("dmType").toString().isEmpty()) {
		errmsg = "dmType attribute missing or "
		    "contains empty string";
		qDebug() << createErrorMsg(errmsg);
		return CLI_REQ_ATR_ERR;
	}
	QString dmType = map.value("dmType").toString();
	if (!(dmType == MT_SENT) && !(dmType == MT_RECEIVED) &&
	    !(dmType == MT_SENT_RECEIVED)) {
		errmsg = "dmType attribute "
		    "contains wrong value";
		qDebug() << createErrorMsg(errmsg);
		return CLI_ATR_VAL_ERR;
	}

	return CLI_SUCCESS;
}

/* ========================================================================= */
static
cli_error checkGetMsgMandatoryAttributes(const QMap<QString, QVariant> &map,
    QString &errmsg)
/* ========================================================================= */
{
	errmsg = "checking of mandatory parameters "
	    "for download message...";
	//qDebug() << CLI_PREFIX << errmsg;

	if (!map.contains("dmID") ||
	    map.value("dmID").toString().isEmpty()) {
		errmsg = "dmID attribute missing or "
		    "contains empty string";
		qDebug() << createErrorMsg(errmsg);
		return CLI_REQ_ATR_ERR;
	}
	if (!map.contains("dmType") ||
	    map.value("dmType").toString().isEmpty()) {
		errmsg = "dmType attribute missing or "
		    "contains empty string";
		qDebug() << createErrorMsg(errmsg);
		return CLI_REQ_ATR_ERR;
	}
	QString dmType = map.value("dmType").toString();
	if (!(dmType == MT_SENT) && !(dmType == MT_RECEIVED)) {
		errmsg = "dmType attribute "
		    "contains wrong value";
		qDebug() << createErrorMsg(errmsg);
		return CLI_ATR_VAL_ERR;
	}

	if (map.contains("download")) {
		QString download = map.value("download").toString();
		if (!(download == "no") && !(download == "yes")
		    && !(download == "ondemand")) {
			errmsg = "download attribute has wrong value "
			"(no,yes,ondemand is required)";
			qDebug() << createErrorMsg(errmsg);
			return CLI_ATR_VAL_ERR;
		}
	}

	return CLI_SUCCESS;
}

/* ========================================================================= */
static
cli_error checkDownloadDeliveryMandatoryAttributes(
    const QMap<QString, QVariant> &map, QString &errmsg)
/* ========================================================================= */
{
	errmsg = "checking of mandatory parameters "
	    "for download delivery info...";
	//qDebug() << CLI_PREFIX << errmsg;

	if (!map.contains("dmID") ||
	    map.value("dmID").toString().isEmpty()) {
		errmsg = "dmID attribute missing or "
		    "contains empty string";
		qDebug() << createErrorMsg(errmsg);
		return CLI_REQ_ATR_ERR;
	}

	if (map.contains("download")) {
		QString download = map.value("download").toString();
		if (!(download == "no") && !(download == "yes")
		    && !(download == "ondemand")) {
			errmsg = "download attribute has "
			    "wrong value (no,yes,ondemand is required)";
			qDebug() << createErrorMsg(errmsg);
			return CLI_ATR_VAL_ERR;
		}
	}

	return CLI_SUCCESS;
}

/* ========================================================================= */
static
cli_error checkFindDataboxMandatoryAttributes(
    const QMap<QString, QVariant> &map, QString &errmsg)
/* ========================================================================= */
{
	errmsg = "checking of mandatory parameters "
	    "for find databox...";

	bool isAttr = false;

	/* dbType */
	if (map.contains("dbType")) {
		QString dbType = map.value("dbType").toString();
		if (!(dbType == DB_OVM) && !(dbType == DB_PO)
		    && !(dbType == DB_PFO) && !(dbType == DB_FO)) {
			errmsg = "dbType attribute has wrong value";
			qDebug() << createErrorMsg(errmsg);
			return CLI_ATR_VAL_ERR;
		}
	} else {
		errmsg = "dbType attribute missing";
		qDebug() << createErrorMsg(errmsg);
		return CLI_REQ_ATR_ERR;
	}

	/* dbID */
	if (map.contains("dbID")) {
		if (map.value("dbID").toString().length() != 7) {
			errmsg = "dbID attribute contains wrong "
			    "value or is empty";
			qDebug() << createErrorMsg(errmsg);
			return CLI_REQ_ATR_ERR;
		}
		isAttr = true;
	}

	/* ic */
	if (map.contains("ic")) {
		if (map.value("ic").toString().length() > 8) {
			errmsg = "ic attribute contains wrong "
			    "value or is empty";
			qDebug() << createErrorMsg(errmsg);
			return CLI_REQ_ATR_ERR;
		}
		isAttr = true;
	}

	/* firmName */
	if (map.contains("firmName")) {
		if (map.value("firmName").toString().length() < 3) {
			errmsg = "firmName attribute is empty or contains "
			    "less than 3 characters";
			qDebug() << createErrorMsg(errmsg);
			return CLI_REQ_ATR_ERR;
		}
		isAttr = true;
	}

	/* pnFirstName */
	if (map.contains("pnFirstName")) {
		if (map.value("pnFirstName").toString().length() < 3) {
			errmsg = "pnFirstName attribute is empty or contains "
			    "less than 3 characters";
			qDebug() << createErrorMsg(errmsg);
			return CLI_REQ_ATR_ERR;
		}
		isAttr = true;
	}

	/* pnLastName */
	if (map.contains("pnLastName")) {
		if (map.value("pnLastName").toString().length() < 3) {
			errmsg = "pnLastName attribute is empty or contains "
			    "less than 3 characters";
			qDebug() << createErrorMsg(errmsg);
			return CLI_REQ_ATR_ERR;
		}
		isAttr = true;
	}

	/* adZipCode */
	if (map.contains("adZipCode")) {
		if (map.value("adZipCode").toString().length() != 5) {
			errmsg = "adZipCode attribute is empty or contains "
			    "wrong value";
			qDebug() << createErrorMsg(errmsg);
			return CLI_REQ_ATR_ERR;
		}
		isAttr = true;
	}

	if (!isAttr) {
		errmsg = "Not specified some attribute for this service";
		qDebug() << createErrorMsg(errmsg);
		return CLI_REQ_ATR_ERR;
	}

	return CLI_SUCCESS;
}

/* ========================================================================= */
static
cli_error checkMandatoryAttributes(const QString &service,
    const QMap<QString, QVariant> &map, QString &errmsg)
/* ========================================================================= */
{
	if (service == SER_LOGIN) {
		return checkLoginMandatoryAttributes(map, errmsg);
	} else if (service == SER_GET_MSG_LIST) {
		return checkGetMsgListMandatoryAttributes(map, errmsg);
	} else if (service == SER_SEND_MSG) {
		return checkSendMsgMandatoryAttributes(map, errmsg);
	} else if (service == SER_GET_MSG) {
		return checkGetMsgMandatoryAttributes(map, errmsg);
	} else if (service == SER_GET_DEL_INFO) {
		return checkDownloadDeliveryMandatoryAttributes(map, errmsg);
	} else if (service == SER_GET_MSG_IDS) {
		return checkGetMsgListMandatoryAttributes(map, errmsg);
	} else if (service == SER_FIND_DATABOX) {
		return checkFindDataboxMandatoryAttributes(map, errmsg);
	}
	return CLI_SUCCESS;
}

/* ========================================================================= */
static
cli_error parsePamamString(const QString &service, const QString &paramString,
    QMap<QString, QVariant> &map, QString &errmsg)
/* ========================================================================= */
{
	QString attribute = "";
	QString value = "";
	cli_error err = CLI_ERROR;
	bool newAttribute = true;
	bool newValue = false;
	bool special = false;
	int attrPosition = 0;

	for (int i = 0; i < paramString.length(); ++i) {
		if (paramString.at(i) == ',') {
			if (newValue) {
				value = value + paramString.at(i);
			} else {
				attrPosition++;
				//qDebug() << attribute << value;
				if (attribute.isEmpty()) {
					errmsg = QString("empty attribute "
					    "name on position '%1'").
					    arg(attrPosition);
					qDebug() << createErrorMsg(errmsg);
					return CLI_ATR_NAME_ERR;
				}
				if (value.isEmpty()) {
					errmsg = QString("empty attribute "
					    "value on position '%1' or value "
					    "is not between apostrophes").
					    arg(attrPosition);
					qDebug() << createErrorMsg(errmsg);
					return CLI_ATR_VAL_ERR;
				}

				if (checkAttributeIfExists(service,attribute)) {
					if (attribute == "dmAttachment") {
						map[attribute] =
						    parseAttachment(value);
					} else
					if (attribute == "dbIDRecipient") {
						map[attribute] =
						    parseDbIDRecipient(value);
					} else {
						map[attribute] = value;
					}
				} else {
					errmsg = QString("unknown attribute "
					    "name '%1'").arg(attribute);
					qDebug() << createErrorMsg(errmsg);
					return CLI_UNKNOWN_ATR;
				}
				attribute.clear();
				value.clear();
				newAttribute = true;
				newValue = false;
			}
		} else if (paramString.at(i) == '=') {
			if (newValue) {
				value = value + paramString.at(i);
			} else {
				newAttribute = false;
			}
		} else if (paramString.at(i) == '\'') {
			if (special) {
				value = value + paramString.at(i);
				special = false;
			} else {
				newValue = !newValue;
			}
		} else if (paramString.at(i) == '\\') {
			if (special) {
				value = value + paramString.at(i);
				special = false;
			} else {
				if (attribute == "dmAttachment" ||
				    attribute == "zfoFile" ||
				    attribute == "certificate") {
					value = value + paramString.at(i);
					special = false;
				} else {
					special = true;
				}
			}
		} else {
			if (newAttribute) {
				attribute = attribute + paramString.at(i);
			}
			if (newValue) {
				value = value + paramString.at(i);
			}
		}
	}

	// parse last token
	attrPosition++;
	if (attribute.isEmpty()) {
		errmsg = QString("empty attribute "
		    "name on position '%1'").arg(attrPosition);
		qDebug() << createErrorMsg(errmsg);
		return CLI_ATR_NAME_ERR;
	}
	if (value.isEmpty()) {
		errmsg = QString("empty attribute value on position '%1' "
		"or value is not between apostrophes").arg(attrPosition);
		qDebug() << createErrorMsg(errmsg);
		return CLI_ATR_VAL_ERR;
	}
	if (checkAttributeIfExists(service, attribute)) {
		if (attribute == "dmAttachment") {
			map[attribute] = parseAttachment(value);
		} else if (attribute == "dbIDRecipient") {
			map[attribute] = parseDbIDRecipient(value);
		} else {
			map[attribute] = value;
		}
	} else {
		errmsg = QString("unknown attribute name '%1'").arg(attribute);
		qDebug() << createErrorMsg(errmsg);
		return CLI_UNKNOWN_ATR;
	}

	err = checkMandatoryAttributes(service, map, errmsg);
	if (CLI_SUCCESS != err) {
		map.clear();
		return err;
	}

	// add service name to map
	map["service"] = service;

	return CLI_SUCCESS;
}

static
cli_error doService(const QString &service, QMap<QString, QVariant> &map,
    MessageDbSet *msgDbSet, bool needsISDS, QString &errmsg)
{

	if (service == SER_GET_MSG_LIST) {
		return getMsgList(map, msgDbSet, errmsg);
	} else if (service == SER_SEND_MSG) {
		return createAndSendMsg(map, msgDbSet, errmsg);
	} else if (service == SER_GET_MSG) {
		return getMsg(map, msgDbSet, needsISDS, errmsg);
	} else if (service == SER_GET_DEL_INFO) {
		return getDeliveryInfo(map, msgDbSet, needsISDS, errmsg);
	} else if (service == SER_GET_USER_INFO) {
		return getUserInfo(map, errmsg);
	} else if (service == SER_GET_OWNER_INFO) {
		return getOwnerInfo(map, errmsg);
	} else if (service == SER_CHECK_ATTACHMENT) {
		return checkAttachment(map, msgDbSet);
	} else if (service == SER_GET_MSG_IDS) {
		return getMsgIds(map, msgDbSet, errmsg);
	} else if (service == SER_FIND_DATABOX) {
		return findDatabox(map, errmsg);
	} else if (service == SER_EXPORT_MSG) {
		return exportMsg(map, msgDbSet, errmsg);
	} else if (service == SER_EXPORT_MSGS) {
		return exportMsgs(map, msgDbSet, errmsg);
	} else if (service == SER_IMPORT_MSG) {
		return importMsg(map, msgDbSet,  errmsg);
	} else if (service == SER_IMPORT_MSGS) {
		return importMsgs(map, msgDbSet, errmsg);
	}
	errmsg = "Unknown service name";
	return CLI_UNKNOWN_SER;
}

int runService(const QString &lParam, const QString &service,
    const QString &sParam)
{
	QMap<QString, QVariant> loginMap;
	QMap<QString, QVariant> serviceMap;
	cli_error cret = CLI_ERROR;
	bool needsISDS = true;
	QString errmsg = "Unknown error";
	int ret = EXIT_FAILURE;

	/* parse service parameter list */
	if (!(service.isNull()) && !(sParam.isNull())) {
		qDebug() << CLI_PREFIX << "Parsing of input string of service"
		    << service << ":" << sParam;
		cret = parsePamamString(service, sParam, serviceMap, errmsg);
		if (CLI_SUCCESS != cret) {
			qDebug() << CLI_PREFIX << "...error";
			printErrToStdErr(cret, errmsg);
			return ret;
		}
		qDebug() << CLI_PREFIX << "...done";
	}

	/* parse login parameter list */
	qDebug() << CLI_PREFIX << "Parsing of input string of \"login\" :"
	    << lParam;
	cret = parsePamamString(SER_LOGIN, lParam, loginMap, errmsg);
	if (CLI_SUCCESS != cret) {
		qDebug() << CLI_PREFIX << "...error";
		printErrToStdErr(cret, errmsg);
		return ret;
	}
	qDebug() << CLI_PREFIX << "...done";

	/* get username from login */
	const QString username = loginMap["username"].toString();
	const AcntId acntId = GlobInstcs::acntMapPtr->acntIdFromUsername(username);

	/* get message database set */
	MessageDbSet *msgDbSet = Q_NULLPTR;
	{
		enum AccountInteraction::AccessStatus status;
		QString dbDir, namesStr;
		msgDbSet = AccountInteraction::accessDbSet(acntId, status,
		    dbDir, namesStr);
	}
	if (Q_UNLIKELY(msgDbSet == Q_NULLPTR)) {
		errmsg = "Database doesn't exists for user " + username;
		qDebug() << CLI_PREFIX << errmsg;
		printErrToStdErr(CLI_DB_ERR, errmsg);
		return ret;
	}

	if (service == SER_GET_MSG) {
		MsgId msgId = msgDbSet->msgsMsgId(serviceMap["dmID"].toLongLong());
		if (Q_UNLIKELY(msgId.dmId() < 0)) {
			errmsg = "Message does not exist in the database "
			    "for user " + username;
			qDebug() << CLI_PREFIX << errmsg;
			/*
			 * Allow to download complete message from ISDS
			 * if not in the local database.
			 */
			//printErrToStdErr(CLI_DB_ERR, errmsg);
			//return ret;
		} else {
			MessageDb *messageDb =
			    msgDbSet->accessMessageDb(msgId.deliveryTime(), false);
			if (Q_UNLIKELY(messageDb == Q_NULLPTR)) {
				errmsg = "Database doesn't exists for user " + username;
				qDebug() << CLI_PREFIX << errmsg;
				printErrToStdErr(CLI_DB_ERR, errmsg);
				return ret;
			}
			if (serviceMap.contains("download")) {
				QString download =
				    serviceMap.value("download").toString();
				if (download == "no") {
					needsISDS = false;
				} else if (download == "ondemand") {
					needsISDS = !messageDb->isCompleteMessageInDb(
					    serviceMap["dmID"].toLongLong());
				}
			} else {
				needsISDS = !messageDb->isCompleteMessageInDb(
				    serviceMap["dmID"].toLongLong());
			}
		}
	}

	if (service == SER_GET_DEL_INFO) {
		MsgId msgId = msgDbSet->msgsMsgId(serviceMap["dmID"].toLongLong());
		if (msgId.dmId() < 0) {
			errmsg = "Message does not exist in the database "
			    "for user " + username;
			qDebug() << CLI_PREFIX << errmsg;
			printErrToStdErr(CLI_DB_ERR, errmsg);
			return ret;
		}
		MessageDb *messageDb =
		    msgDbSet->accessMessageDb(msgId.deliveryTime(), false);
		if (Q_UNLIKELY(messageDb == Q_NULLPTR)) {
			errmsg = "Database doesn't exists for user " + username;
			qDebug() << CLI_PREFIX << errmsg;
			printErrToStdErr(CLI_DB_ERR, errmsg);
			return ret;
		}

		if (serviceMap.contains("download")) {
			QString download =
			    serviceMap.value("download").toString();
			if (download == "no") {
				needsISDS = false;
			} else if (download == "ondemand") {
				needsISDS = messageDb->getDeliveryInfoRaw(
				    serviceMap["dmID"].toLongLong()).isEmpty();
			}
		} else {
			needsISDS = messageDb->getDeliveryInfoRaw(
			    serviceMap["dmID"].toLongLong()).isEmpty();
		}
	}

	if (service == SER_CHECK_ATTACHMENT || service == SER_GET_MSG_IDS) {
		needsISDS = false;
	}

	/* connect to ISDS and login into databox */
	if (needsISDS) {

		QString pwd;
		QString otp;

		if (loginMap.contains("password")) {
			pwd = loginMap["password"].toString();
		}
		if (loginMap.contains("otpcode")) {
			otp = loginMap["otpcode"].toString();
		}

		if (!GlobInstcs::isdsSessionsPtr->isConnectedToIsds(username) &&
		    !connectToIsdsCLI(*GlobInstcs::isdsSessionsPtr,
		        GlobInstcs::acntMapPtr->acntData(acntId), pwd, otp)) {
			errmsg = "Missing session for " + username +
			   " or connection fails";
			qDebug() << errmsg;
			printErrToStdErr(CLI_CONNECT_ERR, errmsg);
			return ret;
		}
		qDebug() << CLI_PREFIX << "User" << username
		    << "has been logged into databox";
		cret = CLI_SUCCESS;
	}

	/* do service */
	if (!service.isNull()) {
		serviceMap["username"] = username;
		cret = doService(service, serviceMap, msgDbSet, needsISDS,
		    errmsg);
	}

	if (CLI_SUCCESS == cret) {
		ret = EXIT_SUCCESS;
	} else {
		printErrToStdErr(cret, errmsg);
	}

	return ret;
}
