/*
 * Copyright (C) 2014-2015 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 <QAbstractTableModel>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QFont>
#include <QIcon>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QList>
#include <QMap>
#include <QModelIndex>
#include <QObject>
#include <QPair>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QSqlQueryModel>
#include <QSslCertificate>
#include <QString>
#include <QTimeZone>
#include <QVariant>
#include <QVector>

#include "message_db.h"
#include "src/common.h"
#include "src/crypto/crypto_threadsafe.h"
#include "src/io/db_tables.h"
#include "src/io/dbs.h"
#include "src/log/log.h"


/*
 * Joined tables messages, supplementary_message_data and raw_message_data.
 * Entry 'is_downloaded' is generated by the database rather than directly read
 * from a table.
 */
const QVector<QString> MessageDb::receivedItemIds = {"dmID", "dmAnnotation",
    "dmSender", "dmDeliveryTime", "dmAcceptanceTime", "read_locally",
    "is_downloaded", "process_status"};


const QVector<QString> MessageDb::sentItemIds = {"dmID", "dmAnnotation",
    "dmRecipient", "dmDeliveryTime", "dmAcceptanceTime", "dmMessageStatus",
    "is_downloaded"};


const QVector<QString> MessageDb::msgAttribs2 = {"dmSenderIdent",
    "dmSenderRefNumber", "dmRecipientIdent", "dmRecipientRefNumber",
    "dmToHands", "dmLegalTitleLaw", "dmLegalTitleYear", "dmLegalTitleSect",
    "dmLegalTitlePar", "dmLegalTitlePoint"};


const QVector<QString> MessageDb::msgStatus = {"dmDeliveryTime",
    "dmAcceptanceTime", "dmMessageStatus"};


const QVector<QString> MessageDb::fileItemIds = {"id", "message_id",
    "dmEncodedContent", "_dmFileDescr", "LENGTH(dmEncodedContent)"};


/* ========================================================================= */
/*
 * Constructor.
 */
DbMsgsTblModel::DbMsgsTblModel(QObject *parent)
/* ========================================================================= */
    : QSqlQueryModel(parent),
    m_overriddenRL(),
    m_overriddenAD(),
    m_overriddenPS()
{
}


#define READLOC_COL 5 /* Read locally. */
#define ATTDOWN_COL 6 /* Attachment downloaded. */
#define PROCSNG_COL 7 /* Processing state. */


/* ========================================================================= */
/*
 * Used for data conversion on display.
 */
QVariant DbMsgsTblModel::data(const QModelIndex &index, int role) const
/* ========================================================================= */
{
	int dataType;

	switch (role) {
	case Qt::DisplayRole:
		dataType = QSqlQueryModel::headerData(index.column(),
		    Qt::Horizontal, ROLE_MSGS_DB_ENTRY_TYPE).toInt();
		switch (dataType) {
		case DB_DATETIME:
			/* Convert date on display. */
			return dateTimeStrFromDbFormat(
			    QSqlQueryModel::data(index, role).toString(),
			    dateTimeDisplayFormat);
			break;
		case DB_BOOL_READ_LOCALLY: /* 'read locally' */
		case DB_BOOL_ATTACHMENT_DOWNLOADED: /* 'is downloaded' */
		case DB_INT_PROCESSING_STATE: /* 'process status' */
			/* Hide text. */
			return QVariant();
			break;
		default:
			return QSqlQueryModel::data(index, role);
			break;
		}
		break;

	case Qt::DecorationRole:
		dataType = QSqlQueryModel::headerData(index.column(),
		    Qt::Horizontal, ROLE_MSGS_DB_ENTRY_TYPE).toInt();
		switch (dataType) {
		case DB_BOOL_READ_LOCALLY:
			{
				/* Show icon for 'read locally'. */
				int dmId = QSqlQueryModel::data(
				    index.sibling(index.row(), 0),
				    Qt::DisplayRole).toInt();
				if (m_overriddenRL.value(dmId, false) ||
				    QSqlQueryModel::data(index).toBool()) {
					return QIcon(
					    ICON_16x16_PATH "grey.png");
				} else {
					return QIcon(
					    ICON_16x16_PATH "green.png");
				}
			}
			break;
		case DB_BOOL_ATTACHMENT_DOWNLOADED:
			{
				/* Show icon for 'is downloaded'. */
				int dmId = QSqlQueryModel::data(
				    index.sibling(index.row(), 0),
				    Qt::DisplayRole).toInt();
				if (m_overriddenAD.value(dmId, false) ||
				    QSqlQueryModel::data(index).toBool()) {
					return QIcon(
					    ICON_14x14_PATH "attachment.png");
				} else {
					return QVariant(); /* No icon. */
				}
			}
			break;
		case DB_INT_PROCESSING_STATE:
			{
				/* Show icon for 'process status'. */
				int dmId = QSqlQueryModel::data(
				    index.sibling(index.row(), 0),
				    Qt::DisplayRole).toInt();
				switch (m_overriddenPS.value(dmId,
				            QSqlQueryModel::data(
				                index).toInt())) {
				case UNSETTLED:
					return QIcon(
					    ICON_16x16_PATH "red.png");
					break;
				case IN_PROGRESS:
					return QIcon(
					    ICON_16x16_PATH "yellow.png");
					break;
				case SETTLED:
					return QIcon(
					    ICON_16x16_PATH "grey.png");
					break;
				default:
					Q_ASSERT(0);
					break;
				}
				return QVariant();
			}
			break;

		default:
			return QSqlQueryModel::data(index, role);
			break;
		}
		break;

	case Qt::FontRole:
		if (DB_BOOL_READ_LOCALLY == QSqlQueryModel::headerData(
		        READLOC_COL, Qt::Horizontal,
		        ROLE_MSGS_DB_ENTRY_TYPE).toInt()) {
			/* In read messages. */
			int dmId = QSqlQueryModel::data(
			    index.sibling(index.row(), 0),
			    Qt::DisplayRole).toInt();
			if ((!QSqlQueryModel::data(index.sibling(index.row(),
			         READLOC_COL)).toBool()) &&
			    (!m_overriddenRL.value(dmId, false))) {
				/* Unread messages are shown bold. */
				QFont boldFont;
				boldFont.setBold(true);
				return boldFont;
			}
		}

		return QSqlQueryModel::data(index, role);
		break;

	case ROLE_MSGS_DB_PROXYSORT:
		dataType = QSqlQueryModel::headerData(index.column(),
		    Qt::Horizontal, ROLE_MSGS_DB_ENTRY_TYPE).toInt();
		switch (dataType) {
		case DB_BOOLEAN:
			{
				qint64 id;
				id = QSqlQueryModel::data(index,
				    Qt::DisplayRole).toBool() ? 1 : 0;
				id = id << 48;
				id += QSqlQueryModel::data(
				    index.sibling(index.row(), 0),
				    Qt::DisplayRole).toInt();
				return id;
			}
			break;
		case DB_BOOL_READ_LOCALLY:
			{
				int dmId = QSqlQueryModel::data(
				    index.sibling(index.row(), 0),
				    Qt::DisplayRole).toInt();
				qint64 id;
				id = (m_overriddenRL.value(dmId, false) ||
				    QSqlQueryModel::data(index,
				        Qt::DisplayRole).toBool()) ? 1 : 0;
				id = id << 48;
				id += QSqlQueryModel::data(
				    index.sibling(index.row(), 0),
				    Qt::DisplayRole).toInt();
				return id;
			}
		case DB_BOOL_ATTACHMENT_DOWNLOADED:
			{
				int dmId = QSqlQueryModel::data(
				    index.sibling(index.row(), 0),
				    Qt::DisplayRole).toInt();
				qint64 id;
				id = (m_overriddenAD.value(dmId, false) ||
				    QSqlQueryModel::data(index,
				        Qt::DisplayRole).toBool()) ? 1 : 0;
				id = id << 48;
				id += QSqlQueryModel::data(
				    index.sibling(index.row(), 0),
				    Qt::DisplayRole).toInt();
				return id;
			}
			break;
		case DB_INT_PROCESSING_STATE:
			{
				int dmId = QSqlQueryModel::data(
				    index.sibling(index.row(), 0),
				    Qt::DisplayRole).toInt();
				qint64 id;
				id = m_overriddenPS.value(dmId,
				    QSqlQueryModel::data(index,
				        Qt::DisplayRole).toInt());
				id = id << 48;
				id += QSqlQueryModel::data(
				    index.sibling(index.row(), 0),
				    Qt::DisplayRole).toInt();
				return id;
			}
			break;
		case DB_TEXT: /* Ignore case for sorting. */
			return QSqlQueryModel::data(index,
			    Qt::DisplayRole).toString().toLower();
			break;
		default:
			return QSqlQueryModel::data(index, Qt::DisplayRole);
			break;
		}
		break;

	default:
		return QSqlQueryModel::data(index, role);
		break;
	}
}


/* ========================================================================= */
/*
 * Convert viewed header data.
 */
QVariant DbMsgsTblModel::headerData(int section, Qt::Orientation orientation,
    int role) const
/* ========================================================================= */
{
	int dataType;

	switch (role) {
	case Qt::DisplayRole:
		if ((section < READLOC_COL) || (section > PROCSNG_COL)) {
			return QSqlQueryModel::headerData(section, orientation,
			    role);
		}

		dataType = QSqlQueryModel::headerData(section, Qt::Horizontal,
		    ROLE_MSGS_DB_ENTRY_TYPE).toInt();
		switch (dataType) {
		case DB_BOOL_READ_LOCALLY: /* 'read locally' */
		case DB_BOOL_ATTACHMENT_DOWNLOADED: /* 'is downloaded' */
		case DB_INT_PROCESSING_STATE: /* 'process status' */
			/* Hide text. */
			return QVariant();
			break;
		default:
			return QSqlQueryModel::headerData(section, orientation,
			    role);
			break;
		}
		break;

	case Qt::DecorationRole:
		if ((section < READLOC_COL) || (section > PROCSNG_COL)) {
			return QSqlQueryModel::headerData(section, orientation,
			    role);
		}

		dataType = QSqlQueryModel::headerData(section, Qt::Horizontal,
		    ROLE_MSGS_DB_ENTRY_TYPE).toInt();
		switch (dataType) {
		case DB_BOOL_READ_LOCALLY:
			/* Show icon for 'read locally'. */
			return QIcon(ICON_16x16_PATH "readcol.png");
			break;
		case DB_BOOL_ATTACHMENT_DOWNLOADED:
			/* Show icon for 'is downloaded'. */
			return QIcon(ICON_14x14_PATH "attachment.png");
			break;
		case DB_INT_PROCESSING_STATE:
			return QIcon(ICON_16x16_PATH "flag.png");
			break;
		default:
			return QSqlQueryModel::headerData(section, orientation,
			    role);
			break;
		}
		break;

	case Qt::ToolTipRole:
		if ((section < READLOC_COL) || (section > PROCSNG_COL)) {
			return QVariant();
		}

		dataType = QSqlQueryModel::headerData(section, Qt::Horizontal,
		    ROLE_MSGS_DB_ENTRY_TYPE).toInt();
		switch (dataType) {
		case DB_BOOL_READ_LOCALLY: /* 'read locally'. */
		case DB_BOOL_ATTACHMENT_DOWNLOADED: /* 'is downloaded'. */
		case DB_INT_PROCESSING_STATE: /* 'process status'. */
			/* Tool tip. */
			return QSqlQueryModel::headerData(section, orientation,
			    Qt::EditRole);
			break;
		default:
			return QVariant();
			break;
		}
		break;

	default:
		return QSqlQueryModel::headerData(section, orientation, role);
		break;
	}
}


#undef READLOC_COL
#undef ATTDOWN_COL
#undef PROCSNG_COL


/* ========================================================================= */
/*
 * Override message as being read.
 */
bool DbMsgsTblModel::overrideRead(int dmId, bool forceRead)
/* ========================================================================= */
{
	m_overriddenRL[dmId] = forceRead;

	/*
	 * The model should be forced to emit dataChanged(). However, finding
	 * the proper model index here is painful. Therefore ensure that the
	 * signal is emitted after calling this function.
	 */

	return true;
}


/* ========================================================================= */
/*
 * Override message as having its attachments having downloaded.
 */
bool DbMsgsTblModel::overrideDownloaded(int dmId, bool forceDownloaded)
/* ========================================================================= */
{
	m_overriddenAD[dmId] = forceDownloaded;

	/*
	 * The model should be forced to emit dataChanged(). However, finding
	 * the proper model index here is painful. Therefore ensure that the
	 * signal is emitted after calling this function.
	 */

	return true;
}


/* ========================================================================= */
/*
 * Override message processing state.
 */
bool DbMsgsTblModel::overrideProcessing(int dmId,
    MessageProcessState forceState)
/* ========================================================================= */
{
	m_overriddenPS[dmId] = forceState;

	/*
	 * The model should be forced to emit dataChanged(). However, finding
	 * the proper model index here is painful. Therefore ensure that the
	 * signal is emitted after calling this function.
	 */

	return true;
}


/* ========================================================================= */
/*
 * Compute viewed data in file size column.
 */
QVariant DbFlsTblModel::data(const QModelIndex &index, int role) const
/* ========================================================================= */
{
	/* TODO -- Add accurate attachment size computation. */
	if ((Qt::DisplayRole == role) && (4 == index.column())) {
		/* Compute attachment size from base64 length. */
		return QSqlQueryModel::data(index, role).toInt() * 3 / 4;
		/* TODO -- Add fast accurate attachment size computation. */
		//const QByteArray &b64 = QSqlQueryModel::data(
		//    index.sibling(index.row(), 2), role).toByteArray();
		//return QByteArray::fromBase64(b64).size();
	} else {
		return QSqlQueryModel::data(index, role);
	}
}


/* ========================================================================= */
MessageDb::MessageDb(const QString &connectionName, QObject *parent)
/* ========================================================================= */
    : QObject(parent),
    m_sqlMsgsModel(),
    m_sqlFilesModel()
{
	m_db = QSqlDatabase::addDatabase(dbContainer::dbDriverType,
	    connectionName);
}


/* ========================================================================= */
MessageDb::~MessageDb(void)
/* ========================================================================= */
{
	m_db.close();
}


/* ========================================================================= */
/*
 * Open database file.
 */
