/*
 * Copyright (C) 2014-2026 CZ.NIC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#ifdef QT_WIDGETS_LIB
#  include <QFileDialog>
#endif /* QT_WIDGETS_LIB */
#include <QPrinter>
#include <QTextDocument>

#include "src/datovka_shared/compat/compiler.h" /* ignoreRetVal, macroStdMove */
#include "src/datovka_shared/html/html_export.h"
#include "src/datovka_shared/io/filesystem.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/settings/prefs.h"
#include "src/datovka_shared/utility/pdf_printer.h"
#include "src/global.h"
#include "src/io/export_file_name.h"
#include "src/io/exports.h"
#include "src/io/filesystem.h"
#include "src/io/message_db.h"
#include "src/io/message_db_set.h"

const QString &Exports::dmTypePrefix(MessageDb *msgDb, qint64 dmId)
{
	static const QString unspecified(QLatin1String("DZ"));
	static const QString received(QLatin1String("DDZ"));
	static const QString sent(QLatin1String("ODZ"));

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

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

QString Exports::attachmentSavePathWithFileName(const MessageDbSet &dbSet,
    const QString &targetPath, const QString &attachName,
    const QString &dbId, const QString &userName, const QString &accountName,
    const MsgId &msgId, bool prohibitDirSep)
{
	debugFuncCall();

	if (Q_UNLIKELY(userName.isEmpty())) {
		Q_ASSERT(0);
		return QString();
	}
	if (Q_UNLIKELY(msgId.dmId() < 0)) {
		Q_ASSERT(0);
		return QString();
	}

	MessageDb *messageDb = dbSet.constAccessMessageDb(msgId.deliveryTime());
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		return QString();
	}

	Isds::Envelope envelope = messageDb->getMessageEnvelope(msgId.dmId());

	QString fileName;
	{
		QString attachFilenameFmt;
		GlobInstcs::prefsPtr->strVal("file_system.format.filename.attachment",
		    attachFilenameFmt);

		fileName = fileSubPathFromFormat(
		    macroStdMove(attachFilenameFmt), prohibitDirSep,
		    msgId.dmId(), dbId, userName, accountName, attachName,
		    envelope, dmTypePrefix(messageDb, msgId.dmId()), QString());
	}
	if (fileName.isEmpty()) {
		return QString();
	}
	return targetPath + QDir::separator() + fileName;
}

/*!
 * @brief Write file content. Data must be non-empty.
 *
 * @param[in] fileName Path and name of target file.
 * @param[in] data File data.
 * @return True on success, false on any error.
 */
static
bool writeNonEmptyFile(const QString &fileName, const QByteArray &data)
{
	if (Q_UNLIKELY(fileName.isEmpty() || data.isEmpty())) {
		return false;
	}
	return (WF_SUCCESS == writeFile(fileName, data, true));
}

#define PDF_FONT_FILE_NAME ":/font/NotoSerif-Regular.ttf"

enum Exports::ExportError Exports::exportAs(const MessageDbSet &dbSet,
    enum ExportFileType fileType, const QString &targetPath,
    QString fileNameFormat, const QString &userName,
    const QString &accountName, const QString &dbId, const MsgId &msgId,
    QString &errStr)
{
	debugFuncCall();

	if (Q_UNLIKELY(userName.isEmpty())) {
		Q_ASSERT(0);
		return EXP_ERROR;
	}
	if (Q_UNLIKELY(msgId.dmId() < 0)) {
		Q_ASSERT(0);
		return EXP_ERROR;
	}

	QString fileTypeStr;
	QString fileSufix;
	QByteArray data;
	bool ret = false;
	QString msgID = QString::number(msgId.dmId());

	MessageDb *messageDb = dbSet.constAccessMessageDb(msgId.deliveryTime());
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		errStr = tr("Cannot access message database for username '%1'.")
		    .arg(userName);
		return EXP_DB_ERROR;
	}

	QString fileName;
	{
		QString dfltFileNameformat;

		/* What will we export? */
		switch (fileType) {
		case ZFO_MESSAGE:
			fileTypeStr = tr("message");
			fileSufix = ".zfo";
			dfltFileNameformat = DEFAULT_MESSAGE_FILENAME_FORMAT;
			data = messageDb->getCompleteMessageRaw(msgId.dmId());
			break;
		case ZFO_DELIVERY:
			fileTypeStr = tr("acceptance info");
			fileSufix = ".zfo";
			dfltFileNameformat = DEFAULT_ACCEPTANCE_INFO_FILENAME_FORMAT;
			data = messageDb->getDeliveryInfoRaw(msgId.dmId());
			break;
		case ZFO_DELIV_ATTACH:
			fileTypeStr = tr("acceptance info");
			fileSufix = ".zfo";
			dfltFileNameformat = DEFAULT_ATTACHMENT_ACCEPTANCE_FORMAT;
			data = messageDb->getDeliveryInfoRaw(msgId.dmId());
			break;
		case PDF_DELIVERY:
			fileTypeStr = tr("acceptance info");
			fileSufix = ".pdf";
			dfltFileNameformat = DEFAULT_ACCEPTANCE_INFO_FILENAME_FORMAT;
			data = messageDb->getDeliveryInfoRaw(msgId.dmId());
			break;
		case PDF_DELIV_ATTACH:
			fileTypeStr = tr("acceptance info");
			fileSufix = ".pdf";
			dfltFileNameformat = DEFAULT_ATTACHMENT_ACCEPTANCE_FORMAT;
			data = messageDb->getDeliveryInfoRaw(msgId.dmId());
			break;
		case PDF_ENVELOPE:
			fileTypeStr = tr("message envelope");
			fileSufix = ".pdf";
			dfltFileNameformat = DEFAULT_MESSAGE_FILENAME_FORMAT;
			data = "n/a";
			break;
		default:
			Q_ASSERT(0);
			errStr = tr("Export file type of message '%1' was not specified!")
			    .arg(msgID);
			return EXP_ERROR;
			break;
		}

		if (fileNameFormat.isEmpty()) {
			fileNameFormat = macroStdMove(dfltFileNameformat);
		}

		/* Is complete message in database? */
		if (Q_UNLIKELY(data.isEmpty())) {
			errStr = tr("Complete message '%1' missing!").arg(msgID);
			return EXP_NOT_MSG_DATA;
		}

		errStr = tr("Export of %1 '%2' to %3 was not successful!")
		    .arg(fileTypeStr).arg(msgID).arg(fileSufix);

		const bool prohibitDirSep = false;

		const Isds::Envelope envelope = messageDb->getMessageEnvelope(msgId.dmId());
		/*
		 * Create new file name with format string. Allow subdirectories
		 * if the user should not be asked after location.
		 */
		fileName = fileSubPathFromFormat(macroStdMove(fileNameFormat),
		    prohibitDirSep, msgId.dmId(), dbId, userName, accountName,
		    QString(), envelope,
		    dmTypePrefix(messageDb, msgId.dmId()), fileSufix);
	}
	fileName = targetPath + QDir::separator() + fileName;

	if (Q_UNLIKELY(fileName.isEmpty())) {
		Q_ASSERT(0);
		return EXP_ERROR;
	}

	createDirStructureRecursive(fileName);

	if (Q_UNLIKELY(fileName.isEmpty())) {
		Q_ASSERT(0);
		return EXP_ERROR;
	}

	// save file to disk
	switch (fileType) {
	case ZFO_DELIV_ATTACH:
	case ZFO_DELIVERY:
	case ZFO_MESSAGE:
		ret = writeNonEmptyFile(fileName, data);
		if (Q_UNLIKELY(!ret)) {
			logErrorNL("Couldn't write file '%s'.", fileName.toUtf8().constData());
		}
		break;
	case PDF_DELIVERY:
	case PDF_DELIV_ATTACH:
		ret = Utility::printPDFWithFont(fileName,
		    Html::Export::htmlInfoPdf(Html::Export::TYPE_DELIVERY_INFO,
		    messageDb->getMessageEnvelope(msgId.dmId()),
		    QList<Isds::Document>(),
		    messageDb->messageAuthorJsonStr(msgId.dmId())),
		    Utility::HTML_TEXT, PDF_FONT_FILE_NAME);
		break;
	case PDF_ENVELOPE:
		ret = Utility::printPDFWithFont(fileName,
		    Html::Export::htmlInfoPdf(Html::Export::TYPE_ENVELOPE_INFO,
		    messageDb->getMessageEnvelope(msgId.dmId()),
		    messageDb->getMessageAttachments(msgId.dmId()),
		    messageDb->messageAuthorJsonStr(msgId.dmId())),
		    Utility::HTML_TEXT, PDF_FONT_FILE_NAME);
		break;
	default:
		Q_ASSERT(0);
		return EXP_ERROR;
		break;
	}

	/* Test if file was exported. TODO - show error dialogue? */
	ret = QFileInfo(fileName).exists();

	if (ret) {
		errStr = tr("Export of %1 '%2' to %3 was successful.")
		    .arg(fileTypeStr).arg(msgID).arg(fileSufix);
		return EXP_SUCCESS;
	} else {
		errStr = tr("Export of %1 '%2' to %3 wasn't successful. Cannot write the file. File name may be too long.")
		    .arg(fileTypeStr).arg(msgID).arg(fileSufix);
		return EXP_WRITE_FILE_ERROR;
	}
}