bool MessageDb::openDb(const QString &fileName)
/* ========================================================================= */
{
	bool ret;

	if (globPref.store_messages_on_disk) {
		m_db.setDatabaseName(QDir::toNativeSeparators(fileName));
	} else {
		m_db.setDatabaseName(":memory:");
	}

	ret = m_db.open();

	if (ret) {
		/* Ensure database contains all tables. */
		createEmptyMissingTables();
	}

	return ret;
}


/* ========================================================================= */
/*
 * Get file name.
 */
QString MessageDb::fileName(void) const
/* ========================================================================= */
{
	return m_db.databaseName();
}


/* ========================================================================= */
/*
 * Return received messages model.
 */
DbMsgsTblModel * MessageDb::msgsRcvdModel(const QString &recipDbId)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr = "SELECT ";
	for (int i = 0; i < (receivedItemIds.size() - 2); ++i) {
		queryStr += receivedItemIds[i] + ", ";
	}
	queryStr += "(ifnull(raw_message_data.message_id, 0) != 0) "
	    "AS is_downloaded" ", ";
	queryStr += "ifnull(process_state.state, 0) AS process_status";
	queryStr += " FROM messages "
	    "LEFT JOIN supplementary_message_data "
	    "ON (messages.dmID = supplementary_message_data.message_id) "
	    "LEFT JOIN raw_message_data "
	    "ON (messages.dmId = raw_message_data.message_id) "
	    "LEFT JOIN process_state "
	    "ON (messages.dmId = process_state.message_id) "
	    "WHERE (dbIDRecipient = :recipDbId) ";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":recipDbId", recipDbId);
	query.exec(); /* TODO -- Handle error. */

	m_sqlMsgsModel.setQuery(query);
	for (int i = 0; i < receivedItemIds.size(); ++i) {
		/* TODO -- Handle the joined tables in a better way. */
		if (msgsTbl.attrProps.find(receivedItemIds[i]) !=
		    msgsTbl.attrProps.end()) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(receivedItemIds[i]).desc);
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(receivedItemIds[i]).type,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if (smsgdtTbl.attrProps.find(receivedItemIds[i]) !=
		    smsgdtTbl.attrProps.end()) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    smsgdtTbl.attrProps.value(
			    receivedItemIds[i]).desc);
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    smsgdtTbl.attrProps.value(receivedItemIds[i]).type,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if ("is_downloaded" == receivedItemIds[i]) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    QObject::tr("Attachments downloaded"));
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    DB_BOOL_ATTACHMENT_DOWNLOADED,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if ("process_status" == receivedItemIds[i]) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    QObject::tr("Processing state"));
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    DB_INT_PROCESSING_STATE, ROLE_MSGS_DB_ENTRY_TYPE);
		} else {
			Q_ASSERT(0);
		}
	}

	return &m_sqlMsgsModel;
}


/* ========================================================================= */
/*
 * Return received messages within past 90 days.
 */
DbMsgsTblModel * MessageDb::msgsRcvdWithin90DaysModel(
    const QString &recipDbId)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr = "SELECT ";
	for (int i = 0; i < (receivedItemIds.size() - 2); ++i) {
		queryStr += receivedItemIds[i] + ", ";
	}
	queryStr += "(ifnull(raw_message_data.message_id, 0) != 0) "
	    "AS is_downloaded" ", ";
	queryStr += "ifnull(process_state.state, 0) AS process_status";
	queryStr += " FROM messages "
	    "LEFT JOIN supplementary_message_data "
	    "ON (messages.dmID = supplementary_message_data.message_id) "
	    "LEFT JOIN raw_message_data "
	    "ON (messages.dmId = raw_message_data.message_id) "
	    "LEFT JOIN process_state "
	    "ON (messages.dmId = process_state.message_id) "
	    "WHERE "
	    "(dbIDRecipient = :recipDbId)"
	    " and "
	    "(dmDeliveryTime >= date('now','-90 day'))";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":recipDbId", recipDbId);
	query.exec(); /* TODO -- Handle error. */

	m_sqlMsgsModel.setQuery(query);
	for (int i = 0; i < receivedItemIds.size(); ++i) {
		/* TODO -- Handle the joined tables in a better way. */
		if (msgsTbl.attrProps.find(receivedItemIds[i]) !=
		    msgsTbl.attrProps.end()) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(receivedItemIds[i]).desc);
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(receivedItemIds[i]).type,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if (smsgdtTbl.attrProps.find(receivedItemIds[i]) !=
		    smsgdtTbl.attrProps.end()) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    smsgdtTbl.attrProps.value(
			    receivedItemIds[i]).desc);
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    smsgdtTbl.attrProps.value(receivedItemIds[i]).type,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if ("is_downloaded" == receivedItemIds[i]) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    QObject::tr("Attachments downloaded"));
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    DB_BOOL_ATTACHMENT_DOWNLOADED,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if ("process_status" == receivedItemIds[i]) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    QObject::tr("Processing state"));
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    DB_INT_PROCESSING_STATE, ROLE_MSGS_DB_ENTRY_TYPE);
		} else {
			Q_ASSERT(0);
		}
	}

	return &m_sqlMsgsModel;
}


/* ========================================================================= */
/*
 * Return received messages within given year.
 */
DbMsgsTblModel * MessageDb::msgsRcvdInYearModel(const QString &recipDbId,
    const QString &year)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr = "SELECT ";
	for (int i = 0; i < (receivedItemIds.size() - 2); ++i) {
		queryStr += receivedItemIds[i] + ", ";
	}
	queryStr += "(ifnull(raw_message_data.message_id, 0) != 0) "
	    "AS is_downloaded" ", ";
	queryStr += "ifnull(process_state.state, 0) AS process_status";
	queryStr += " FROM messages "
	    "LEFT JOIN supplementary_message_data "
	    "ON (messages.dmID = supplementary_message_data.message_id) "
	    "LEFT JOIN raw_message_data "
	    "ON (messages.dmId = raw_message_data.message_id) "
	    "LEFT JOIN process_state "
	    "ON (messages.dmId = process_state.message_id) "
	    "WHERE "
	    "(dbIDRecipient = :recipDbId)"
	    " and "
	    "(strftime('%Y', dmDeliveryTime) = :year)";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":recipDbId", recipDbId);
	query.bindValue(":year", year);
	query.exec(); /* TODO -- Handle error. */

	m_sqlMsgsModel.setQuery(query);
	for (int i = 0; i < receivedItemIds.size(); ++i) {
		/* TODO -- Handle the joined tables in a better way. */
		if (msgsTbl.attrProps.find(receivedItemIds[i]) !=
		    msgsTbl.attrProps.end()) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(receivedItemIds[i]).desc);
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(receivedItemIds[i]).type,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if (smsgdtTbl.attrProps.find(receivedItemIds[i]) !=
		    smsgdtTbl.attrProps.end()) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    smsgdtTbl.attrProps.value(
			    receivedItemIds[i]).desc);
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    smsgdtTbl.attrProps.value(receivedItemIds[i]).type,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if ("is_downloaded" == receivedItemIds[i]) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    QObject::tr("Attachments downloaded"));
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    DB_BOOL_ATTACHMENT_DOWNLOADED,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if ("process_status" == receivedItemIds[i]) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    QObject::tr("Processing state"));
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    DB_INT_PROCESSING_STATE, ROLE_MSGS_DB_ENTRY_TYPE);
		} else {
			Q_ASSERT(0);
		}
	}

	return &m_sqlMsgsModel;
}


/* ========================================================================= */
/*
 * Return list of years (strings) in database.
 */
QList<QString> MessageDb::msgsRcvdYears(const QString &recipDbId,
     enum sorting sorting) const
/* ========================================================================= */
{
	QList<QString> yearList;
	QSqlQuery query(m_db);
	QString queryStr = "SELECT DISTINCT strftime('%Y', dmDeliveryTime) "
	    "FROM messages WHERE "
	    "dbIDRecipient = :recipDbId";
	switch (sorting) {
	case ASCENDING:
		queryStr += " ORDER BY dmDeliveryTime ASC";
	case DESCENDING:
		queryStr += " ORDER BY dmDeliveryTime DESC";
	default:
		break;
	}
	//qDebug() << "Generating received year list" << recipDbId;
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":recipDbId", recipDbId);
	if (query.exec()) {
		query.first();
		while (query.isValid()) {
			// qDebug() << query.value(0).toString();
			yearList.append(query.value(0).toString());
			query.next();
		}
	}

	return yearList;
}


/* ========================================================================= */
/*
 * Return list of years and number of messages in database.
 */
QList< QPair<QString, int> > MessageDb::msgsRcvdYearlyCounts(
    const QString &recipDbId, enum sorting sorting) const
/* ========================================================================= */
{
	QList< QPair<QString, int> > yearlyCounts;
	QList<QString> yearList = msgsRcvdYears(recipDbId, sorting);
	QSqlQuery query(m_db);
	QString queryStr;

	for (int i = 0; i < yearList.size(); ++i) {
		queryStr = "SELECT COUNT(*) AS nrRecords FROM messages WHERE "
		    "(dbIDRecipient = :recipDbId)"
		    " and "
		    "(strftime('%Y', dmDeliveryTime) = :year)";
		//qDebug() << queryStr;
		if (!query.prepare(queryStr)) {
			/* TODO -- Handle error. */
		}
		query.bindValue(":recipDbId", recipDbId);
		query.bindValue(":year", yearList[i]);
		if (query.exec() && query.isActive()) {
			query.first();
			yearlyCounts.append(QPair<QString, int>(yearList[i],
			    query.value(0).toInt()));
		}
	}

	return yearlyCounts;
}


/* ========================================================================= */
/*
 * Return number of unread messages received within past 90
 *     days.
 */
int MessageDb::msgsRcvdUnreadWithin90Days(const QString &recipDbId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT COUNT(*) AS nrUnread "
	    "FROM messages LEFT JOIN supplementary_message_data "
	    "ON (messages.dmID = supplementary_message_data.message_id) "
	    "WHERE "
	    "(dbIDRecipient = :recipDbId)"
	    " and "
	    "(dmDeliveryTime >= date('now','-90 day'))"
	    " and "
	    "(read_locally = 0)";
	//qDebug() << queryStr << recipDbId;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":recipDbId", recipDbId);
	if (query.exec() && query.isActive()) {
		query.first();
		return query.value(0).toInt();
	}

	return 0;
}


/* ========================================================================= */
/*
 * Return number of unread received messages in year.
 */
int MessageDb::msgsRcvdUnreadInYear(const QString &recipDbId,
    const QString &year) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT COUNT(*) AS nrUnread "
	    "FROM messages LEFT JOIN supplementary_message_data "
	    "ON (messages.dmID = supplementary_message_data.message_id) "
	    "WHERE "
	    "(dbIDRecipient = :recipDbId)"
	    " and "
	    "(strftime('%Y', dmDeliveryTime) = :year)"
	    " and "
	    "(read_locally = 0)";
	//qDebug() << queryStr << recipDbId << year;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":recipDbId", recipDbId);
	query.bindValue(":year", year);
	if (query.exec() && query.isActive()) {
		query.first();
		return query.value(0).toInt();
	}

	return 0;
}


/* ========================================================================= */
/*
 * Return sent messages model.
 */
DbMsgsTblModel * MessageDb::msgsSntModel(const QString &sendDbId)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr = "SELECT ";
	for (int i = 0; i < (sentItemIds.size() - 1); ++i) {
		queryStr += sentItemIds[i] + ", ";
	}
	queryStr += "(ifnull(raw_message_data.message_id, 0) != 0) "
	    "AS is_downloaded";
	queryStr += " FROM messages "
	    "LEFT JOIN raw_message_data "
	    "ON (messages.dmId = raw_message_data.message_id) "
	    "WHERE dbIDSender = :sendDbId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":sendDbId", sendDbId);
	query.exec(); /* TODO -- Handle error. */

	m_sqlMsgsModel.setQuery(query);
	for (int i = 0; i < sentItemIds.size(); ++i) {
		/* TODO -- Handle the joined tables in a better way. */
		if (msgsTbl.attrProps.find(sentItemIds[i]) !=
		    msgsTbl.attrProps.end()) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(sentItemIds[i]).desc);
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(sentItemIds[i]).type,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if ("is_downloaded" == sentItemIds[i]) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    QObject::tr("Attachments downloaded"));
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    DB_BOOL_ATTACHMENT_DOWNLOADED,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else {
			Q_ASSERT(0);
		}
	}

	return &m_sqlMsgsModel;
}


/* ========================================================================= */
/*
 * Return sent messages within past 90 days.
 */
DbMsgsTblModel * MessageDb::msgsSntWithin90DaysModel(
    const QString &sendDbId)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr = "SELECT ";
	for (int i = 0; i < (sentItemIds.size() - 1); ++i) {
		queryStr += sentItemIds[i] + ", ";
	}
	queryStr += "(ifnull(raw_message_data.message_id, 0) != 0) "
	    "AS is_downloaded";
	queryStr += " FROM messages "
	    "LEFT JOIN raw_message_data "
	    "ON (messages.dmId = raw_message_data.message_id) "
	    "WHERE "
	    "(dbIDSender = :sendDbId)"
	    " and "
	    "(dmDeliveryTime >= date('now','-90 day'))";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":sendDbId", sendDbId);
	query.exec(); /* TODO -- Handle error. */

	m_sqlMsgsModel.setQuery(query);
	for (int i = 0; i < sentItemIds.size(); ++i) {
		/* TODO -- Handle the joined tables in a better way. */
		if (msgsTbl.attrProps.find(sentItemIds[i]) !=
		    msgsTbl.attrProps.end()) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(sentItemIds[i]).desc);
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(sentItemIds[i]).type,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if ("is_downloaded" == sentItemIds[i]) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    QObject::tr("Attachments downloaded"));
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    DB_BOOL_ATTACHMENT_DOWNLOADED,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else {
			Q_ASSERT(0);
		}
	}

	return &m_sqlMsgsModel;
}


/* ========================================================================= */
/*
 * Return sent messages within given year.
 */
DbMsgsTblModel * MessageDb::msgsSntInYearModel(const QString &sendDbId,
    const QString &year)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr = "SELECT ";
	for (int i = 0; i < (sentItemIds.size() - 1); ++i) {
		queryStr += sentItemIds[i] + ", ";
	}
	queryStr += "(ifnull(raw_message_data.message_id, 0) != 0) "
	    "AS is_downloaded";
	queryStr += " FROM messages "
	    "LEFT JOIN raw_message_data "
	    "ON (messages.dmId = raw_message_data.message_id) "
	    "WHERE "
	    "(dbIDSender = :sendDbId)"
	    " and "
	    "(strftime('%Y', dmDeliveryTime) = :year)";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":sendDbId", sendDbId);
	query.bindValue(":year", year);
	query.exec(); /* TODO -- Handle error. */

	m_sqlMsgsModel.setQuery(query);
	for (int i = 0; i < sentItemIds.size(); ++i) {
		/* TODO -- Handle the joined tables in a better way. */
		if (msgsTbl.attrProps.find(sentItemIds[i]) !=
		    msgsTbl.attrProps.end()) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(sentItemIds[i]).desc);
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    msgsTbl.attrProps.value(sentItemIds[i]).type,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else if ("is_downloaded" == sentItemIds[i]) {
			/* Description. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    QObject::tr("Attachments downloaded"));
			/* Data type. */
			m_sqlMsgsModel.setHeaderData(i, Qt::Horizontal,
			    DB_BOOL_ATTACHMENT_DOWNLOADED,
			    ROLE_MSGS_DB_ENTRY_TYPE);
		} else {
			Q_ASSERT(0);
		}
	}

	return &m_sqlMsgsModel;
}


/* ========================================================================= */
/*
 * Return list of years (strings) in database.
 */
QList<QString> MessageDb::msgsSntYears(const QString &sendDbId,
    enum sorting sorting) const
/* ========================================================================= */
{
	QList<QString> yearList;
	QSqlQuery query(m_db);
	QString queryStr = "SELECT DISTINCT strftime('%Y', dmDeliveryTime) "
	    "FROM messages WHERE "
	    "dbIDSender = :sendDbId";
	switch (sorting) {
	case ASCENDING:
		queryStr += " ORDER BY dmDeliveryTime ASC";
	case DESCENDING:
		queryStr += " ORDER BY dmDeliveryTime DESC";
	default:
		break;
	}
	//qDebug() << "Generating received year list" << recipDbId;
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":sendDbId", sendDbId);
	if (query.exec()) {
		query.first();
		while (query.isValid()) {
			// qDebug() << query.value(0).toString();
			yearList.append(query.value(0).toString());
			query.next();
		}
	}

	return yearList;
}


/* ========================================================================= */
/*
 * Return list of years and number of messages in database.
 */
QList< QPair<QString, int> > MessageDb::msgsSntYearlyCounts(
    const QString &sendDbId, enum sorting sorting) const
/* ========================================================================= */
{
	QList< QPair<QString, int> > yearlyCounts;
	QList<QString> yearList = msgsSntYears(sendDbId, sorting);
	QSqlQuery query(m_db);
	QString queryStr;

	for (int i = 0; i < yearList.size(); ++i) {
		queryStr = "SELECT COUNT(*) AS nrRecords FROM messages WHERE "
		    "(dbIDSender = :sendDbId)"
		    " and "
		    "(strftime('%Y', dmDeliveryTime) = :year)";
		//qDebug() << queryStr;
		if (!query.prepare(queryStr)) {
			/* TODO -- Handle error. */
		}
		query.bindValue(":sendDbId", sendDbId);
		query.bindValue(":year", yearList[i]);
		if (query.exec() && query.isActive()) {
			query.first();
			if (query.isValid()) {
				yearlyCounts.append(QPair<QString, int>(
				    yearList[i], query.value(0).toInt()));
			}
		}
	}

	return yearlyCounts;
}


/* ========================================================================= */
/*
 * Return number of unread messages sent within past 90
 *     days.
 */
int MessageDb::msgsSntUnreadWithin90Days(const QString &sendDbId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT COUNT(*) AS nrUnread "
	    "FROM messages LEFT JOIN supplementary_message_data "
	    "ON (messages.dmID = supplementary_message_data.message_id) "
	    "WHERE "
	    "(dbIDSender = :sendDbId)"
	    " and "
	    "(dmDeliveryTime >= date('now','-90 day'))"
	    " and "
	    "(read_locally = 0)";
	//qDebug() << queryStr << sendDbId;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":sendDbId", sendDbId);
	if (query.exec() && query.isActive()) {
		query.first();
		return query.value(0).toInt();
	}

	return 0;
}


/* ========================================================================= */
/*
 * Return number of unread sent messages in year.
 */
int MessageDb::msgsSntUnreadInYear(const QString &sendDbId,
    const QString &year) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT COUNT(*) AS nrUnread "
	    "FROM messages LEFT JOIN supplementary_message_data "
	    "ON (messages.dmID = supplementary_message_data.message_id) "
	    "WHERE "
	    "(dbIDSender = :sendDbId)"
	    " and "
	    "(strftime('%Y', dmDeliveryTime) = :year)"
	    " and "
	    "(read_locally = 0)";
	//qDebug() << queryStr << sendDbId << year;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":sendDbId", sendDbId);
	query.bindValue(":year", year);
	if (query.exec() && query.isActive()) {
		query.first();
		return query.value(0).toInt();
	}

	return 0;
}


/* ========================================================================= */
/*
 * Generate information for reply dialog.
 */
QVector<QString> MessageDb::msgsReplyDataTo(int dmId) const
/* ========================================================================= */
{
	QVector<QString> reply(6);
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT "
	    "dmAnnotation, dbIDSender, dmSender, dmSenderAddress, _dmType, "
	    "dmSenderRefNumber FROM messages WHERE "
	    "dmID = :dmId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();
		if (query.isValid()) {
			reply[0] = query.value(0).toString();
			reply[1] = query.value(1).toString();
			reply[2] = query.value(2).toString();
			reply[3] = query.value(3).toString();
			reply[4] = query.value(4).toString();
			reply[5] = query.value(5).toString();
		}
	}

	return reply;
}


/* ========================================================================= */
/*
 * Returns true if verification attempt was performed.
 */
bool MessageDb::msgsVerificationAttempted(int dmId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	debugFuncCall();

	queryStr = "SELECT "
	    "is_verified"
	    " FROM messages WHERE "
	    "dmID = :dmId";
	// qDebug() << queryStr << dmId;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive() &&
	    query.first() && query.isValid()) {
		/* If no value is set then the conversion will fail. */
		bool ret = ! query.value(0).isNull();
		qDebug() << "Verification attempted" << ret;
		return ret;
	}

	return false;
}


/* ========================================================================= */
/*
 * Was message locally read.
 */
bool MessageDb::smsgdtLocallyRead(int dmId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT read_locally FROM supplementary_message_data "
	    "WHERE "
	    "message_id = :dmId";
	//qDebug() << queryStr << dmId;
	if (!query.prepare(queryStr)) {
		return false;
	}
	query.bindValue(":dmId", dmId);
	if (query.exec()) {
		query.first();
		return query.value(0).toBool();
	}

	return false;
}


/* ========================================================================= */
/*
 * Set message status to locally read.
 */
bool MessageDb::smsgdtSetLocallyRead(int dmId, bool read)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "UPDATE supplementary_message_data "
	    "SET read_locally = :read WHERE "
	    "message_id = :dmId";
	//qDebug() << queryStr << dmId;
	if (!query.prepare(queryStr)) {
		return false;
	}
	query.bindValue(":read", read);
	query.bindValue(":dmId", dmId);
	return query.exec();
}


/* ========================================================================= */
/*
 * Return contact list from message db.
 */
QList< QVector<QString> > MessageDb::uniqueContacts(void) const
/* ========================================================================= */
{
	QList<QVector<QString>> list_contacts;
	QSqlQuery query(m_db);
	QString queryStr = "SELECT DISTINCT "
	   "dbIDSender, dmSender, dmSenderAddress"
	   " FROM messages";
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	if (query.exec()) {
		query.first();
		while (query.isValid()) {
			QVector<QString> contact;
			contact.append(query.value(0).toString());
			contact.append(query.value(1).toString());
			contact.append(query.value(2).toString());
			list_contacts.append(contact);
			query.next();
		}
	}
	return list_contacts;
}


/* ========================================================================= */
/*
 * Return message delivery info HTML to PDF
 */
QString MessageDb::deliveryInfoHtmlToPdf(int dmId) const
/* ========================================================================= */
{
	QString html;
	QSqlQuery query(m_db);
	QString queryStr;
	QString tmp;

	html += indentDivStart;

	html += "<table width=\"100%\" style=\"padding: 30px 30px 30px 30px; font-size: 20px;\"><tr><td>" +
	    strongMessagePdf(QObject::tr("Advice of Delivery")) + "</td><td align=\"right\">" +
	    QObject::tr("Message ID:") + " " + strongMessagePdf(QString::number(dmId)) +
	    "</td></tr></table><br/><br/>";

	queryStr = "SELECT "
	    "dmSender, dmSenderAddress, dbIDSender, _dmType, "
	    "dmRecipient, dmRecipientAddress, dmDeliveryTime, dmAnnotation, "
	    "dmLegalTitleLaw, dmLegalTitleYear, dmLegalTitleSect, "
	    "dmLegalTitlePar, dmLegalTitlePoint, "
	    "dmRecipientRefNumber, dmRecipientIdent, "
	    "dmSenderRefNumber, dmSenderIdent, "
	    "dmToHands, dmPersonalDelivery, dmAllowSubstDelivery, "
	    "dmAcceptanceTime "
	    "FROM messages WHERE "
	    "dmID = :dmId";

	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();

		/* Sender info */
		html += messageTableSectionPdf(QObject::tr("Sender"));
		html += messageTableInfoStartPdf();
		tmp = query.value(0).toString() + QString(", ") +
		    query.value(1).toString();
		html += messageTableInfoPdf(QObject::tr("Name"), tmp);
		html += messageTableInfoEndPdf();

		/* Recipient info */
		html += messageTableSectionPdf(QObject::tr("Recipient"));
		html += messageTableInfoStartPdf();
		tmp = query.value(4).toString() + QString(", ") +
		    query.value(5).toString();
		html += messageTableInfoPdf(QObject::tr("Name"), tmp);
		html += messageTableInfoEndPdf();

		/* General info */
		html += messageTableSectionPdf(QObject::tr("General Information"));
		html += messageTableInfoStartPdf();
		html += messageTableInfoPdf(QObject::tr("Subject"),
		    query.value(7).toString());

		query.value(8).toString().isEmpty() ? tmp = "0" :
		    tmp = query.value(8).toString();
		tmp += QString(" / ");
		query.value(9).toString().isEmpty() ? tmp += "0" :
		    tmp += query.value(9).toString();
		tmp += QString(" § ") + " " +
		    query.value(10).toString() + " " +
		    QString(QObject::tr("paragraph")) + " " +
		    query.value(11).toString() + " " +
		    QString(QObject::tr("letter")) + " " +
		    query.value(12).toString();

		html += messageTableInfoPdf(QObject::tr("Delegation"), tmp);

		(query.value(13).toString()).isEmpty() ? tmp=QObject::tr("Not specified")
		: tmp = query.value(13).toString();
		html += messageTableInfoPdf(QObject::tr("Our ref.number"), tmp);
		(query.value(14).toString()).isEmpty() ? tmp=QObject::tr("Not specified")
		: tmp = query.value(14).toString();
		html += messageTableInfoPdf(QObject::tr("Our doc.id"), tmp);
		(query.value(15).toString()).isEmpty() ? tmp=QObject::tr("Not specified")
		: tmp = query.value(15).toString();
		html += messageTableInfoPdf(QObject::tr("Your ref.number"), tmp);
		(query.value(16).toString()).isEmpty() ? tmp=QObject::tr("Not specified")
		: tmp = query.value(16).toString();
		html += messageTableInfoPdf(QObject::tr("Your doc.id"), tmp);
		(query.value(17).toString()).isEmpty() ? tmp=QObject::tr("Not specified")
		: tmp = query.value(17).toString();
		html += messageTableInfoPdf(QObject::tr("To hands"), tmp);

		((query.value(18)).toInt()) ? tmp = QObject::tr("yes")
		: tmp = QObject::tr("no");
		html += messageTableInfoPdf(QObject::tr("Personal Delivery"), tmp);

		((query.value(19)).toInt()) ? tmp = QObject::tr("no")
		: tmp = QObject::tr("yes");
		html += messageTableInfoPdf(QObject::tr("Prohibit Delivery by Fiction"),
		    tmp);

		html += messageTableInfoEndPdf();

		/* Delivery info */
		html += messageTableSectionPdf(
		    QObject::tr("Delivery/Acceptance Information"));
		html += messageTableInfoStartPdf();
		html += messageTableInfoPdf(QObject::tr("Delivery"),
		        dateTimeStrFromDbFormat(query.value(6).toString(),
		        dateTimeDisplayFormat));
		html += messageTableInfoPdf(QObject::tr("Acceptance"),
		        dateTimeStrFromDbFormat(query.value(20).toString(),
		        dateTimeDisplayFormat));
		html += messageTableInfoEndPdf();
	}

	queryStr = "SELECT dmEventTime, dmEventDescr "
	    "FROM events WHERE message_id = :dmId";

	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}

	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();
		/* Attachments info */
		html += messageTableSectionPdf(QObject::tr("Events"));
		html += messageTableInfoStartPdf();
		int i = 1;
		while (query.isValid()) {
			tmp = dateTimeStrFromDbFormat(query.value(0).toString(),
			    dateTimeDisplayFormat) + " - " +
			    query.value(1).toString();
			html += messageTableInfoPdf(QObject::tr("Time"), tmp);
			query.next();
			i++;
		}
		html += messageTableInfoEndPdf();
	}

	html += divEnd;

	return html;
}


/* ========================================================================= */
/*
 * Return message envelope HTML to PDF
 */