#ifdef QT_WIDGETS_LIB
enum Exports::ExportError Exports::exportAsGUI(QWidget *parent,
    const MessageDbSet &dbSet, enum ExportFileType fileType,
    const QString &targetPath, const QString &attachFileName,
    const QString &userName, const QString &accountName, const QString &dbId,
    const MsgId &msgId, bool askLocation, QString &lastPath, QString &errStr)
{
	debugFuncCall();

	if (Q_UNLIKELY(userName.isEmpty())) {
		Q_ASSERT(0);
		return EXP_ERROR;
	}
	if (Q_UNLIKELY(msgId.dmId() < 0)) {
		Q_ASSERT(0);
		return EXP_ERROR;
	}

	QString fileTypeStr;
	QString fileSufix;
	QByteArray data;
	bool ret = false;
	QString msgID = QString::number(msgId.dmId());

	MessageDb *messageDb = dbSet.constAccessMessageDb(msgId.deliveryTime());
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		errStr = tr("Cannot access message database for username '%1'.")
		    .arg(userName);
		return EXP_DB_ERROR;
	}

	QString fileName;
	{
		QString fileNameFormat;

		/* What will we export? */
		switch (fileType) {
		case ZFO_MESSAGE:
			fileTypeStr = tr("message");
			fileSufix = ".zfo";
			GlobInstcs::prefsPtr->strVal(
			    "file_system.format.filename.message", fileNameFormat);
			data = messageDb->getCompleteMessageRaw(msgId.dmId());
			break;
		case ZFO_DELIVERY:
			fileTypeStr = tr("acceptance info");
			fileSufix = ".zfo";
			GlobInstcs::prefsPtr->strVal(
			    "file_system.format.filename.acceptance_info", fileNameFormat);
			data = messageDb->getDeliveryInfoRaw(msgId.dmId());
			break;
		case ZFO_DELIV_ATTACH:
			fileTypeStr = tr("acceptance info");
			fileSufix = ".zfo";
			GlobInstcs::prefsPtr->strVal(
			    "file_system.format.filename.every_attachment_acceptance_info",
			    fileNameFormat);
			data = messageDb->getDeliveryInfoRaw(msgId.dmId());
			break;
		case PDF_DELIVERY:
			fileTypeStr = tr("acceptance info");
			fileSufix = ".pdf";
			GlobInstcs::prefsPtr->strVal(
			    "file_system.format.filename.acceptance_info", fileNameFormat);
			data = messageDb->getDeliveryInfoRaw(msgId.dmId());
			break;
		case PDF_DELIV_ATTACH:
			fileTypeStr = tr("acceptance info");
			fileSufix = ".pdf";
			GlobInstcs::prefsPtr->strVal(
			    "file_system.format.filename.every_attachment_acceptance_info",
			    fileNameFormat);
			data = messageDb->getDeliveryInfoRaw(msgId.dmId());
			break;
		case PDF_ENVELOPE:
			fileTypeStr = tr("message envelope");
			fileSufix = ".pdf";
			GlobInstcs::prefsPtr->strVal(
			    "file_system.format.filename.message", fileNameFormat);
			data = "n/a";
			break;
		default:
			Q_ASSERT(0);
			errStr = tr("Export file type of message '%1' was not specified!")
			    .arg(msgID);
			return EXP_ERROR;
			break;
		}

		/* Is complete message in database? */
		if (Q_UNLIKELY(data.isEmpty())) {
			errStr = tr("Complete message '%1' missing!").arg(msgID);
			return EXP_NOT_MSG_DATA;
		}

		errStr = tr("Export of %1 '%2' to %3 was not successful!")
		    .arg(fileTypeStr).arg(msgID).arg(fileSufix);

		const bool prohibitDirSep = askLocation;

		const Isds::Envelope envelope = messageDb->getMessageEnvelope(msgId.dmId());
		/*
		 * Create new file name with format string. Allow subdirectories
		 * if the user should not be asked after location.
		 */
		fileName = fileSubPathFromFormat(macroStdMove(fileNameFormat),
		    prohibitDirSep, msgId.dmId(), dbId, userName, accountName,
		    attachFileName, envelope,
		    dmTypePrefix(messageDb, msgId.dmId()), fileSufix);
	}
	fileName = targetPath + QDir::separator() + fileName;

	if (Q_UNLIKELY(fileName.isEmpty())) {
		Q_ASSERT(0);
		return EXP_ERROR;
	}

	/* Ask for a new location. */
	if (askLocation) {
		fileName = QFileDialog::getSaveFileName(parent,
		    tr("Save %1 as file (*%2)").arg(fileTypeStr).arg(fileSufix),
		    fileName, tr("File (*%1)").arg(fileSufix));
		if (fileName.isEmpty()) {
			/* Export has been cancelled. */
			return EXP_CANCELED;
		}
	} else {
		createDirStructureRecursive(fileName);
	}

	if (Q_UNLIKELY(fileName.isEmpty())) {
		Q_ASSERT(0);
		return EXP_ERROR;
	}

	// save file to disk
	switch (fileType) {
	case ZFO_DELIV_ATTACH:
	case ZFO_DELIVERY:
	case ZFO_MESSAGE:
		ret = writeNonEmptyFile(fileName, data);
		if (Q_UNLIKELY(!ret)) {
			logErrorNL("Couldn't write file '%s'.", fileName.toUtf8().constData());
		}
		break;
	case PDF_DELIVERY:
	case PDF_DELIV_ATTACH:
		ret = Utility::printPDFWithFont(fileName,
		    Html::Export::htmlInfoPdf(Html::Export::TYPE_DELIVERY_INFO,
		    messageDb->getMessageEnvelope(msgId.dmId()),
		    QList<Isds::Document>(),
		    messageDb->messageAuthorJsonStr(msgId.dmId())),
		    Utility::HTML_TEXT, PDF_FONT_FILE_NAME);
		break;
	case PDF_ENVELOPE:
		ret = Utility::printPDFWithFont(fileName,
		    Html::Export::htmlInfoPdf(Html::Export::TYPE_ENVELOPE_INFO,
		    messageDb->getMessageEnvelope(msgId.dmId()),
		    messageDb->getMessageAttachments(msgId.dmId()),
		    messageDb->messageAuthorJsonStr(msgId.dmId())),
		    Utility::HTML_TEXT, PDF_FONT_FILE_NAME);
		break;
	default:
		Q_ASSERT(0);
		return EXP_ERROR;
		break;
	}

	/* Test if file was exported. TODO - show error dialogue? */
	ret = QFileInfo(fileName).exists();

	if (ret) {
		// remember last path
		lastPath = QFileInfo(fileName).absoluteDir().absolutePath();
		errStr = tr("Export of %1 '%2' to %3 was successful.")
		    .arg(fileTypeStr).arg(msgID).arg(fileSufix);
		return EXP_SUCCESS;
	} else {
		errStr = tr("Export of %1 '%2' to %3 wasn't successful. Cannot write the file. File name may be too long.")
		    .arg(fileTypeStr).arg(msgID).arg(fileSufix);
		return EXP_WRITE_FILE_ERROR;
	}
}
#endif /* QT_WIDGETS_LIB */