QString MessageDb::envelopeInfoHtmlToPdf(int dmId, QString dbType) const
/* ========================================================================= */
{
	QString html;
	QSqlQuery query(m_db);
	QString queryStr;
	QString tmp;

	html += indentDivStart;

	html += "<table width=\"100%\" style=\"padding: 30px 30px 30px 30px; font-size: 20px;\"><tr><td>" +
	    strongMessagePdf(QObject::tr("Envelope")) + "</td><td align=\"right\">" +
	    QObject::tr("Message ID:") + " " + strongMessagePdf(QString::number(dmId)) +
	    "</td></tr></table><br/><br/>";

	queryStr = "SELECT "
	    "dmSender, dmSenderAddress, dbIDSender, _dmType, "
	    "dmRecipient, dmRecipientAddress, dmDeliveryTime, dmAnnotation, "
	    "dmLegalTitleLaw, dmLegalTitleYear, dmLegalTitleSect, "
	    "dmLegalTitlePar, dmLegalTitlePoint, "
	    "dmRecipientRefNumber, dmRecipientIdent, "
	    "dmSenderRefNumber, dmSenderIdent, "
	    "dmToHands, dmPersonalDelivery, dmAllowSubstDelivery "
	    "FROM messages WHERE "
	    "dmID = :dmId";

	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();

		/* Sender info */
		html += messageTableSectionPdf(QObject::tr("Sender"));
		html += messageTableInfoStartPdf();
		tmp = query.value(0).toString() + QString(", ") +
		    query.value(1).toString();
		html += messageTableInfoPdf(QObject::tr("Name"), tmp);
		html += messageTableInfoPdf(QObject::tr("Databox ID"),
		    query.value(2).toString());
		html += messageTableInfoPdf(QObject::tr("Databox Type"),
		    dbType);
		html += messageTableInfoEndPdf();

		/* Recipient info */
		html += messageTableSectionPdf(QObject::tr("Recipient"));
		html += messageTableInfoStartPdf();
		tmp = query.value(4).toString() + QString(", ") +
		    query.value(5).toString();
		html += messageTableInfoPdf(QObject::tr("Name"), tmp);
		html += messageTableInfoPdf(QObject::tr("Delivery"),
		        dateTimeStrFromDbFormat(query.value(6).toString(),
		        dateTimeDisplayFormat));
		html += messageTableInfoEndPdf();

		/* General info */
		html += messageTableSectionPdf(QObject::tr("General Information"));
		html += messageTableInfoStartPdf();
		html += messageTableInfoPdf(QObject::tr("Subject"),
		    query.value(7).toString());

		query.value(8).toString().isEmpty() ? tmp = "0" :
		    tmp = query.value(8).toString();
		tmp += QString(" / ");
		query.value(9).toString().isEmpty() ? tmp += "0" :
		    tmp += query.value(9).toString();
		tmp += QString(" § ") + " " +
		    query.value(10).toString() + " " +
		    QString(QObject::tr("paragraph")) + " " +
		    query.value(11).toString() + " " +
		    QString(QObject::tr("letter")) + " " +
		    query.value(12).toString();

		html += messageTableInfoPdf(QObject::tr("Delegation"), tmp);

		(query.value(13).toString()).isEmpty() ? tmp=QObject::tr("Not specified")
		: tmp = query.value(13).toString();
		html += messageTableInfoPdf(QObject::tr("Our ref.number"), tmp);
		(query.value(14).toString()).isEmpty() ? tmp=QObject::tr("Not specified")
		: tmp = query.value(14).toString();
		html += messageTableInfoPdf(QObject::tr("Our doc.id"), tmp);
		(query.value(15).toString()).isEmpty() ? tmp=QObject::tr("Not specified")
		: tmp = query.value(15).toString();
		html += messageTableInfoPdf(QObject::tr("Your ref.number"), tmp);
		(query.value(16).toString()).isEmpty() ? tmp=QObject::tr("Not specified")
		: tmp = query.value(16).toString();
		html += messageTableInfoPdf(QObject::tr("Your doc.id"), tmp);
		(query.value(17).toString()).isEmpty() ? tmp=QObject::tr("Not specified")
		: tmp = query.value(17).toString();
		html += messageTableInfoPdf(QObject::tr("To hands"), tmp);


		((query.value(18)).toInt()) ? tmp = QObject::tr("yes")
		: tmp = QObject::tr("no");
		html += messageTableInfoPdf(QObject::tr("Personal Delivery"), tmp);

		((query.value(19)).toInt()) ? tmp = QObject::tr("no")
		: tmp = QObject::tr("yes");
		html += messageTableInfoPdf(QObject::tr("Prohibit Delivery by Fiction"),
		    tmp);

		html += messageTableInfoEndPdf();
	}

	queryStr = "SELECT _dmFileDescr FROM files WHERE message_id = :dmId";

	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}

	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();
		/* Attachments info */
		html += messageTableSectionPdf(QObject::tr("Attachments"));
		html += messageTableInfoStartPdf();
		int i = 1;
		while (query.isValid()) {
			html += messageTableInfoPdf(QString::number(i),
			query.value(0).toString());
			query.next();
			i++;
		}
		html += messageTableInfoEndPdf();
	}

	html += divEnd;

	return html;
}


/* ========================================================================= */
/*
 * Return message HTML formatted description.
 */
QString MessageDb::descriptionHtml(int dmId, QAbstractButton *verifySignature,
    bool showId, bool warnOld) const
/* ========================================================================= */
{
	QString html;
	QSqlQuery query(m_db);
	QString queryStr;

	html += indentDivStart;
	html += "<h3>" + QObject::tr("Identification") + "</h3>";
	if (showId) {
		html += strongAccountInfoLine(QObject::tr("ID"), QString::number(dmId));
	}

	queryStr = "SELECT "
	    "dmAnnotation, _dmType, dmSender, dmSenderAddress, "
	    "dmRecipient, dmRecipientAddress"
	    " FROM messages WHERE "
	    "dmID = :dmId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();
		html += strongAccountInfoLine(QObject::tr("Subject"),
		    query.value(0).toString());
		if (!query.value(1).toString().isEmpty() &&
		    (!dmTypeToText(query.value(1).toString()).isEmpty())) {
			html += strongAccountInfoLine(QObject::tr("Message type"),
			    dmTypeToText(query.value(1).toString()));
		}

		html += "<br/>";

		/* Information about message author. */
		html += strongAccountInfoLine(QObject::tr("From"),
		    query.value(2).toString());
		html += strongAccountInfoLine(QObject::tr("Sender Address"),
		    query.value(3).toString());
		/* Custom data. */
		QJsonDocument customData = smsgdCustomData(dmId);
		if (!customData.isEmpty() && customData.isObject()) {
			QJsonValue value =
			    customData.object().value("message_author");
			if (value.isObject()) {
				QString authorInfo = value.toObject().value(
				        "authorName").toString();
				if (!authorInfo.isEmpty()) {
					authorInfo += ", ";
				}
				html += strongAccountInfoLine(
				    QObject::tr("Message author"),
				    authorInfo +
				    authorTypeToText(value.toObject().value(
				        "userType").toString()));
			}
		}

		html += "<br/>";

		html += strongAccountInfoLine(QObject::tr("To"),
		    query.value(4).toString());
		html += strongAccountInfoLine(QObject::tr("Recipient Address"),
		    query.value(5).toString());
	}

	queryStr = "SELECT ";
	for (int i = 0; i < (msgAttribs2.size() - 1); ++i) {
		queryStr += msgAttribs2[i] + ", ";
	}
	queryStr += msgAttribs2.last();
	queryStr += " FROM messages WHERE "
	    "dmID = :dmId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();
		for (int i = 0; i < msgAttribs2.size(); ++i) {
			if (!query.value(i).toString().isEmpty()) {
				html += strongAccountInfoLine(
				    msgsTbl.attrProps[msgAttribs2[i]].desc,
				    query.value(i).toString());
			}
		}
	}

	html += "<h3>" + QObject::tr("Status") + "</h3>";
	/* Status. */
	queryStr = "SELECT ";
	for (int i = 0; i < (msgStatus.size() - 1); ++i) {
		queryStr += msgStatus[i] + ", ";
	}
	queryStr += msgStatus.last();
	queryStr += " FROM messages WHERE "
	    "dmID = :dmId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();
		html += strongAccountInfoLine(
		    msgsTbl.attrProps[msgStatus[0]].desc,
		    dateTimeStrFromDbFormat(query.value(0).toString(),
		        dateTimeDisplayFormat));
		html += strongAccountInfoLine(
		    msgsTbl.attrProps[msgStatus[1]].desc,
		    dateTimeStrFromDbFormat(query.value(1).toString(),
		        dateTimeDisplayFormat));
		html += strongAccountInfoLine(
		    msgsTbl.attrProps[msgStatus[2]].desc,
		    QString::number(query.value(2).toInt()) + " -- " +
		    msgStatusToText(query.value(2).toInt()));
	}
	/* Events. */
	queryStr = "SELECT "
	    "dmEventTime, dmEventDescr"
	    " FROM events WHERE "
	    "message_id = :dmId"
	    " ORDER BY dmEventTime ASC";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();
		if (query.isValid()) {
			html += strongAccountInfoLine(QObject::tr("Events"),
			    "");
		}
		while (query.isValid()) {
			html += indentDivStart +
			    strongAccountInfoLine(
			        dateTimeStrFromDbFormat(
			            query.value(0).toString(),
			            dateTimeDisplayFormat),
			        query.value(1).toString()) +
			    divEnd;
			query.next();
		} 
	}
	/* Attachments. */
	queryStr = "SELECT COUNT(*) AS nrFiles "
	    " FROM files WHERE "
	    "message_id = :dmId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive() &&
	    query.first() && query.isValid() &&
	    (query.value(0).toInt() > 0)) {
		html += strongAccountInfoLine(QObject::tr("Attachments"),
		    QString::number(query.value(0).toInt()) + " " +
		    QObject::tr("(downloaded and ready)"));
	} else {
		queryStr = "SELECT "
		    "dmAttachmentSize"
		    " FROM messages WHERE "
		    "dmID = :dmId";
		//qDebug() << queryStr;
		if (!query.prepare(queryStr)) {
			/* TODO -- Handle error. */
		}
		query.bindValue(":dmId", dmId);
		if (query.exec() && query.isActive() &&
		    query.first() && query.isValid() &&
		   (query.value(0).toInt() > 0)) {
			html += strongAccountInfoLine(QObject::tr("Attachments"),
			    QObject::tr("not downloaded yet, ~") +
			    QString::number(query.value(0).toInt()) +
			    QObject::tr(" KB; use 'Download' to get them."));
		} else {
			html += strongAccountInfoLine(QObject::tr("Attachments"),
			    QObject::tr("(not available)"));
		}
	}
	if (warnOld) {
		/* TODO */
	}

	/* Disable verify signature button. It is re-enabled when needed. */
	if (0 != verifySignature) {
		verifySignature->setEnabled(false);
	}

	html += "<h3>" + QObject::tr("Signature") + "</h3>";
	/* Signature. */
	if (!msgsVerificationAttempted(dmId)) {
		/* Verification no attempted. */
		html += strongAccountInfoLine(QObject::tr("Message signature"),
		    QObject::tr("Not present"));
		/* Enable verification button. */
		if (0 != verifySignature) {
			verifySignature->setEnabled(true);
		}
	} else if (!msgsVerified(dmId)) {
		html += strongAccountInfoLine(QObject::tr("Message signature"),
		    QObject::tr("Invalid")  + " -- " +
		    QObject::tr("Message signature and content do not correspond!"));
	} else {
		html += strongAccountInfoLine(QObject::tr("Message signature"),
		    QObject::tr("Valid"));
		/* Check signing certificate. */
		// qDebug() << msgsVerificationDate(dmId);
		bool verified = msgCertValidAtDate(dmId,
		    msgsVerificationDate(dmId), !globPref.check_crl);
		QString verifiedText = verified ? QObject::tr("Valid") : QObject::tr("Invalid");
		if (!globPref.check_crl) {
			verifiedText += " (" +
			    QObject::tr("Certificate revocation check is turned off!") +
			    ")";
		}
		html += strongAccountInfoLine(QObject::tr("Signing certificate"),
		    verifiedText);
	}

	/* Time-stamp. */
	QDateTime tst;
	QByteArray tstData = msgsTimestampDER(dmId);
	QString timeStampStr;
	if (tstData.isEmpty()) {
		timeStampStr = QObject::tr("Not present");
	} else {
		time_t utc_time = 0;
		int ret = rawTstVerify(tstData.data(), tstData.size(),
		    &utc_time);

		if (-1 != ret) {
			tst = QDateTime::fromTime_t(utc_time);
		}

		timeStampStr = (1 == ret) ? QObject::tr("Valid") : QObject::tr("Invalid");
		if (-1 != ret) {
			timeStampStr +=
			    " (" + tst.toString("dd.MM.yyyy hh:mm:ss") + " " +
			    tst.timeZone().abbreviation(tst) + ")";
		}
	}
	html += strongAccountInfoLine(QObject::tr("Time stamp"),
	    timeStampStr);

	html += divEnd;

	//html += QString::number(dmId);
	// qDebug() << html;
	/* TODO */

	return html;
}


/* ========================================================================= */
/*
 * Return files related to given message.
 */
QAbstractTableModel * MessageDb::flsModel(int msgId)
/* ========================================================================= */
{
	int i;
	QSqlQuery query(m_db);
	QString queryStr = "SELECT ";
	for (i = 0; i < (fileItemIds.size() - 1); ++i) {
		queryStr += fileItemIds[i] + ", ";
	}
	queryStr += fileItemIds.last();
	queryStr += " FROM files WHERE "
	    "message_id = :msgId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":msgId", msgId);
	query.exec(); /* TODO -- Handle error. */

	/* First three columns ought to be hidden. */

	m_sqlFilesModel.setQuery(query);
	for (i = 0; i < fileItemIds.size(); ++i) {
		/* Description. */
		m_sqlFilesModel.setHeaderData(i, Qt::Horizontal,
		    flsTbl.attrProps.value(fileItemIds[i]).desc);
		/* Data type. */
		m_sqlFilesModel.setHeaderData(i, Qt::Horizontal,
		    flsTbl.attrProps.value(fileItemIds[i]).type,
		    ROLE_MSGS_DB_ENTRY_TYPE);
	}

	/* Rename last column to file size. */
	m_sqlFilesModel.setHeaderData(i - 1, Qt::Horizontal, QObject::tr("File Size"));

	return &m_sqlFilesModel;
}


/* ========================================================================= */
/*
 * Check if any message (dmID) exists in the table
 */
bool MessageDb::isInMessageDb(int dmId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;
	bool inMessages = false;
	bool inSupplementary = false;

	queryStr = "SELECT count(*) FROM messages WHERE "
	    "dmID = :dmId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();
		if (query.isValid()) {
			Q_ASSERT(query.value(0).toInt() < 2);
			inMessages = (1 == query.value(0).toInt());
		}
	}

	/*
	 * Entry in supplementary_message_data must exist if exists in
	 * messages.
	 */
	queryStr = "SELECT count(*) FROM supplementary_message_data WHERE "
	    "message_id = :dmId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();
		if (query.isValid()) {
			Q_ASSERT(query.value(0).toInt() < 2);
			inSupplementary = (1 == query.value(0).toInt());
		}
	}

	Q_ASSERT(inMessages == inSupplementary);

	return inMessages && inSupplementary;
}


/* ========================================================================= */
/*
 * Get message hash from db
 */
QList<QString> MessageDb::msgsGetHashFromDb(int dmId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QList<QString> retitem;
	QString queryStr;

	queryStr = "SELECT value,_algorithm FROM hashes WHERE "
	    "message_id = :dmId";

	if (!query.prepare(queryStr)) {
		qDebug() << "Select hashes errro:" << query.lastError();
	}

	query.bindValue(":dmId", dmId);

	if (query.exec() && query.isActive()) {
		query.first();
		if (query.isValid()) {
			retitem.append(query.value(0).toString());
			retitem.append(query.value(1).toString());
			return retitem;
		}
	}
	retitem.append("");
	retitem.append("");
	return retitem;
}


/* ========================================================================= */
/*
 * Insert additional info about author (sender) into db
 */
bool MessageDb::addMessageAuthorInfo(int dmID, const QString &sender_type,
    const QString &sender_name)
/* ========================================================================= */
{
	QSqlQuery query(m_db);

	QJsonObject authorObject;
	authorObject.insert("userType", sender_type.isEmpty() ?
	    QJsonValue(QJsonValue::Null) : sender_type);
	authorObject.insert("authorName", sender_name.isEmpty() ?
	    QJsonValue(QJsonValue::Null) : sender_name);
	QJsonObject object;
	object.insert("message_author", authorObject);

	QJsonDocument document;
	document.setObject(object);
	QString  json = document.toJson(QJsonDocument::Compact);

	QString queryStr = "UPDATE supplementary_message_data SET "
	    "custom_data = :custom_data WHERE message_id = :dmId";

	if (!query.prepare(queryStr)) {
		qDebug() << "Update supplementary_message_data error:"
		    << query.lastError();
		return false;
	}

	query.bindValue(":dmId", dmID);
	query.bindValue(":custom_data", json);

	if (query.exec()) {
		return true;
	} else {
		qDebug() << "Update supplementary_message_data error:"
		    << query.lastError();
		return false;
	}
}

/* ========================================================================= */
/*
 * Insert/update message file into file table
 */
bool MessageDb::msgsInsertUpdateMessageFile(int dmId,
    const QString &dmFileDescr, const QString &dmUpFileGuid,
    const QString &dmFileGuid, const QString &dmMimeType,
    const QString &dmFormat, const QString &dmFileMetaType,
    const QString &dmEncodedContent)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	int dbId;

	QString queryStr = "SELECT id FROM files WHERE "
	    "message_id = :message_id AND _dmFileDescr = :dmFileDescr "
	    "AND _dmMimeType = :dmMimeType";

	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateMessageFile"
		    << query.lastError();
		return false;
	}

	query.bindValue(":message_id", dmId);
	query.bindValue(":dmFileDescr", dmFileDescr);
	query.bindValue(":dmMimeType", dmMimeType);

	if (!query.exec()) {
		qDebug() << "Error: msgsInsertUpdateMessageFile"
		    << query.lastError();
	 	return false;
	} else {
		query.first();
		if (query.isValid()) {
			dbId = query.value(0).toInt();
		} else {
			dbId = 0;
		}
	}

	if (dbId != 0) {
		queryStr = "UPDATE files SET "
		" _dmFileDescr = :_dmFileDescr, _dmUpFileGuid = :_dmUpFileGuid,"
		" _dmFileGuid = :_dmFileGuid, _dmMimeType = :_dmMimeType, "
		"_dmFormat = :_dmFormat, _dmFileMetaType = :_dmFileMetaType, "
		"dmEncodedContent = :dmEncodedContent "
		"WHERE id = :dbId";
	} else {
		queryStr = "INSERT INTO files ("
		    "message_id, _dmFileDescr, _dmUpFileGuid, _dmFileGuid, "
		    "_dmMimeType, _dmFormat, _dmFileMetaType, dmEncodedContent"
		    ") VALUES ("
		    ":message_id, :_dmFileDescr, :_dmUpFileGuid, :_dmFileGuid,"
		    " :_dmMimeType, :_dmFormat, :_dmFileMetaType, "
		    ":dmEncodedContent)";
	}

	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateMessageFile"
		    << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	query.bindValue(":_dmFileDescr", dmFileDescr);
	query.bindValue(":_dmUpFileGuid", dmUpFileGuid);
	query.bindValue(":_dmFileGuid", dmFileGuid);
	query.bindValue(":_dmMimeType", dmMimeType);
	query.bindValue(":_dmFormat", dmFormat);
	query.bindValue(":_dmFileMetaType", dmFileMetaType);
	query.bindValue(":dmEncodedContent", dmEncodedContent);
	if (dbId != 0) {
	    query.bindValue(":dbId", dbId);
	}

	if (query.exec()) {
		return true;
	} else {
		qDebug() << "Error: msgsInsertUpdateMessageFile"
		    << query.lastError();
		return false;
	}
}


/* ========================================================================= */
/*
 * Insert message hash into hashes table
 */
bool MessageDb::msgsInsertUpdateMessageHash(int dmId, const QString &value,
    const QString &algorithm)
/* ========================================================================= */
{

	QSqlQuery query(m_db);
	int dbId;

	QString queryStr = "SELECT id FROM hashes WHERE "
	    "message_id = :message_id";

	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateMessageHash"
		    << query.lastError();
		return false;
	}

	query.bindValue(":message_id", dmId);

	if (!query.exec()) {
		qDebug() << "Error: msgsInsertUpdateMessageHash"
		    << query.lastError();
	 	return false;
	} else {
		query.first();
		if (query.isValid()) {
			dbId = query.value(0).toInt();
		} else {
			dbId = 0;
		}
	}

	if (dbId != 0) {
		queryStr = "UPDATE hashes SET "
		"value = :value, _algorithm = :algorithm "
		"WHERE id = :dbId";
	} else {
		queryStr = "INSERT INTO hashes (message_id, value, _algorithm)"
		" VALUES (:dmId, :value, :algorithm)";

	}

	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateMessageHash"
		    << query.lastError();
		return false;
	}
	query.bindValue(":dmId", dmId);
	query.bindValue(":value", value);
	query.bindValue(":algorithm", algorithm);
	if (dbId != 0) {
	    query.bindValue(":dbId", dbId);
	}

	if (query.exec()) {
		return true;
	} else {
		qDebug() << "Error: msgsInsertUpdateMessageHash"
		    << query.lastError();
		return false;
	}
}


/* ========================================================================= */
/*
 * Insert raw message data into raw_message_data table
 */
bool MessageDb::msgsInsertUpdateMessageRaw(int dmId, const QByteArray &raw,
    int message_type)
/* ========================================================================= */
{
	/* TODO -- The whole operation must fail or succeed. */

	QSqlQuery query(m_db);
	int dbId;

	QString queryStr = "SELECT message_id FROM raw_message_data WHERE "
	    "message_id = :message_id";

	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateMessageRaw"
		    << query.lastError();
		return false;
	}

	query.bindValue(":message_id", dmId);

	if (!query.exec()) {
		qDebug() << "Error: msgsInsertUpdateMessageRaw"
		    << query.lastError();
		return false;
	} else {
		query.first();
		if (query.isValid()) {
			dbId = query.value(0).toInt();
		} else {
			dbId = 0;
		}
	}

	if (dbId != 0) {
		queryStr = "UPDATE raw_message_data SET "
		"data = :data, message_type = :message_type "
		"WHERE message_id = :dbId";
	} else {
		queryStr = "INSERT INTO raw_message_data "
		"(message_id, message_type, data) "
		"VALUES (:dmId, :message_type, :data)";
	}

	QString base64 = raw.toBase64();

	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateMessageRaw"
		    << query.lastError();
		return false;
	}
	query.bindValue(":dmId", dmId);
	query.bindValue(":data", base64);
	query.bindValue(":message_type", message_type);
	if (dbId != 0) {
		query.bindValue(":dbId", dbId);
	}
	if (!query.exec()) {
		qDebug() << "Error: msgsInsertUpdateMessageRaw"
		    << query.lastError();
		return false;
	}

	/* Get certificate data. */
	QString crtBase64;
	struct x509_crt *crt = rawCmsSigningCert(raw.data(), raw.size());
	if (NULL != crt) {
		QByteArray crtDer;

		void *der = NULL;
		size_t derSize = 0;
		if (0 == x509CrtToDer(crt, &der, &derSize)) {
			crtDer.setRawData((char *) der, derSize);

			crtBase64 = crtDer.toBase64();

			free(der); der = NULL; derSize = 0;
		}

		x509CrtDestroy(crt); crt = NULL;
	}
	if (!crtBase64.isEmpty()) {
		msgsInsertUpdateMessageCertBase64(dmId, crtBase64);
	}

	return true;
}


/* ========================================================================= */
/*
 * Add/update message certificate in database.
 */
bool MessageDb::msgsInsertUpdateMessageCertBase64(int dmId,
    const QString &crtBase64)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	int certId;

	/* Search for certificate in 'certificate_data' table. */
	QString queryStr = "SELECT id FROM certificate_data WHERE "
	    "der_data = :der_data";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateMessageCertBase64"
		    << query.lastError();
		return false;
	}
	query.bindValue(":der_data", crtBase64);
	if (!query.exec()) {
		qDebug() << "Error: msgsInsertUpdateMessageCertBase64"
		    << query.lastError();
		return false;
	} else {
		query.first();
		if (query.isValid()) {
			certId = query.value(0).toInt(); /* Found. */
		} else {
			certId = -1; /* Not found. */
		}
	}

	/* If certificate was not found. */
	if (certId < 0) {
		/* Create unique certificate identifier. */
		queryStr = "SELECT max(id) from certificate_data";
		if (!query.prepare(queryStr)) {
			qDebug() << "Error: msgsInsertUpdateMessageCertBase64"
			    << query.lastError();
			return false;
		}
		if (!query.exec()) {
			qDebug() << "Error: msgsInsertUpdateMessageCertBase64"
			    << query.lastError();
			return false;
		} else {
			query.first();
			if (query.isValid()) {
				certId = query.value(0).toInt();
			} else {
				certId = -1;
			}
		}
		if (certId <= 0) {
			certId = 1; /* First certificate. */
		} else {
			++certId;
		}

		/* Insert missing certificate. */
		queryStr = "INSERT INTO certificate_data "
		    "(id, der_data) VALUES (:id, :der_data)";
		if (!query.prepare(queryStr)) {
			qDebug() << "Error: msgsInsertUpdateMessageCertBase64"
			    << query.lastError();
			return false;
		}
		query.bindValue(":id", certId);
		query.bindValue(":der_data", crtBase64);
		if (!query.exec()) {
			qDebug() << "Error: msgsInsertUpdateMessageCertBase64"
			    << query.lastError();
			return false;
		}
	}

	/*
	 * Abort operation if there is still no matching certificate available.
	 */
	if (certId <= 0) {
		return false;
	}

	/*
	 * Tie certificate to message. Find whether there is already
	 * an entry for the message.
	 */
	bool entryFound = false;
	queryStr = "SELECT * FROM message_certificate_data WHERE"
	    " message_id = :message_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateMessageCertBase64"
		    << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	if (!query.exec()) {
		qDebug() << "Error: msgsInsertUpdateMessageCertBase64"
		    << query.lastError();
		return false;
	}
	query.first();
	entryFound = query.isValid();
	/*
	 * Create or update message entry depending on whether a corresponding
	 * entry was found.
	 */
	if (entryFound) {
		queryStr = "UPDATE message_certificate_data SET "
		    "certificate_id = :certificate_id WHERE "
		    "message_id = :message_id";
	} else {
		queryStr = "INSERT INTO message_certificate_data "
		    "(message_id, certificate_id) VALUES "
		    "(:message_id, :certificate_id)";
	}
	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateMessageCertBase64"
		    << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	query.bindValue(":certificate_id", certId);
	if (!query.exec()) {
		qDebug() << "Error: msgsInsertUpdateMessageCertBase64"
		    << query.lastError();
		return false;
	}

	return true;
}


/* ========================================================================= */
/*
 * Insert raw delivery info into raw_delivery_info_data table
 */
bool MessageDb::msgsInsertUpdateDeliveryRaw(int dmId, const QString &raw)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	int dbId;

	QString queryStr = "SELECT message_id FROM raw_delivery_info_data WHERE"
	    " message_id = :message_id";

	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateDeliveryRaw"
		    << query.lastError();
		return false;
	}

	query.bindValue(":message_id", dmId);

	if (!query.exec()) {
		qDebug() << "Error: msgsInsertUpdateDeliveryRaw"
		    << query.lastError();
	 	return false;
	} else {
		query.first();
		if (query.isValid()) {
			dbId = query.value(0).toInt();
		} else {
			dbId = 0;
		}
	}

	if (dbId != 0) {
		queryStr = "UPDATE raw_delivery_info_data SET "
		"data = :data WHERE message_id = :dbId";
	} else {
		queryStr = "INSERT INTO raw_delivery_info_data "
		"(message_id, data) VALUES (:dmId, :data)";

	}

	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateDeliveryRaw"
		    << query.lastError();
		return false;
	}
	query.bindValue(":dmId", dmId);
	query.bindValue(":data", raw);
	if (dbId != 0) {
	    query.bindValue(":dbId", dbId);
	}

	if (query.exec()) {
		return true;
	} else {
		qDebug() << "Error: msgsInsertUpdateDeliveryRaw"
		    << query.lastError();
		return false;
	}
}


/* ========================================================================= */
/*
 * Insert/update message event into events table
 */