/*!
 * @brief Split the file name into the base name and full suffix.
 *
 * @param[in] fileName File name.
 * @return List containing the base name and the suffix if some supplied.
 */
static
QStringList splitSuffix(const QString &fileName)
{
	int dotPos = fileName.indexOf(QChar('.'));
	if (dotPos < 0) {
		return QStringList() << fileName;
	}

	return QStringList() << fileName.left(dotPos)
	    << fileName.right(fileName.size() - dotPos - 1);
}

/*!
 * @brief Construct a file name distinct from the supplied set content.
 *     Leaves suffix intact.
 *
 * @param[in] fileName Original file name.
 * @param[in] fileNames Set of already used names.
 * @param[in] caseSensitive Set to false if the letter case should be ignored.
 * @return New file name distinct form those in the set.
 */
static
QString setNonconflictingFileName(const QString &fileName,
    const QSet<QString> &fileNames, bool caseSensitive)
{
	if (Q_UNLIKELY(fileName.isEmpty())) {
		return QString();
	}

	if (fileNames.contains(caseSensitive ? fileName : fileName.toLower())) {

		int fileCnt = 0;

		QString baseName;
		QString suffix;
		{
			QStringList nameParts = splitSuffix(fileName);
			baseName = nameParts.at(0);
			if (nameParts.size() > 1) {
				suffix = nameParts.at(1);
			}
		}

		QString newFileName;
		do {
			++fileCnt;
			newFileName = baseName + QLatin1String("_") + QString::number(fileCnt);
			if (!suffix.isEmpty()) {
				newFileName += QChar('.') + suffix;
			}
		} while (fileNames.contains(caseSensitive ? newFileName : newFileName.toLower()));

		return newFileName;
	}

	return fileName;
}