bool MessageDb::msgsInsertUpdateMessageEvent(int dmId, const QString &dmEventTime,
    const QString &dmEventType, const QString &dmEventDescr)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	int dbId;

	QString queryStr = "SELECT id FROM events WHERE "
	    "message_id = :message_id AND dmEventTime = :dmEventTime";

	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateMessageEvent"
		    << query.lastError();
		return false;
	}

	query.bindValue(":message_id", dmId);
	query.bindValue(":dmEventTime", dmEventTime);

	if (!query.exec()) {
		qDebug() << "Error: msgsInsertUpdateMessageEvent"
		    << query.lastError();
	 	return false;
	} else {
		query.first();
		if (query.isValid()) {
			dbId = query.value(0).toInt();
		} else {
			dbId = 0;
		}
	}

	QString dmEventDescrType = dmEventType + dmEventDescr;

	if (dbId != 0) {
		queryStr = "UPDATE events SET "
		"dmEventTime = :dmEventTime, dmEventDescr = :dmEventDescr "
		"WHERE id = :dbId";
	} else {
		queryStr = "INSERT INTO events (message_id, dmEventTime, "
		    "dmEventDescr) VALUES (:dmId, :dmEventTime, :dmEventDescr)";
	}

	if (!query.prepare(queryStr)) {
		qDebug() << "Error: msgsInsertUpdateMessageEvent"
		    << query.lastError();
		return false;
	}
	query.bindValue(":dmId", dmId);
	query.bindValue(":dmEventTime", dmEventTime);
	query.bindValue(":dmEventDescr", dmEventDescrType);
	if (dbId != 0) {
	    query.bindValue(":dbId", dbId);
	}

	if (query.exec()) {
		return true;
	} else {
		qDebug() << "Error: msgsInsertUpdateMessageEvent"
		    << query.lastError();
		return false;
	}
}

/* ========================================================================= */
/*
 * Insert message envelope into messages table
 */
bool MessageDb::msgsInsertMessageEnvelope(int dmId,
    const QString &_origin, const QString &dbIDSender,
    const QString &dmSender, const QString &dmSenderAddress,
    int dmSenderType, const QString &dmRecipient,
    const QString &dmRecipientAddress,
    const QString &dmAmbiguousRecipient,
    const QString &dmSenderOrgUnit, const QString &dmSenderOrgUnitNum,
    const QString &dbIDRecipient, const QString &dmRecipientOrgUnit,
    const QString &dmRecipientOrgUnitNum, const QString &dmToHands,
    const QString &dmAnnotation, const QString &dmRecipientRefNumber,
    const QString &dmSenderRefNumber, const QString &dmRecipientIdent,
    const QString &dmSenderIdent, const QString &dmLegalTitleLaw,
    const QString &dmLegalTitleYear, const QString &dmLegalTitleSect,
    const QString &dmLegalTitlePar, const QString &dmLegalTitlePoint,
    bool dmPersonalDelivery, bool dmAllowSubstDelivery,
    const QString &dmQTimestamp, const QString &dmDeliveryTime,
    const QString &dmAcceptanceTime, int dmMessageStatus,
    int dmAttachmentSize, const QString &_dmType, const QString messtype)
/* ========================================================================= */
{

	QSqlQuery query(m_db);

	QString queryStr = "INSERT INTO messages ("
	    "dmID, _origin, dbIDSender, dmSender, "
	    "dmSenderAddress, dmSenderType, dmRecipient, "
	    "dmRecipientAddress, dmAmbiguousRecipient, dmSenderOrgUnit, "
	    "dmSenderOrgUnitNum, dbIDRecipient, dmRecipientOrgUnit, "
	    "dmRecipientOrgUnitNum, dmToHands, dmAnnotation, "
	    "dmRecipientRefNumber, dmSenderRefNumber, dmRecipientIdent, "
	    "dmSenderIdent, dmLegalTitleLaw, dmLegalTitleYear, "
	    "dmLegalTitleSect, dmLegalTitlePar, dmLegalTitlePoint, "
	    "dmPersonalDelivery, dmAllowSubstDelivery, dmQTimestamp, "
	    "dmDeliveryTime, dmAcceptanceTime, dmMessageStatus, "
	    "dmAttachmentSize, _dmType"
	    ") VALUES ("
	    ":dmId, :_origin, :dbIDSender, :dmSender, "
	    ":dmSenderAddress, :dmSenderType, :dmRecipient, "
	    ":dmRecipientAddress, :dmAmbiguousRecipient, :dmSenderOrgUnit, "
	    ":dmSenderOrgUnitNum, :dbIDRecipient, :dmRecipientOrgUnit, "
	    ":dmRecipientOrgUnitNum, :dmToHands, :dmAnnotation, "
	    ":dmRecipientRefNumber, :dmSenderRefNumber, :dmRecipientIdent, "
	    ":dmSenderIdent, :dmLegalTitleLaw, :dmLegalTitleYear, "
	    ":dmLegalTitleSect, :dmLegalTitlePar, :dmLegalTitlePoint,"
	    ":dmPersonalDelivery, :dmAllowSubstDelivery, :dmQTimestamp, "
	    ":dmDeliveryTime, :dmAcceptanceTime, :dmMessageStatus, "
	    ":dmAttachmentSize, :_dmType"
	    ")";

	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	query.bindValue(":_origin", _origin);
	query.bindValue(":dbIDSender", dbIDSender);
	query.bindValue(":dmSender", dmSender);
	query.bindValue(":dmSenderAddress", dmSenderAddress);
	query.bindValue(":dmSenderType", dmSenderType);
	query.bindValue(":dmRecipient", dmRecipient);
	query.bindValue(":dmRecipientAddress", dmRecipientAddress);
	query.bindValue(":dmAmbiguousRecipient", dmAmbiguousRecipient);
	query.bindValue(":dmSenderOrgUnit", dmSenderOrgUnit);
	query.bindValue(":dmSenderOrgUnitNum", dmSenderOrgUnitNum);
	query.bindValue(":dbIDRecipient", dbIDRecipient);
	query.bindValue(":dmRecipientOrgUnit", dmRecipientOrgUnit);
	query.bindValue(":dmRecipientOrgUnitNum", dmRecipientOrgUnitNum);
	query.bindValue(":dmToHands", dmToHands);
	query.bindValue(":dmAnnotation", dmAnnotation);
	query.bindValue(":dmRecipientRefNumber", dmRecipientRefNumber);
	query.bindValue(":dmSenderRefNumber", dmSenderRefNumber);
	query.bindValue(":dmRecipientIdent", dmRecipientIdent);
	query.bindValue(":dmSenderIdent", dmSenderIdent);
	query.bindValue(":dmLegalTitleLaw", dmLegalTitleLaw);
	query.bindValue(":dmLegalTitleYear", dmLegalTitleYear);
	query.bindValue(":dmLegalTitleSect", dmLegalTitleSect);
	query.bindValue(":dmLegalTitlePar", dmLegalTitlePar);
	query.bindValue(":dmLegalTitlePoint", dmLegalTitlePoint);
	query.bindValue(":dmPersonalDelivery", dmPersonalDelivery);
	query.bindValue(":dmAllowSubstDelivery", dmAllowSubstDelivery);
	query.bindValue(":dmQTimestamp", dmQTimestamp);
	query.bindValue(":dmDeliveryTime", dmDeliveryTime);
	query.bindValue(":dmAcceptanceTime", dmAcceptanceTime);
	query.bindValue(":dmMessageStatus", dmMessageStatus);
	query.bindValue(":dmAttachmentSize", dmAttachmentSize);
	query.bindValue(":_dmType", _dmType);

	if (!query.exec()) {
		qDebug() << "Insert messages error:"
		    << query.lastError();
		return false;
	}

	queryStr = "INSERT INTO supplementary_message_data ("
	    "message_id, message_type, read_locally, download_date, custom_data"
	    ") VALUES (:dmId, :message_type, :read_locally, :download_date,"
	    ":custom_data)";

	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}

	QDateTime current = QDateTime::currentDateTime();
	QString dmDownloadTime = qDateTimeToDbFormat(current);

	query.bindValue(":dmId", dmId);
	if (messtype == "received") {
		query.bindValue(":message_type", 1);
	} else {
		query.bindValue(":message_type", 2);
	}
	query.bindValue(":read_locally", false);
	query.bindValue(":download_date", dmDownloadTime);
	query.bindValue(":custom_data", "null");

	if (!query.exec()) {
		qDebug() << "Insert supplementary_message_data error:"
		    << query.lastError();
		return false;
	}

	return msgSetProcessState(dmId, UNSETTLED, true);
}


/* ========================================================================= */
/*
 * Update exist message envelope/supplementary data in db
 */
bool MessageDb::msgsUpdateMessageEnvelope(int dmId,
    const QString &_origin, const QString &dbIDSender,
    const QString &dmSender, const QString &dmSenderAddress,
    int dmSenderType, const QString &dmRecipient,
    const QString &dmRecipientAddress,
    const QString &dmAmbiguousRecipient,
    const QString &dmSenderOrgUnit, const QString &dmSenderOrgUnitNum,
    const QString &dbIDRecipient, const QString &dmRecipientOrgUnit,
    const QString &dmRecipientOrgUnitNum, const QString &dmToHands,
    const QString &dmAnnotation, const QString &dmRecipientRefNumber,
    const QString &dmSenderRefNumber, const QString &dmRecipientIdent,
    const QString &dmSenderIdent, const QString &dmLegalTitleLaw,
    const QString &dmLegalTitleYear, const QString &dmLegalTitleSect,
    const QString &dmLegalTitlePar, const QString &dmLegalTitlePoint,
    bool dmPersonalDelivery, bool dmAllowSubstDelivery,
    const QString &dmQTimestamp, const QString &dmDeliveryTime,
    const QString &dmAcceptanceTime, int dmMessageStatus,
    int dmAttachmentSize, const QString &_dmType,
    const QString messtype)
/* ========================================================================= */
{
	QSqlQuery query(m_db);

	QString queryStr = "UPDATE messages SET "
	    "_origin = :_origin, "
	    "dbIDSender = :dbIDSender, dmSender = :dmSender, "
	    "dmSenderAddress = :dmSenderAddress, "
	    "dmSenderType = :dmSenderType, "
	    "dmRecipient = :dmRecipient, "
	    "dmRecipientAddress = :dmRecipientAddress, "
	    "dmAmbiguousRecipient = :dmAmbiguousRecipient, "
	    "dmSenderOrgUnit = :dmSenderOrgUnit, "
	    "dmSenderOrgUnitNum = :dmSenderOrgUnitNum, "
	    "dbIDRecipient = :dbIDRecipient, "
	    "dmRecipientOrgUnit = :dmRecipientOrgUnit, "
	    "dmRecipientOrgUnitNum = :dmRecipientOrgUnitNum, "
	    "dmToHands = :dmToHands, dmAnnotation = :dmAnnotation, "
	    "dmRecipientRefNumber = :dmRecipientRefNumber, "
	    "dmSenderRefNumber = :dmSenderRefNumber, "
	    "dmRecipientIdent = :dmRecipientIdent, "
	    "dmSenderIdent = :dmSenderIdent, "
	    "dmLegalTitleLaw = :dmLegalTitleLaw, "
	    "dmLegalTitleYear = :dmLegalTitleYear, "
	    "dmLegalTitleSect = :dmLegalTitleSect, "
	    "dmLegalTitlePar = :dmLegalTitlePar, "
	    "dmLegalTitlePoint = :dmLegalTitlePoint, "
	    "dmPersonalDelivery = :dmPersonalDelivery, "
	    "dmAllowSubstDelivery = :dmAllowSubstDelivery, "
	    "dmQTimestamp = :dmQTimestamp, "
	    "dmDeliveryTime = :dmDeliveryTime, "
	    "dmAcceptanceTime = :dmAcceptanceTime, "
	    "dmMessageStatus = :dmMessageStatus, "
	    "dmAttachmentSize = :dmAttachmentSize, "
	    "_dmType = :_dmType WHERE dmID = :dmId";

	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	query.bindValue(":_origin", _origin);
	query.bindValue(":dbIDSender", dbIDSender);
	query.bindValue(":dmSender", dmSender);
	query.bindValue(":dmSenderAddress", dmSenderAddress);
	query.bindValue(":dmSenderType", dmSenderType);
	query.bindValue(":dmRecipient", dmRecipient);
	query.bindValue(":dmRecipientAddress", dmRecipientAddress);
	query.bindValue(":dmAmbiguousRecipient", dmAmbiguousRecipient);
	query.bindValue(":dmSenderOrgUnit", dmSenderOrgUnit);
	query.bindValue(":dmSenderOrgUnitNum", dmSenderOrgUnitNum);
	query.bindValue(":dbIDRecipient", dbIDRecipient);
	query.bindValue(":dmRecipientOrgUnit", dmRecipientOrgUnit);
	query.bindValue(":dmRecipientOrgUnitNum", dmRecipientOrgUnitNum);
	query.bindValue(":dmToHands", dmToHands);
	query.bindValue(":dmAnnotation", dmAnnotation);
	query.bindValue(":dmRecipientRefNumber", dmRecipientRefNumber);
	query.bindValue(":dmSenderRefNumber", dmSenderRefNumber);
	query.bindValue(":dmRecipientIdent", dmRecipientIdent);
	query.bindValue(":dmSenderIdent", dmSenderIdent);
	query.bindValue(":dmLegalTitleLaw", dmLegalTitleLaw);
	query.bindValue(":dmLegalTitleYear", dmLegalTitleYear);
	query.bindValue(":dmLegalTitleSect", dmLegalTitleSect);
	query.bindValue(":dmLegalTitlePar", dmLegalTitlePar);
	query.bindValue(":dmLegalTitlePoint", dmLegalTitlePoint);
	query.bindValue(":dmPersonalDelivery", dmPersonalDelivery);
	query.bindValue(":dmAllowSubstDelivery", dmAllowSubstDelivery);
	query.bindValue(":dmQTimestamp", dmQTimestamp);
	query.bindValue(":dmDeliveryTime", dmDeliveryTime);
	query.bindValue(":dmAcceptanceTime", dmAcceptanceTime);
	query.bindValue(":dmMessageStatus", dmMessageStatus);
	query.bindValue(":dmAttachmentSize", dmAttachmentSize);
	query.bindValue(":_dmType", _dmType);

	if (!query.exec()) {
		qDebug() << "Update messages error:" << query.lastError();
		return false;
	}

	queryStr = "UPDATE supplementary_message_data SET "
	    "message_type = :message_type, read_locally = :read_locally, "
	    "custom_data = :custom_data "
	    "WHERE message_id = :dmId";

	if (!query.prepare(queryStr)) {

	}

	query.bindValue(":dmId", dmId);
	if (messtype == "received") {
		query.bindValue(":message_type", 1);
	} else {
		query.bindValue(":message_type", 2);
	}
	query.bindValue(":read_locally", true);
	query.bindValue(":custom_data", "null");

	if (query.exec()) {
		return true;
	} else {
		qDebug() << "Update supplementary_message_data error:"
		    << query.lastError();
		return false;
	}
}