/*!
 * @brief Different attachments of same message can have same file names. The
 *     function alters the names in order to make them distinct form each other.
 *
 * @param[in,out] documents Document list to be altered.
 * @param[in]     caseSensitive Set to false if the letter case should be ignored.
 */
static
void makeDocumentNamesDistinct(QList<Isds::Document> &documents,
    bool caseSensitive)
{
	QSet<QString> usedNames;

	QList<Isds::Document>::iterator it;
	for (it = documents.begin(); it != documents.end(); ++it) {
		QString fileName = setNonconflictingFileName(it->fileDescr(),
		    usedNames, caseSensitive);
		usedNames.insert(caseSensitive ? fileName : fileName.toLower());
		if (fileName != it->fileDescr()) {
			/* Replace document name. */
			it->setFileDescr(macroStdMove(fileName));
		}
	}
}

enum Exports::ExportError Exports::exportEnvAndAttachments(
    const MessageDbSet &dbSet, const QString &targetPath,
    const QString &userName, const QString &accountName, const QString &dbId,
    const MsgId &msgId, QString &errStr)
{
	debugFuncCall();

	if (Q_UNLIKELY(userName.isEmpty())) {
		Q_ASSERT(0);
		return EXP_ERROR;
	}
	if (Q_UNLIKELY(msgId.dmId() < 0)) {
		Q_ASSERT(0);
		return EXP_ERROR;
	}

	QString newTargetPath;
	QDir dir(targetPath);
	QString msgID = QString::number(msgId.dmId());
	bool attachWriteSuccess = true;

	// create new folder with message id
	dir.mkdir(QString::number(msgId.dmId()));
	newTargetPath = targetPath + QDir::separator() +
	    QString::number(msgId.dmId());

	MessageDb *messageDb = dbSet.constAccessMessageDb(msgId.deliveryTime());
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		errStr = tr("Cannot access message database for username '%1'.")
		    .arg(userName);
		return EXP_DB_ERROR;
	}

	/* Get list of attachment documents. */
	QList<Isds::Document> attachList =
	    messageDb->getMessageAttachments(msgId.dmId());
	if (Q_UNLIKELY(attachList.isEmpty())) {
		errStr = tr("Complete message '%1' is missing.").arg(msgID);
		return EXP_NOT_MSG_DATA;
	}

	/*
	 * Treat all FS as case-insensitive. This will make the life a little
	 * bit easier. Also for the user, as they may copy the generated files
	 * without any worries.
	 */
	makeDocumentNamesDistinct(attachList, false);

	/* Save attachments to target directory. */
	foreach (const Isds::Document &attach, attachList) {
		QString attName(attach.fileDescr());
		if (Q_UNLIKELY(attName.isEmpty())) {
			Q_ASSERT(0);
			errStr = tr("Some attachments of message '%1' couldn't be stored.")
			    .arg(msgID);
			attachWriteSuccess = false;
			continue;
		}

		/* Create complete file path according to format string. */
		attName = attachmentSavePathWithFileName(dbSet, newTargetPath,
		    attName, dbId, userName, accountName, msgId, false);

		/* Recursively create subdirectories. */
		createDirStructureRecursive(attName);

		const QByteArray &data(attach.binaryContent());

		/* Write file. */
		if (WF_SUCCESS != writeFile(attName, data, true)) {
			errStr = tr("Some files of message '%1' have not been written to storage.")
			    .arg(msgID);
			attachWriteSuccess = false;
			continue;
		}
	}

	Isds::Envelope envelope = messageDb->getMessageEnvelope(msgId.dmId());

	QString fileName;
	{
		QString msgFilenameFmt;
		GlobInstcs::prefsPtr->strVal("file_system.format.filename.message",
		    msgFilenameFmt);

		/* Create new file name according to the format string. */
		fileName = fileSubPathFromFormat(macroStdMove(msgFilenameFmt),
		    false, msgId.dmId(), dbId, userName, accountName, QString(),
		    envelope, dmTypePrefix(messageDb, msgId.dmId()), QString(".pdf"));
	}
	fileName = newTargetPath + QDir::separator() + fileName;
	if (Q_UNLIKELY(fileName.isEmpty())) {
		errStr = tr("Export of message envelope '%1' to PDF was not successful.")
		    .arg(msgID);
		return EXP_ERROR;
	}

	/* Recursively create subdirectories. */
	createDirStructureRecursive(fileName);

	/* Write file. */
	if (Utility::printPDFWithFont(fileName,
	    Html::Export::htmlInfoPdf(Html::Export::TYPE_ENVELOPE_INFO,
	    envelope,
	    messageDb->getMessageAttachments(msgId.dmId()),
	    messageDb->messageAuthorJsonStr(msgId.dmId())),
	    Utility::HTML_TEXT, PDF_FONT_FILE_NAME) && attachWriteSuccess) {
		errStr = tr("All attachments of message '%1' and the envelope PDF have been successfully saved to target directory.").arg(msgID);
		return EXP_SUCCESS;
	} else {
		errStr = tr("The envelope PDF of message '%1' hasn't been saved.").arg(msgID);
		return EXP_WRITE_FILE_ERROR;
	}
}