/* ========================================================================= */
/*
 * Update exist message envelope delivery info in db.
 */
bool MessageDb::msgsUpdateMessageDeliveryInfo(int dmId,
    const QString &dmDeliveryTime, const QString &dmAcceptanceTime,
    int dmMessageStatus)
/* ========================================================================= */
{
	QSqlQuery query(m_db);

	QString queryStr = "UPDATE messages SET "
	    "dmDeliveryTime = :dmDeliveryTime, "
	    "dmAcceptanceTime = :dmAcceptanceTime, "
	    "dmMessageStatus = :dmMessageStatus "
	    "WHERE dmID = :dmId";

	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
		qDebug() << "Update messages error:" << query.lastError();
		return false;
	}

	query.bindValue(":dmId", dmId);
	query.bindValue(":dmDeliveryTime", dmDeliveryTime);
	query.bindValue(":dmAcceptanceTime", dmAcceptanceTime);
	query.bindValue(":dmMessageStatus", dmMessageStatus);

	if (!query.exec()) {
		qDebug() << "Update messages error:" << query.lastError();
		return false;
	}

	return true;
}


/* ========================================================================= */
/*
 * Close database file.
 */
void MessageDb::closeDb(void)
/* ========================================================================= */
{
	m_db.close();
}


/* ========================================================================= */
/*
 * Copy db.
 */
bool MessageDb::copyDb(const QString &newFileName)
/* ========================================================================= */
{
	bool copy_ret, open_ret;

	/* Close database. */
	m_db.close();

	/* Backup old file name. */
	QString oldFileName = fileName();
	qDebug() << oldFileName << "-copy->" << newFileName;

	/* Fail if target equals the source. */
	/* TODO -- Perform a more reliable check than string comparison. */
	if (oldFileName == newFileName) {
		return false;
	}

	/* Erase target if exists. */
	QFile::remove(newFileName);

	/* Copy database file. */
	copy_ret = QFile::copy(oldFileName, newFileName);

	/* Open database. */
	open_ret = openDb(copy_ret ? newFileName : oldFileName);
	Q_ASSERT(open_ret);
	if (!open_ret) {
		qDebug() << "File" << (copy_ret ? newFileName : oldFileName)
		    << "could not be opened.";
		/* TODO -- qFatal() ? */
	}

	return copy_ret;
}


/* ========================================================================= */
/*
 * Move db.
 */
bool MessageDb::moveDb(const QString &newFileName)
/* ========================================================================= */
{
	bool move_ret, open_ret;

	/* Close database. */
	m_db.close();

	/* Backup old file name. */
	QString oldFileName = fileName();
	qDebug() << oldFileName << "-move->" << newFileName;

	/* Fail if target equals the source. */
	/* TODO -- Perform a more reliable check than string comparison. */
	if (oldFileName == newFileName) {
		return false;
	}

	/* Erase target if exists. */
	QFile::remove(newFileName);

	/* Move database file. */
	move_ret = QFile::rename(oldFileName, newFileName);

	/* Open database. */
	open_ret = openDb(move_ret ? newFileName : oldFileName);
	Q_ASSERT(open_ret);
	if (!open_ret) {
		qDebug() << "File" << (move_ret ? newFileName : oldFileName)
		    << "could not be opened.";
		/* TODO -- qFatal() ? */
	}

	return move_ret;
}


/* ========================================================================= */
/*
 * Re-open a different database file.
 */
bool MessageDb::reopenDb(const QString &newFileName)
/* ========================================================================= */
{
	bool reopen_ret, open_ret;

	/* Close database. */
	m_db.close();

	/* Backup old file name. */
	QString oldFileName = fileName();
	qDebug() << oldFileName << "-reopen->" << newFileName;

	/* Fail if target equals the source. */
	/* TODO -- Perform a more reliable check than string comparison. */
	if (oldFileName == newFileName) {
		return false;
	}

	/* Erase target if exists. */
	QFile::remove(newFileName);

	/* Open new database file. */
	reopen_ret = openDb(newFileName);

	/* Open database. */
	if (!reopen_ret) {
		open_ret = openDb(oldFileName);
		Q_ASSERT(open_ret);
		if (!open_ret) {
			qDebug() << "File" << oldFileName
			    << "could not be opened.";
			/* TODO -- qFatal() ? */
		}
	}

	return reopen_ret;
}


/* ========================================================================= */
/*
 * Create empty tables if tables do not already exist.
 */
void MessageDb::createEmptyMissingTables(void)
/* ========================================================================= */
{
	bool ret;

	if (!msgsTbl.existsInDb(this->m_db)) {
		ret = msgsTbl.createEmpty(this->m_db);
		Q_ASSERT(ret); /* TODO -- Proper check and recovery? */
	}
	if (!flsTbl.existsInDb(this->m_db)) {
		ret = flsTbl.createEmpty(this->m_db);
		Q_ASSERT(ret); /* TODO -- Proper check and recovery? */
	}
	if (!hshsTbl.existsInDb(this->m_db)) {
		ret = hshsTbl.createEmpty(this->m_db);
		Q_ASSERT(ret); /* TODO -- Proper check and recovery? */
	}
	if (!evntsTbl.existsInDb(this->m_db)) {
		ret = evntsTbl.createEmpty(this->m_db);
		Q_ASSERT(ret); /* TODO -- Proper check and recovery? */
	}
	if (!rwmsgdtTbl.existsInDb(this->m_db)) {
		ret = rwmsgdtTbl.createEmpty(this->m_db);
		Q_ASSERT(ret); /* TODO -- Proper check and recovery? */
	}
	if (!rwdlvrinfdtTbl.existsInDb(this->m_db)) {
		ret = rwdlvrinfdtTbl.createEmpty(this->m_db);
		Q_ASSERT(ret); /* TODO -- Proper check and recovery? */
	}
	if (!smsgdtTbl.existsInDb(this->m_db)) {
		ret = smsgdtTbl.createEmpty(this->m_db);
		Q_ASSERT(ret); /* TODO -- Proper check and recovery? */
	}
	if (!crtdtTbl.existsInDb(this->m_db)) {
		ret = crtdtTbl.createEmpty(this->m_db);
		Q_ASSERT(ret); /* TODO -- Proper check and recovery? */
	}
	if (!msgcrtdtTbl.existsInDb(this->m_db)) {
		ret = msgcrtdtTbl.createEmpty(this->m_db);
		Q_ASSERT(ret); /* TODO -- Proper check and recovery? */
	}
	if (!prcstTbl.existsInDb(this->m_db)) {
		ret = prcstTbl.createEmpty(this->m_db);
		Q_ASSERT(ret); /* TODO -- Proper check and recovery? */
	}

}


/* ========================================================================= */
/*
 * Returns whether message is verified.
 */
bool MessageDb::msgsVerified(int dmId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT "
	    "is_verified"
	    " FROM messages WHERE "
	    "dmID = :dmId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive() &&
	    query.first() && query.isValid()) {
		return query.value(0).toBool();
	}

	return false;
}


/* ========================================================================= */
/*
 * Set the verification result.
 */
bool MessageDb::msgsSetVerified(int dmId, bool verified)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	debugFuncCall();

	queryStr = "UPDATE messages "
	    "SET is_verified = :verified WHERE "
	    "dmID = :dmId";
	qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":verified", verified);
	query.bindValue(":dmId", dmId);
	return query.exec();
}


/* ========================================================================= */
/*
 * Get process state of received message
 */
int MessageDb::msgGetProcessState(int dmId)
/* ========================================================================= */
{
	debugFuncCall();

	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT state FROM process_state WHERE message_id = :dmId";

	if (!query.prepare(queryStr)) {
		return -1;
	}

	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive() &&
	    query.first() && query.isValid()) {
		return query.value(0).toInt();
	}

	return -1;
}


/* ========================================================================= */
/*
 * Set process state of received message
 */
bool MessageDb::msgSetProcessState(int dmId, int state, bool insert)
/* ========================================================================= */
{
	debugFuncCall();

	QSqlQuery query(m_db);
	QString queryStr;

	if (insert) {
		queryStr = "INSERT INTO process_state ("
		    "message_id, state) VALUES (:dmId, :state)";
	} else {
		queryStr = "UPDATE process_state SET state = :state WHERE "
		    "message_id = :dmId";
	}

	if (!query.prepare(queryStr)) {
		return false;
	}

	query.bindValue(":dmId", dmId);
	query.bindValue(":state", state);
	return query.exec();
}


/* ========================================================================= */
/*
 * Returns verification date (in localtime).
 */
QDateTime MessageDb::msgsVerificationDate(int dmId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	if (GlobPreferences::DOWNLOAD_DATE ==
	    globPref.certificate_validation_date) {

		queryStr = "SELECT "
		    "download_date"
		    " FROM supplementary_message_data WHERE "
		    "message_id = :dmId";
		//qDebug() << queryStr;
		if (!query.prepare(queryStr)) {
			/* TODO -- Handle error. */
		}
		query.bindValue(":dmId", dmId);
		if (query.exec() && query.isActive() &&
		    query.first() && query.isValid()) {
			// qDebug() << "dateTime" << query.value(0).toString();
			QDateTime dateTime =
			    dateTimeFromDbFormat(query.value(0).toString());

			if (dateTime.isValid()) {
				return dateTime;
			}
		}
	}

	return QDateTime::currentDateTime();
}


/* ========================================================================= */
/*
 * Returns time stamp in DER format.
 */
QByteArray MessageDb::msgsTimestampDER(int dmId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT "
	    "dmQTimestamp"
	    " FROM messages WHERE "
	    "dmID = :dmId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive() &&
	    query.first() && query.isValid()) {
		QByteArray byteArray = query.value(0).toByteArray();
		if (byteArray.isEmpty()) {
			return QByteArray();
		}

		return QByteArray::fromBase64(byteArray);
	}

	return QByteArray();
}


/* ========================================================================= */
/*
 * Delete message records from db
 */
bool MessageDb::msgsDeleteMessageData(int dmId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	/* Delete hash from hashes table */
	queryStr = "DELETE FROM hashes WHERE message_id = :message_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error1: msgsDeleteMessageData" << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	if (!query.exec()) {
		qDebug() << "Error1: msgsDeleteMessageData" << query.lastError();
		return false;
	}

	/* Delete file(s) from files table */
	queryStr = "DELETE FROM files WHERE message_id = :message_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error2: msgsDeleteMessageData" << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	if (!query.exec()) {
		qDebug() << "Error2: msgsDeleteMessageData" << query.lastError();
		return false;
	}

	/* Delete event(s) from events table */
	queryStr = "DELETE FROM events WHERE message_id = :message_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error3: msgsDeleteMessageData" << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	if (!query.exec()) {
		qDebug() << "Error3: msgsDeleteMessageData" << query.lastError();
		return false;
	}

	/* Delete raw message data from raw_message_data table */
	queryStr= "DELETE FROM raw_message_data WHERE message_id = :message_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error4: msgsDeleteMessageData" << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	if (!query.exec()) {
		qDebug() << "Error4: msgsDeleteMessageData" << query.lastError();
		return false;
	}

	/* Delete raw info data from raw_delivery_info_data table */
	queryStr = "DELETE FROM raw_delivery_info_data WHERE "
	    "message_id = :message_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error5: msgsDeleteMessageData" << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	if (!query.exec()) {
		qDebug() << "Error5: msgsDeleteMessageData" << query.lastError();
		return false;
	}

	/* Delete supplementary from supplementary_message_data table */
	queryStr = "DELETE FROM supplementary_message_data WHERE "
	    "message_id = :message_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error6: msgsDeleteMessageData" << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	if (!query.exec()) {
		qDebug() << "Error6: msgsDeleteMessageData" << query.lastError();
		return false;
	}

	/* Select certificate_id from message_certificate_data table */
	int certificate_id = 0;
	queryStr = "SELECT certificate_id FROM message_certificate_data WHERE "
	    "message_id = :message_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error7: msgsDeleteMessageData" << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	if (!query.exec()) {
		qDebug() << "Error7: msgsDeleteMessageData" << query.lastError();
		return false;
	} else {
		query.first();
		if (query.isValid()) {
			certificate_id = query.value(0).toInt();
		}
	}
	/* Delete certificate reference from message_certificate_data table */
	queryStr = "DELETE FROM message_certificate_data WHERE "
	    "message_id = :message_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error8: msgsDeleteMessageData" << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	if (!query.exec()) {
		qDebug() << "Error8: msgsDeleteMessageData" << query.lastError();
		return false;
	}

	/* Select certificate id from message_certificate_data table */
	bool deleteCert = false;
	queryStr = "SELECT count(*) FROM message_certificate_data WHERE "
	    "certificate_id = :certificate_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error9: msgsDeleteMessageData" << query.lastError();
		return false;
	}
	query.bindValue(":certificate_id", certificate_id);
	if (query.exec() && query.isActive()) {
		query.first();
		if (query.isValid()) {
			if (query.value(0).toInt() > 0) {
				deleteCert = false;
			} else {
				deleteCert = true;
			}
		}
	}

	/* Delete certificate data from certificate_data table if no messages */
	if (deleteCert) {
		queryStr = "DELETE FROM certificate_data WHERE "
		    "id = :certificate_id";
		if (!query.prepare(queryStr)) {
			qDebug() << "Error9: msgsDeleteMessageData"
			    << query.lastError();
			return false;
		}
		query.bindValue(":certificate_id", certificate_id);
		if (!query.exec()) {
			qDebug() << "Error9: msgsDeleteMessageData"
			    << query.lastError();
			return false;
		}
	}

	/* Delete process state information from process_state table.*/
	queryStr = "DELETE FROM process_state WHERE "
	    "message_id = :message_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error10: msgsDeleteMessageData" << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	if (!query.exec()) {
		qDebug() << "Error10: msgsDeleteMessageData" << query.lastError();
		return false;
	}

	/* Delete message from messages table */
	queryStr = "DELETE FROM messages WHERE dmID = :message_id";
	if (!query.prepare(queryStr)) {
		qDebug() << "Error11: msgsDeleteMessageData" << query.lastError();
		return false;
	}
	query.bindValue(":message_id", dmId);
	if (!query.exec()) {
		qDebug() << "Error11: msgsDeleteMessageData" << query.lastError();
		return false;
	}

	return true;
}