#ifdef QT_WIDGETS_LIB
enum Exports::ExportError Exports::saveAttachmentsWithExports(
    const MessageDbSet &dbSet, const QString &targetPath,
    const QString &userName, const QString &accountName, const QString &dbId,
    const MsgId &msgId, QString &errStr)
{
	debugFuncCall();

	QString lastPath;
	QString msgID = QString::number(msgId.dmId());
	bool attachWriteSuccess = true;

	MessageDb *messageDb = dbSet.constAccessMessageDb(msgId.deliveryTime());
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		errStr = tr("Cannot access message database for username '%1'.")
		    .arg(userName);
		return EXP_DB_ERROR;
	}

	/* Get list of attachment documents. */
	QList<Isds::Document> attachList =
	    messageDb->getMessageAttachments(msgId.dmId());
	if (Q_UNLIKELY(attachList.isEmpty())) {
		errStr = tr("Complete message '%1' is missing.").arg(msgID);
		return EXP_NOT_MSG_DATA;
	}

	/*
	 * Treat all FS as case-insensitive. This will make the life a little
	 * bit easier. Also for the user, as they may copy the generated files
	 * without any worries.
	 */
	makeDocumentNamesDistinct(attachList, false);

	bool exportMsgZfo = false;
	ignoreRetVal(GlobInstcs::prefsPtr->boolVal(
	    "action.save_all_attachments.tie.action.export_message_zfo",
	    exportMsgZfo));
	bool exportAccInfoZfo = false;
	ignoreRetVal(GlobInstcs::prefsPtr->boolVal(
	    "action.save_all_attachments.tie.action.export_acceptance_info_zfo",
	    exportAccInfoZfo));
	bool exportEnvelPdf = false;
	ignoreRetVal(GlobInstcs::prefsPtr->boolVal(
	    "action.save_all_attachments.tie.action.export_envelope_pdf",
	    exportEnvelPdf));
	bool exportAccInfoPdf = false;
	ignoreRetVal(GlobInstcs::prefsPtr->boolVal(
	    "action.save_all_attachments.tie.action.export_acceptance_info_pdf",
	    exportAccInfoPdf));
	bool accInfoForEveryFile = false;
	ignoreRetVal(GlobInstcs::prefsPtr->boolVal(
	    "action.save_all_attachments.every_attachment.export_acceptance_info",
	    accInfoForEveryFile));

	/* Save attachments to target directory. */
	foreach (const Isds::Document &attach, attachList) {
		QString attName(attach.fileDescr());
		if (Q_UNLIKELY(attName.isEmpty())) {
			Q_ASSERT(0);
			errStr = tr("Some attachments of message '%1' couldn't be stored.")
			    .arg(msgID);
			attachWriteSuccess = false;
			continue;
		}

		/* Create complete file path according to format string. */
		attName = attachmentSavePathWithFileName(dbSet, targetPath,
		    attName, dbId, userName, accountName, msgId, false);

		/* Recursively create subdirectories. */
		createDirStructureRecursive(attName);

		const QByteArray &data(attach.binaryContent());

		/* Write file. */
		if (WF_SUCCESS != writeFile(attName, data, true)) {
			errStr = tr("Some files of message '%1' have not been written to storage.")
			    .arg(msgID);
			attachWriteSuccess = false;
			continue;
		}

		if (accInfoForEveryFile) {
			if (exportAccInfoZfo) {
				exportAsGUI(Q_NULLPTR, dbSet, Exports::ZFO_DELIV_ATTACH,
				    targetPath, attach.fileDescr(), userName,
				    accountName, dbId, msgId, false, lastPath,
				    errStr);
			}
			if (exportAccInfoPdf) {
				exportAsGUI(Q_NULLPTR, dbSet, Exports::PDF_DELIV_ATTACH,
				    targetPath, attach.fileDescr(), userName,
				    accountName, dbId, msgId, false, lastPath,
				    errStr);
			}
		}
	}

	if (exportMsgZfo) {
		exportAsGUI(Q_NULLPTR, dbSet, Exports::ZFO_MESSAGE,
		    targetPath, QString(), userName, accountName, dbId, msgId,
		    false, lastPath, errStr);
	}

	if (exportEnvelPdf) {
		exportAsGUI(Q_NULLPTR, dbSet, Exports::PDF_ENVELOPE,
		    targetPath, QString(), userName, accountName, dbId, msgId,
		    false, lastPath, errStr);
	}

	if (!accInfoForEveryFile) {
		if (exportAccInfoZfo) {
			exportAsGUI(Q_NULLPTR, dbSet, Exports::ZFO_DELIVERY,
			    targetPath, QString(), userName, accountName, dbId,
			    msgId, false, lastPath, errStr);
		}
		if (exportAccInfoPdf) {
			exportAsGUI(Q_NULLPTR, dbSet, Exports::PDF_DELIVERY,
			    targetPath, QString(), userName, accountName, dbId,
			    msgId, false, lastPath, errStr);
		}
	}

	if (attachWriteSuccess) {
		errStr = tr("All attachments of message '%1' have been successfully saved to target directory.")
		    .arg(msgID);
		return EXP_SUCCESS;
	} else {
		errStr = tr("Some attachments of message '%1' haven't been saved.")
		    .arg(msgID);
		return EXP_WRITE_FILE_ERROR;
	}
}
#endif /* QT_WIDGETS_LIB */

bool Exports::printPDF(const QString &fileName, const QString &data)
{
	if (data.isEmpty() || fileName.isEmpty()) {
		return false;
	}

	QTextDocument doc;
	doc.setHtml(data);
	QPrinter printer;
	printer.setOutputFileName(fileName);
	printer.setOutputFormat(QPrinter::PdfFormat);
	doc.print(&printer);

	return true;
}