/* ========================================================================= */
/*
 * Get raw message data from raw_message_data table.
 */
QString MessageDb::msgsMessageBase64(int dmId) const
/* ========================================================================= */
{
	debugFuncCall();

	QSqlQuery query(m_db);
	QString queryStr;

	queryStr =
	    "SELECT data FROM raw_message_data WHERE message_id = :dmId";

	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}

	query.bindValue(":dmId", dmId);

	if (query.exec() && query.isActive()) {
		query.first();
		return query.value(0).toString();
	}
	return QString();
}


/* ========================================================================= */
/*
 * Get message data in DER format.
 */
QByteArray MessageDb::msgsMessageDER(int dmId) const
/* ========================================================================= */
{
	debugFuncCall();

	return QByteArray::fromBase64(msgsMessageBase64(dmId).toUtf8());
}


/* ========================================================================= */
/*
 * Get raw delivery info from raw_delivery_info_data table.
 */
QString MessageDb::msgsGetDeliveryInfoRaw(int dmId)
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT data FROM raw_delivery_info_data WHERE message_id = :dmId";

	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}

	query.bindValue(":dmId", dmId);

	if (query.exec() && query.isActive()) {
		query.first();
		return query.value(0).toString();
	}
	return "";
}



/* ========================================================================= */
/*
 * Read data from supplementary message data table.
 */
QJsonDocument MessageDb::smsgdCustomData(int msgId) const
/* ========================================================================= */
{
	QJsonDocument jsonDoc;
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT "
	    "custom_data"
	    " FROM supplementary_message_data WHERE "
	    "message_id = :msgId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":msgId", msgId);
	if (query.exec() && query.isActive()) {
		query.first();
		if (query.isValid()) {
			jsonDoc = QJsonDocument::fromJson(
			    query.value(0).toByteArray());
		}
	}

	return jsonDoc;
}


/* ========================================================================= */
/*
 * Certificates related to given message.
 */
QList<QSslCertificate> MessageDb::msgCerts(int dmId) const
/* ========================================================================= */
{
	QList<QSslCertificate> certList;
	QSqlQuery query(m_db);
	QString queryStr;
	QList<int> certIds;

	queryStr = "SELECT "
	    "certificate_id"
	    " FROM message_certificate_data WHERE "
	    "message_id = :dmId";
	//qDebug() << queryStr;
	if (!query.prepare(queryStr)) {
		/* TODO -- Handle error. */
	}
	query.bindValue(":dmId", dmId);
	if (query.exec() && query.isActive()) {
		query.first();
		if (query.isValid()) {
			bool ok;
			int ret;
			ret = query.value(0).toInt(&ok);
			Q_ASSERT(ok);
			if (ok) {
				certIds.append(ret);
			}
		}
	}

	if (certIds.size() > 0) {
		queryStr = "SELECT "
		    "der_data"
		    " FROM certificate_data WHERE ";
		for (int i = 0; i < (certIds.size() - 1); ++i) {
			queryStr += "(id = ?) or ";
		}
		queryStr += "(id = ?)";
		//qDebug() << queryStr;
		if (!query.prepare(queryStr)) {
			/* TODO -- Handle error. */
		}
		for (int i = 0; i < certIds.size(); ++i) {
			query.addBindValue(certIds[i]);
		}
		if (query.exec() && query.isActive()) {
			query.first();
			while (query.isValid()) {
				QList<QSslCertificate> certs =
				    QSslCertificate::fromData(
				        QByteArray::fromBase64(
				            query.value(0).toByteArray()),
				        QSsl::Der);
				Q_ASSERT(1 == certs.size());

				certList.append(certs.first());

				query.next();
			}
		}
	}

	return certList;
}


/* ========================================================================= */
/*
 * Check whether message signature was valid at given date
 *     (local time).
 */
bool MessageDb::msgCertValidAtDate(int dmId, const QDateTime &dateTime,
    bool ignoreMissingCrlCheck) const
/* ========================================================================= */
{
	debugFuncCall();

	QByteArray rawBytes = msgsMessageDER(dmId);
	Q_ASSERT(rawBytes.size() > 0);

	if (ignoreMissingCrlCheck) {
		logWarning("CRL check is not performed for message %d.\n",
		    dmId);
	}
	time_t utcTime = dateTime.toTime_t();

	return 1 == rawMsgVerifySignatureDate(
	    rawBytes.data(), rawBytes.size(), utcTime,
	    ignoreMissingCrlCheck ? 0 : 1);
}


/* ========================================================================= */
/*
 * Adds _dmType column.
 */
bool MessageDb::addDmtypeColumn(void)
/* ========================================================================= */
{
	if (false == m_db.isOpen()) {
		return false;
	}

	/*
	 * Create _dmType column if it does not exist.
	 */
	QSqlQuery query(m_db);
	query.prepare("SELECT _dmType FROM messages LIMIT 1");
	if (false == query.exec()) {
		query.clear();
		query.prepare("ALTER TABLE messages ADD COLUMN _dmType TEXT");
		query.exec();
	}

	return true;
}


/* ========================================================================= */
dbContainer::dbContainer(void)
/* ========================================================================= */
    : QMap<QString, MessageDb *>()
{
}


/* ========================================================================= */
dbContainer::~dbContainer(void)
/* ========================================================================= */
{
	QMap<QString, MessageDb *>::iterator i;

	for (i = this->begin(); i != this->end(); ++i) {
		delete i.value();
	}
}


/* ========================================================================= */
/*
 * Access/create+open message database related to item.
 */
MessageDb * dbContainer::accessMessageDb(const QString &key,
    const QString &locDir, bool testing)
/* ========================================================================= */
{
	MessageDb *db;

	/* Already opened. */
	if (this->find(key) != this->end()) {
		// qDebug() << key << "db found";
		return (*this)[key];
	}

	// qDebug() << "creating new" << key;
	db = new MessageDb(key);

	// qDebug() << "searching for file" << key << "in" << locDir;
	/* TODO -- Handle file name deviations! */
	// qDebug() << "opening";
	/*
	 * Test accounts have ___1 in their names, ___0 relates to standard
	 * accounts.
	 */
	db->openDb(constructDbFileName(key, locDir, testing));

	this->insert(key, db);
	return db;
}


/* ========================================================================= */
/*
 * Creates a copy of the current data base into a given new
 *     directory.
 */
bool dbContainer::copyMessageDb(MessageDb *db, const QString &newLocDir)
/* ========================================================================= */
{
	Q_ASSERT(0 != db);
	if (0 == db) {
		return false;
	}

	/* Find entry. */
	QMap<QString, MessageDb *>::iterator it = this->begin();
	while ((it != this->end()) && (it.value() != db)) {
		++it;
	}
	/* Must exist. */
	Q_ASSERT(this->end() != it);
	if (this->end() == it) {
		return false;
	}

	/* Get old and new file name. */
	QString oldFileName = db->fileName();
	QFileInfo fileInfo(oldFileName);
	QString newFileName =
	    newLocDir + QDir::separator() + fileInfo.fileName();

	/* Copy database. */
	return db->copyDb(newFileName);
}


/* ========================================================================= */
/*
 * Move message database into a new directory.
 */
bool dbContainer::moveMessageDb(MessageDb *db, const QString &newLocDir)
/* ========================================================================= */
{
	Q_ASSERT(0 != db);
	if (0 == db) {
		return false;
	}

	/* Find entry. */
	QMap<QString, MessageDb *>::iterator it = this->begin();
	while ((it != this->end()) && (it.value() != db)) {
		++it;
	}
	/* Must exist. */
	Q_ASSERT(this->end() != it);
	if (this->end() == it) {
		return false;
	}

	/* Get old and new file name. */
	QString oldFileName = db->fileName();
	QFileInfo fileInfo(oldFileName);
	QString newFileName =
	    newLocDir + QDir::separator() + fileInfo.fileName();

	/* Move database. */
	return db->moveDb(newFileName);
}


/* ========================================================================= */
/*
 * Re-open a new database file. The old file is left untouched.
 */
bool dbContainer::reopenMessageDb(MessageDb *db, const QString &newLocDir)
/* ========================================================================= */
{
	Q_ASSERT(0 != db);
	if (0 == db) {
		return false;
	}

	/* Find entry. */
	QMap<QString, MessageDb *>::iterator it = this->begin();
	while ((it != this->end()) && (it.value() != db)) {
		++it;
	}
	/* Must exist. */
	Q_ASSERT(this->end() != it);
	if (this->end() == it) {
		return false;
	}

	/* Get old and new file name. */
	QString oldFileName = db->fileName();
	QFileInfo fileInfo(oldFileName);
	QString newFileName =
	    newLocDir + QDir::separator() + fileInfo.fileName();

	/* Move database. */
	return db->reopenDb(newFileName);
}


/* ========================================================================= */
/*
 * Delete message db.
 */
bool dbContainer::deleteMessageDb(MessageDb * db)
/* ========================================================================= */
{
	Q_ASSERT(0 != db);
	if (0 == db) {
		return false;
	}

	/* Find entry. */
	QMap<QString, MessageDb *>::iterator it = this->begin();
	while ((it != this->end()) && (it.value() != db)) {
		++it;
	}
	/* Must exist. */
	Q_ASSERT(this->end() != it);
	if (this->end() == it) {
		return false;
	}

	/* Remove from container. */
	this->erase(it);

	/* Get file name. */
	QString fileName = db->fileName();

	/* Close database. */
	delete db;

	/* Delete file. */
	qDebug() << "Deleting database file" << fileName;

	if (!QFile::remove(fileName)) {
		qWarning() << "Failed deleting database file" << fileName;
		return false;
	}

	return true;
}


const QString dbContainer::dbDriverType("QSQLITE");


/* ========================================================================= */
/*
 * Check whether required SQL driver is present.
 */
bool dbContainer::dbDriverSupport(void)
/* ========================================================================= */
{
	QStringList driversList = QSqlDatabase::drivers();

	return driversList.contains(dbDriverType, Qt::CaseSensitive);
}


/* ========================================================================= */
/*
 * Creates the database name from supplied information.
 */
QString dbContainer::constructDbFileName(const QString &key,
    const QString &locDir, bool testing)
/* ========================================================================= */
{
	return locDir + QDir::separator() +
	    key + "___" + (testing ? "1" : "0") + ".db";
}


/* ========================================================================= */
/*
 * Return id of messages in database correspond with date interval.
 */
QList<int> MessageDb::msgsDateInterval(QDate fromDate,
    QDate toDate, bool sent) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;
	QList<int> dmIDs;

	if (sent) {
		queryStr = "SELECT dmID "
		    "FROM messages AS m LEFT JOIN supplementary_message_data "
		    "AS s ON (m.dmID = s.message_id) WHERE "
		    "message_type = 2 AND "
		    "(strftime('%Y-%m-%d', dmDeliveryTime) >= :fromDate) AND "
		    "(strftime('%Y-%m-%d', dmDeliveryTime) <= :toDate)";
	} else {
		queryStr = "SELECT dmID "
		    "FROM messages AS m LEFT JOIN supplementary_message_data "
		    "AS s ON (m.dmID = s.message_id) WHERE "
		    "message_type = 1 AND "
		    "(strftime('%Y-%m-%d', dmDeliveryTime) >= :fromDate) AND "
		    "(strftime('%Y-%m-%d', dmDeliveryTime) <= :toDate)";
	}

	if (!query.prepare(queryStr)) {
		qDebug() << "Select error:" << query.lastError();
	}

	query.bindValue(":fromDate", fromDate);
	query.bindValue(":toDate", toDate);

	if (query.exec()) {
		query.first();
		while (query.isValid()) {
			dmIDs.append(query.value(0).toString().toInt());
			query.next();
		}
	}


	return dmIDs;
}


/* ========================================================================= */
/*
 * Return some message items for export correspondence to html.
 */
QList<QString> MessageDb::getMsgForHtmlExport(int dmId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;
	QList<QString> messageItems;

	queryStr = "SELECT dmSender, dmRecipient, dmAnnotation, dmDeliveryTime "
	    "FROM messages WHERE dmID = :dmId";

	if (!query.prepare(queryStr)) {
		qDebug() << "Select error:" << query.lastError();
	}

	query.bindValue(":dmId", dmId);

	if (query.exec() && query.isActive()) {
		query.first();
		if (query.isValid()) {
			messageItems.append(query.value(0).toString());
			messageItems.append(query.value(1).toString());
			messageItems.append(query.value(2).toString());
			QDateTime dateTime =
			    dateTimeFromDbFormat(query.value(3).toString());
			messageItems.append(dateTime.toString("dd.MM.yyyy"));
			messageItems.append(dateTime.toString("hh:mm:ss"));
		}
	}

	return messageItems;
}


/* ========================================================================= */
/*
 * Return some message items for export correspondence to csv.
 */
QList<QString> MessageDb::getMsgForCsvExport(int dmId) const
/* ========================================================================= */
{
	QSqlQuery query(m_db);
	QString queryStr;
	QList<QString> messageItems;

	queryStr = "SELECT dmMessageStatus, _dmType, dmDeliveryTime, "
	    "dmAcceptanceTime, dmAnnotation, dmSender, dmSenderAddress, "
	    "dmRecipient, dmRecipientAddress, dmSenderOrgUnit, "
	    "dmSenderOrgUnitNum, dmRecipientOrgUnit, dmRecipientOrgUnitNum "
	    "FROM messages WHERE dmID = :dmId";

	if (!query.prepare(queryStr)) {
		qDebug() << "Select error:" << query.lastError();
	}

	query.bindValue(":dmId", dmId);

	if (query.exec() && query.isActive()) {
		query.first();
		if (query.isValid()) {
			messageItems.append(query.value(0).toString());
			messageItems.append(query.value(1).toString());
			messageItems.append(query.value(2).toString());
			messageItems.append(query.value(3).toString());
			messageItems.append(query.value(4).toString());
			messageItems.append(query.value(5).toString());
			messageItems.append(query.value(6).toString());
			messageItems.append(query.value(7).toString());
			messageItems.append(query.value(8).toString());
			messageItems.append(query.value(9).toString());
			messageItems.append(query.value(10).toString());
			messageItems.append(query.value(11).toString());
			messageItems.append(query.value(12).toString());
		}
	}

	return messageItems;
}
