/*
 * Copyright (C) 2014-2022 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 <QMenu>

#include "src/datovka_shared/log/log.h"
#include "src/global.h"
#include "src/gui/dlg_download_messages.h"
#include "src/gui/dlg_msg_search.h"
#include "src/gui/icon_container.h"
#include "src/gui/message_operations.h"
#include "src/io/message_db_set_container.h"
#include "src/io/message_db_set.h"
#include "src/io/tag_db.h"
#include "src/settings/accounts.h"
#include "src/views/table_home_end_filter.h"
#include "src/views/table_tab_ignore_filter.h"
#include "ui_dlg_msg_search.h"

/* Tooltip is generated for every item in the search result table. */
#define ENABLE_TOOLTIP true

#define BOX_ID_LEN 7

DlgMsgSearch::DlgMsgSearch(const QList<AcntIdDb> &acntIdDbList,
    const AcntId &acntId, MainWindow *mw, QWidget *parent,
    Qt::WindowFlags flags)
    : QDialog(parent, flags),
    m_ui(new (::std::nothrow) Ui::DlgMsgSearch),
    m_actionDownloadMessage(Q_NULLPTR),
    m_actionExportMessageZfo(Q_NULLPTR),
    m_actionExportAcceptanceInfoZfo(Q_NULLPTR),
    m_actionExportAcceptanceInfoPdf(Q_NULLPTR),
    m_actionExportEnvelopePdf(Q_NULLPTR),
    m_actionExportEnvelopePdfAndAttachments(Q_NULLPTR),
    m_actionEmailZfos(Q_NULLPTR),
    m_actionEmailAllAttachments(Q_NULLPTR),
    m_mw(mw),
    m_modelRowSelection(),
    m_acntIdDbList(acntIdDbList),
    m_foundListProxyModel(this),
    m_foundTableModel(this)
{
	m_ui->setupUi(this);
	/* Tab order is defined in UI file. */

	m_ui->filterLine->setToolTip(tr("Enter sought expression"));
	m_ui->filterLine->setClearButtonEnabled(true);

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

	m_ui->resultsTableView->setSelectionMode(
	    QAbstractItemView::ExtendedSelection);
	m_ui->resultsTableView->setSelectionBehavior(
	    QAbstractItemView::SelectRows);

	m_foundListProxyModel.setSortRole(MsgFoundModel::ROLE_PROXYSORT);
	m_foundListProxyModel.setSourceModel(&m_foundTableModel);
	{
		QList<int> columnList;
		columnList.append(MsgFoundModel::COL_ACCOUNT_ID);
		columnList.append(MsgFoundModel::COL_MESSAGE_ID);
		columnList.append(MsgFoundModel::COL_ANNOTATION);
		columnList.append(MsgFoundModel::COL_SENDER);
		columnList.append(MsgFoundModel::COL_RECIPIENT);
		columnList.append(MsgFoundModel::COL_DELIVERY_TIME);
		m_foundListProxyModel.setFilterKeyColumns(columnList);
	}
	m_ui->resultsTableView->setModel(&m_foundListProxyModel);

	m_ui->resultsTableView->setSortingEnabled(true);
	m_ui->resultsTableView->sortByColumn(MsgFoundModel::COL_ACCOUNT_ID,
	    Qt::AscendingOrder);

	m_ui->resultsTableView->setSquareColumnWidth(
	    MsgFoundModel::COL_ATTACH_DOWNLOADED);

	m_ui->resultsTableView->setEnabled(false);

	setupActions();
	initSearchWindow(acntId);
}

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

void DlgMsgSearch::checkInputFields(void)
{
	bool isAnyMsgTypeChecked = true;

	m_ui->searchPushButton->setEnabled(false);
	m_ui->tooManyFields->hide();

	/* is any message type checked? */
	if (!m_ui->searchRcvdMsgCheckBox->isChecked() &&
	    !m_ui->searchSntMsgCheckBox->isChecked()) {
		isAnyMsgTypeChecked = false;
	}

	const bool msgIdMissing = m_ui->msgIdLine->text().isEmpty();

	m_ui->subjectLine->setEnabled(msgIdMissing);
	m_ui->sndrBoxIdLine->setEnabled(msgIdMissing);
	m_ui->sndrNameLine->setEnabled(msgIdMissing);
	m_ui->sndrRefNumLine->setEnabled(msgIdMissing);
	m_ui->sndrFileMarkLine->setEnabled(msgIdMissing);
	m_ui->rcpntBoxIdLine->setEnabled(msgIdMissing);
	m_ui->rcpntNameLine->setEnabled(msgIdMissing);
	m_ui->rcpntRefNumLine->setEnabled(msgIdMissing);
	m_ui->rcpntFileMarkLine->setEnabled(msgIdMissing);
	m_ui->addressLine->setEnabled(msgIdMissing);
	m_ui->toHandsLine->setEnabled(msgIdMissing);
	m_ui->tagLine->setEnabled(msgIdMissing);
	m_ui->attachNameLine->setEnabled(msgIdMissing);

	if (!msgIdMissing) {
		/* Search via message ID. */
		m_ui->tagLine->clear();
	} else {
		/* Use either sender box ID or sender name for searching. */
		m_ui->sndrBoxIdLine->setEnabled(m_ui->sndrNameLine->text().isEmpty());
		m_ui->sndrNameLine->setEnabled(m_ui->sndrBoxIdLine->text().isEmpty());

		/* Use either recipient box ID or recipient name for searching. */
		m_ui->rcpntBoxIdLine->setEnabled(m_ui->rcpntNameLine->text().isEmpty());
		m_ui->rcpntNameLine->setEnabled(m_ui->rcpntBoxIdLine->text().isEmpty());
	}

	/* Search using message ID only. */
	if (!msgIdMissing) {
		/*
		 * Test if message ID to be a number.
		 * Exact match. A digit (\d), zero or more times (*).
		 */
		const QRegularExpression re("^\\d*$");
		const QString text = m_ui->msgIdLine->text();
		/* Test whether message ID is at least 3 characters. */
		m_ui->searchPushButton->setEnabled(isAnyMsgTypeChecked &&
		    (text.length() > 2) &&
		    (re.match(text).capturedLength() == text.length()));
		return;
	}

	/* Search according to supplied tag text. */
	const bool tagCorrect = m_ui->tagLine->text().isEmpty() ||
	    (m_ui->tagLine->text().length() > 2);

	/* Data-box ID must have 7 characters. */
	const bool boxIdCorrect =
	    (m_ui->sndrBoxIdLine->text().isEmpty() ||
	     (m_ui->sndrBoxIdLine->text().size() == BOX_ID_LEN)) &&
	    (m_ui->rcpntBoxIdLine->text().isEmpty() ||
	     (m_ui->rcpntBoxIdLine->text().size() == BOX_ID_LEN));

	/* only 3 fields can be set together */
	bool isNotFillManyFileds = true;

	const int itemsWithoutTag = filledInExceptTags();
	if (itemsWithoutTag > 3) {
		isNotFillManyFileds = false;
		m_ui->tooManyFields->show();
	} else if ((itemsWithoutTag < 1) && m_ui->tagLine->text().isEmpty()) {
		isNotFillManyFileds = false;
	}

	m_ui->searchPushButton->setEnabled(isAnyMsgTypeChecked &&
	    boxIdCorrect && tagCorrect && isNotFillManyFileds);
}

/*!
 * @brief Computes intersection between tag and envelope search result data.
 *
 * @param[in] envelData Envelope search result data.
 * @param[in] tagData Tag search data.
 * @param[in] messageDbSet Message database set.
 * @return Intersection data.
 */
static
QList<MessageDb::SoughtMsg> dataIntersect(
    const QList<MessageDb::SoughtMsg> &envelData, const QList<qint64> &tagData,
    MessageDbSet *messageDbSet)
{
	if (Q_UNLIKELY(messageDbSet == Q_NULLPTR)) {
		Q_ASSERT(0);
		return QList<MessageDb::SoughtMsg>();
	}

	QList<MessageDb::SoughtMsg> result;

	foreach (const qint64 msgId, tagData) {
		foreach (const MessageDb::SoughtMsg msg, envelData) {
			if (msg.mId.dmId() == msgId) {
				MessageDb::SoughtMsg msgData(
				    messageDbSet->msgsGetMsgDataFromId(msgId));
				if (msgData.mId.dmId() != -1) {
					result.append(msgData);
				}
			}
		}
	}

	return result;
}

/*!
 * @brief Obtains data from databases that match found tag data.
 *
 * @param[in] tagData Tag search data.
 * @param[in] messageDbSet Message database set.
 * @return Message data.
 */
static
QList<MessageDb::SoughtMsg> displayableTagData(const QList<qint64> &tagData,
    MessageDbSet *messageDbSet)
{
	if (Q_UNLIKELY(messageDbSet == Q_NULLPTR)) {
		Q_ASSERT(0);
		return QList<MessageDb::SoughtMsg>();
	}

	QList<MessageDb::SoughtMsg> result;

	foreach (const qint64 msgId, tagData) {
		MessageDb::SoughtMsg msgData(
		    messageDbSet->msgsGetMsgDataFromId(msgId));

		if (msgData.mId.dmId() != -1) {
			result.append(msgData);
		}
	}

	return result;
}

/*!
 * @brief Insert message detail data into model.
 *
 * @param[in,out] foundTableModel Table model to put message detail entries into.
 * @param[in]     accountDescr Username and related database set.
 * @param[in]     msgDataList Data list to serve as list of account identifiers.
 */
static
void setMessageDetails(MsgFoundModel &foundTableModel,
    const AcntIdDb &accountDescr,
    const QList<MessageDb::SoughtMsg> &msgDataList)
{
	foreach (const MessageDb::SoughtMsg &msgData, msgDataList) {
		MessageDb *messageDb =
		    accountDescr.messageDbSet()->constAccessMessageDb(
		        msgData.mId.deliveryTime());
		if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
			Q_ASSERT(0);
			continue;
		}
		foundTableModel.setMessageDetail(accountDescr, msgData.mId,
		    messageDb->descriptionHtml(msgData.mId.dmId(),
		        QString(), false));
	}
}

/*!
 * @brief Append message list to result table.
 *
 * @param[in,out] resultsTableView Table view.
 * @param[in,out] foundTableModel Table model.
 * @param[in]     accountDescr Username and related database set.
 * @param[in]     msgDataList Message data list.
 */
static
void appendMsgsToTable(QTableView &resultsTableView,
    MsgFoundModel &foundTableModel, const AcntIdDb &accountDescr,
    const QList<MessageDb::SoughtMsg> &msgDataList)
{
	resultsTableView.setEnabled(true);

	foundTableModel.appendData(accountDescr, msgDataList);
	if (ENABLE_TOOLTIP) {
		setMessageDetails(foundTableModel, accountDescr, msgDataList);
	}

	resultsTableView.resizeColumnsToContents();
	resultsTableView.horizontalHeader()->setStretchLastSection(true);
}

void DlgMsgSearch::searchMessages(void)
{
	debugSlotCall();

	m_foundTableModel.removeAllRows();
	m_ui->resultsTableView->setEnabled(false);

	/* Message envelope search results. */
	QList<MessageDb::SoughtMsg> envelResults;

	/* Tag search result. */
	QList<qint64> tagResults;

	/* Displayed results. */
	QList<MessageDb::SoughtMsg> resultsToBeDisplayed;

	/* Types of messages to search for. */
	enum MessageDirection msgType = MSG_ALL;
	if (m_ui->searchRcvdMsgCheckBox->isChecked() &&
	    m_ui->searchSntMsgCheckBox->isChecked()) {
		msgType = MSG_ALL;
	} else if (m_ui->searchRcvdMsgCheckBox->isChecked()) {
		msgType = MSG_RECEIVED;
	} else if (m_ui->searchSntMsgCheckBox->isChecked()) {
		msgType = MSG_SENT;
	}

	/* If tag data were supplied, get message ids from tag table. */
	const bool searchTags = !m_ui->tagLine->text().isEmpty();
	if (searchTags) {
		tagResults =
		    GlobInstcs::tagDbPtr->getMsgIdsContainSearchTagText(
		        m_ui->tagLine->text());
	}

	/* How many envelope fields (without tags) are supplied. */
	const int envelopeItems = filledInExceptTags();

	/* Fill search envelope items. */
	Isds::Envelope searchEnvelope;
	searchEnvelope.setDmId(m_ui->msgIdLine->text().isEmpty() ? -1 :
	    m_ui->msgIdLine->text().toLongLong());
	searchEnvelope.setDmAnnotation(m_ui->subjectLine->text());
	searchEnvelope.setDbIDSender(m_ui->sndrBoxIdLine->text());
	searchEnvelope.setDmSender(m_ui->sndrNameLine->text());
	searchEnvelope.setDmSenderAddress(m_ui->addressLine->text());
	searchEnvelope.setDbIDRecipient(m_ui->rcpntBoxIdLine->text());
	searchEnvelope.setDmRecipient(m_ui->rcpntNameLine->text());
	searchEnvelope.setDmSenderRefNumber(m_ui->sndrRefNumLine->text());
	searchEnvelope.setDmSenderIdent(m_ui->sndrFileMarkLine->text());
	searchEnvelope.setDmRecipientRefNumber(m_ui->rcpntRefNumLine->text());
	searchEnvelope.setDmRecipientIdent(m_ui->rcpntFileMarkLine->text());
	searchEnvelope.setDmToHands(m_ui->toHandsLine->text());

	/* Number of accounts in which to search for messages in. */
	const int dbCount = m_ui->searchAllAcntCheckBox->isChecked() ?
	    m_acntIdDbList.count() : 1;

	/* Search in accounts. */
	for (int i = 0; i < dbCount; ++i) {

		/* Get currently selected account if processing only one account. */
		const AcntIdDb &acntIdDb((dbCount == 1) ?
		    m_acntIdDbList.at(m_ui->accountComboBox->currentIndex()) :
		    m_acntIdDbList.at(i));

		envelResults.clear();
		resultsToBeDisplayed.clear();

		if (envelopeItems > 0) {
			/* Search in envelope envelope data. */
			envelResults = acntIdDb.messageDbSet()->msgsSearch(
			    searchEnvelope, msgType,
			    m_ui->attachNameLine->text(),
			    m_ui->logicalAndRelationCheckBox->isChecked());
		}

		if (searchTags && !tagResults.isEmpty() && !envelResults.isEmpty()) {
			/*
			 * Tag data were supplied and some envelope data also.
			 * Intersection of tag and envelope search results is
			 * needed.
			 */
			resultsToBeDisplayed = dataIntersect(envelResults,
			    tagResults, acntIdDb.messageDbSet());
		} else if (!searchTags && !envelResults.isEmpty()) {
			/*
			 * No tag data were supplied. Envelope were supplied.
			 * Use envelope search results only.
			 */
			resultsToBeDisplayed = envelResults;
		} else if (searchTags && !tagResults.isEmpty()) {
			/*
			 * Only tag tag data were supplied.
			 * Convert tag results into displayable form.
			 */
			resultsToBeDisplayed = displayableTagData(tagResults,
			    acntIdDb.messageDbSet());
		}

		if (!resultsToBeDisplayed.isEmpty()) {
			appendMsgsToTable(*(m_ui->resultsTableView),
			    m_foundTableModel, acntIdDb, resultsToBeDisplayed);
		}
	}
}

void DlgMsgSearch::filterFound(const QString &text)
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
	m_foundListProxyModel.setFilterRegularExpression(
	    QRegularExpression(QRegularExpression::escape(text),
	        QRegularExpression::CaseInsensitiveOption));
#else /* < Qt-5.15 */
	m_foundListProxyModel.setFilterRegExp(QRegExp(text,
	    Qt::CaseInsensitive, QRegExp::FixedString));
#endif /* >= Qt-5.15 */
	/* Set filter field background colour. */
	if (text.isEmpty()) {
		m_ui->filterLine->setStyleSheet(
		    SortFilterProxyModel::blankFilterEditStyle);
	} else if (m_foundListProxyModel.rowCount() != 0) {
		m_ui->filterLine->setStyleSheet(
		    SortFilterProxyModel::foundFilterEditStyle);
	} else {
		m_ui->filterLine->setStyleSheet(
		    SortFilterProxyModel::notFoundFilterEditStyle);
	}
}

/*!
 * @brief Obtain list of selected rows.
 *
 * @param[in] view Message table view.
 * @param[in] proxyModel Filter proxy model.
 */
static
QList<int> selectedRows(const QTableView &view,
    const SortFilterProxyModel &proxyModel)
{
	QList<int> rowList;

	QModelIndexList selIdxs =
	    view.selectionModel()->selectedRows(MsgFoundModel::COL_ACCOUNT_ID);

	/* Translate to underlying model. */
	foreach (const QModelIndex &idx, selIdxs) {
		QModelIndex srcIdx = proxyModel.mapToSource(idx);
		if (Q_UNLIKELY(!srcIdx.isValid())) {
			Q_ASSERT(0);
			continue;
		}
		rowList.append(srcIdx.row());
	}

	return rowList;
}

void DlgMsgSearch::updateMessageSelection(const QItemSelection &selected,
    const QItemSelection &deselected)
{
	Q_UNUSED(selected);
	Q_UNUSED(deselected);

	m_modelRowSelection = selectedRows(*(m_ui->resultsTableView),
	    m_foundListProxyModel);

	updateActionActivation();
}

void DlgMsgSearch::messageItemDoubleClicked(const QModelIndex &index)
{
	if (Q_UNLIKELY(!index.isValid())) {
		return;
	}

	const QModelIndex srcIdx = m_foundListProxyModel.mapToSource(index);
	if (Q_UNLIKELY(!srcIdx.isValid())) {
		Q_ASSERT(0);
		return;
	}

	const MsgFoundModel::MsgIdentification &identif =
	    m_foundTableModel.msgIdentification(srcIdx.row());
	if (Q_UNLIKELY(!identif.acntIdDb.isValid())) {
		Q_ASSERT(0);
		return;
	}

	emit focusSelectedMsg(identif.acntIdDb, identif.msgId.dmId(),
	    MessageDbSet::yearFromDateTime(identif.msgId.deliveryTime()),
	    (int)identif.msgType);
	/* Don't close the dialogue. */
}

void DlgMsgSearch::viewMessageListContextMenu(const QPoint &point)
{
	/*
	 * The slot updateMessageSelection() is called before this one
	 * if the selection changes with the right click.
	 */

	if (!m_ui->resultsTableView->indexAt(point).isValid()) {
		/* Do nothing. */
		return;
	}

	QMenu *menu = new (::std::nothrow) QMenu(m_ui->resultsTableView);
	if (Q_UNLIKELY(menu == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	menu->addAction(m_actionDownloadMessage);
	menu->addSeparator();
	menu->addAction(m_actionExportMessageZfo);
	menu->addAction(m_actionExportAcceptanceInfoZfo);
	menu->addAction(m_actionExportAcceptanceInfoPdf);
	menu->addAction(m_actionExportEnvelopePdf);
	menu->addAction(m_actionExportEnvelopePdfAndAttachments);
	menu->addSeparator();
	menu->addAction(m_actionEmailZfos);
	menu->addAction(m_actionEmailAllAttachments);

	menu->exec(QCursor::pos());
	menu->deleteLater();
}

void DlgMsgSearch::watchMessageInserted(const AcntId &acntId,
    const MsgId &msgId, int direction)
{
	Q_UNUSED(direction);

	if (Q_UNLIKELY((!acntId.isValid()) || (msgId.dmId() < 0))) {
		Q_ASSERT(0);
		return;
	}

	m_foundTableModel.overrideDownloaded(acntId, msgId, true);
}

void DlgMsgSearch::watchMessageDataDeleted(const AcntId &acntId,
    const MsgId &msgId)
{
	if (Q_UNLIKELY((!acntId.isValid()) || (msgId.dmId() < 0))) {
		Q_ASSERT(0);
		return;
	}

	m_foundTableModel.remove(acntId, msgId);
}

/*!
 * @brief Convert enum MessageDb::MessageType to enum MessageDirection.
 *
 * @param[in]  type Message type.
 * @param[out] ok Set to true on success.
 * @return Message direction.
 */
static
enum MessageDirection messageType2messageDirection(
    enum MessageDb::MessageType type, bool *ok = Q_NULLPTR)
{
	enum MessageDirection direct = MSG_ALL;

	switch (type) {
	case MessageDb::TYPE_RECEIVED:
		direct = MSG_RECEIVED;
		break;
	case MessageDb::TYPE_SENT:
		direct = MSG_SENT;
		break;
	default:
		Q_ASSERT(0);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return direct;
		break;
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return direct;
}

/*!
 * @brief Construct exhaustive message selection list.
 *
 * @param[in] msgSelection Message selection status.
 * @param[in] model Found table model.
 * @return Origin list of selected messages.
 */
static
QList<MsgOrigin> messageOriginList(const QList<int> &modelRowSelection,
    const MsgFoundModel &model)
{
	QList<MsgOrigin> originList;

	foreach (int row, modelRowSelection) {
		if (Q_UNLIKELY(row < 0)) {
			Q_ASSERT(0);
			continue;
		}
		const MsgFoundModel::MsgIdentification &identif =
		    model.msgIdentification(row);

		originList.append(MsgOrigin(identif.acntIdDb, identif.msgId,
		    messageType2messageDirection(identif.msgType),
		    identif.downloaded));
	}

	return originList;
}

void DlgMsgSearch::downloadSelectedMessageAttachments(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	DlgDownloadMessages::downloadAll(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);
}

void DlgMsgSearch::exportSelectedMessageZfos(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

	GuiMsgOps::exportSelectedData(originList, Exports::ZFO_MESSAGE, this);
}

void DlgMsgSearch::exportSelectedAcceptanceInfoZfos(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

	GuiMsgOps::exportSelectedData(originList, Exports::ZFO_DELIVERY, this);
}

void DlgMsgSearch::exportSelectedAcceptanceInfoPdfs(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

	GuiMsgOps::exportSelectedData(originList, Exports::PDF_DELIVERY, this);
}

void DlgMsgSearch::exportSelectedEnvelopePdfs(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

	GuiMsgOps::exportSelectedData(originList, Exports::PDF_ENVELOPE, this);
}

void DlgMsgSearch::exportSelectedEnvelopeAttachments(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

	GuiMsgOps::exportSelectedEnvelopeAndAttachments(originList, this);
}

void DlgMsgSearch::createEmailWithZfos(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

	GuiMsgOps::createEmailWithZfos(originList);
}

void DlgMsgSearch::createEmailWithAllAttachments(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

	GuiMsgOps::createEmailWithAllAttachments(originList);
}

void DlgMsgSearch::setupActions(void)
{
	m_actionDownloadMessage = new (::std::nothrow) QAction(this);
	if (m_actionDownloadMessage != Q_NULLPTR) {
		m_actionDownloadMessage->setObjectName(QString::fromUtf8("actionDownloadMessage"));
		m_actionDownloadMessage->setIcon(IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE_DOWNLOAD));
		m_actionDownloadMessage->setMenuRole(QAction::NoRole);
		m_actionDownloadMessage->setText(tr("Download Signed Message"));
		m_actionDownloadMessage->setToolTip(tr("Download the complete message, i.e. including attachments, and verify its signature."));
	}

	m_actionExportMessageZfo = new (::std::nothrow) QAction(this);
	if (m_actionExportMessageZfo != Q_NULLPTR) {
		m_actionExportMessageZfo->setObjectName(QString::fromUtf8("actionExportMessageZfo"));
		//m_actionExportMessageZfo->setIcon();
		m_actionExportMessageZfo->setMenuRole(QAction::NoRole);
		m_actionExportMessageZfo->setText(tr("Export Message as ZFO"));
		m_actionExportMessageZfo->setToolTip(tr("Export the selected message as a ZFO file."));
	}

	m_actionExportAcceptanceInfoZfo = new (::std::nothrow) QAction(this);
	if (m_actionExportAcceptanceInfoZfo != Q_NULLPTR) {
		m_actionExportAcceptanceInfoZfo->setObjectName(QString::fromUtf8("actionExportAcceptanceInfoZfo"));
		//m_actionExportAcceptanceInfoZfo->setIcon();
		m_actionExportAcceptanceInfoZfo->setMenuRole(QAction::NoRole);
		m_actionExportAcceptanceInfoZfo->setText(tr("Export Acceptance Info as ZFO"));
		m_actionExportAcceptanceInfoZfo->setToolTip(tr("Export the acceptance information of the selected message as a ZFO file."));
	}

	m_actionExportAcceptanceInfoPdf = new (::std::nothrow) QAction(this);
	if (m_actionExportAcceptanceInfoPdf != Q_NULLPTR) {
		m_actionExportAcceptanceInfoPdf->setObjectName(QString::fromUtf8("actionExportAcceptanceInfoPdf"));
		//m_actionExportAcceptanceInfoPdf->setIcon();
		m_actionExportAcceptanceInfoPdf->setMenuRole(QAction::NoRole);
		m_actionExportAcceptanceInfoPdf->setText(tr("Export Acceptance Info as PDF"));
		m_actionExportAcceptanceInfoPdf->setToolTip(tr("Export the content of the acceptance information\n"
		    "of the selected message into a PDF file."));
	}

	m_actionExportEnvelopePdf = new (::std::nothrow) QAction(this);
	if (m_actionExportEnvelopePdf != Q_NULLPTR) {
		m_actionExportEnvelopePdf->setObjectName(QString::fromUtf8("actionExportEnvelopePdf"));
		//m_actionExportEnvelopePdf->setIcon();
		m_actionExportEnvelopePdf->setMenuRole(QAction::NoRole);
		m_actionExportEnvelopePdf->setText(tr("Export Message Envelope as PDF"));
		m_actionExportEnvelopePdf->setToolTip(tr("Export content of the envelope\n"
		    "of the selected message as a PDF file."));
	}

	m_actionExportEnvelopePdfAndAttachments = new (::std::nothrow) QAction(this);
	if (m_actionExportEnvelopePdfAndAttachments != Q_NULLPTR) {
		m_actionExportEnvelopePdfAndAttachments->setObjectName(QString::fromUtf8("actionExportEnvelopePdfAndAttachments"));
		//m_actionExportEnvelopePdfAndAttachments->setIcon();
		m_actionExportEnvelopePdfAndAttachments->setMenuRole(QAction::NoRole);
		m_actionExportEnvelopePdfAndAttachments->setText(tr("Export Envelope PDF with Attachments"));
		m_actionExportEnvelopePdfAndAttachments->setToolTip(tr("Export the content of the envelope\n"
		    "of the selected message to a PDF file.\n"
		    "Also export message attachments."));
	}

	m_actionEmailZfos = new (::std::nothrow) QAction(this);
	if (m_actionEmailZfos != Q_NULLPTR) {
		m_actionEmailZfos->setObjectName(QString::fromUtf8("actionEmailZfos"));
		//m_actionEmailZfos->setIcon();
		m_actionEmailZfos->setMenuRole(QAction::NoRole);
		m_actionEmailZfos->setText(tr("E-mail with ZFOs"));
		m_actionEmailZfos->setToolTip(tr("Creates an e-mail containing ZFOs of selected messages."));
	}

	m_actionEmailAllAttachments = new (::std::nothrow) QAction(this);
	if (m_actionEmailAllAttachments != Q_NULLPTR) {
		m_actionEmailAllAttachments->setObjectName(QString::fromUtf8("actionEmailAllAttachments"));
		//m_actionEmailAllAttachments->setIcon();
		m_actionEmailAllAttachments->setMenuRole(QAction::NoRole);
		m_actionEmailAllAttachments->setText(tr("E-mail with All Attachments"));
		m_actionEmailAllAttachments->setToolTip(tr("Creates an e-mail containing all attachments of selected messages."));
	}

	connect(m_actionDownloadMessage, SIGNAL(triggered()),
	    this, SLOT(downloadSelectedMessageAttachments()));
	    /* Separator. */
	connect(m_actionExportMessageZfo, SIGNAL(triggered()),
	    this, SLOT(exportSelectedMessageZfos()));
	connect(m_actionExportAcceptanceInfoZfo, SIGNAL(triggered()),
	    this, SLOT(exportSelectedAcceptanceInfoZfos()));
	connect(m_actionExportAcceptanceInfoPdf, SIGNAL(triggered()),
	    this, SLOT(exportSelectedAcceptanceInfoPdfs()));
	connect(m_actionExportEnvelopePdf, SIGNAL(triggered()),
	    this, SLOT(exportSelectedEnvelopePdfs()));
	connect(m_actionExportEnvelopePdfAndAttachments, SIGNAL(triggered()),
	    this, SLOT(exportSelectedEnvelopeAttachments()));
	    /* Separator. */
	connect(m_actionEmailZfos, SIGNAL(triggered()),
	    this, SLOT(createEmailWithZfos()));
	connect(m_actionEmailAllAttachments, SIGNAL(triggered()),
	    this, SLOT(createEmailWithAllAttachments()));
}

void DlgMsgSearch::initSearchWindow(const AcntId &acntId)
{
	m_ui->infoTextLabel->setText(tr(
	    "Double clicking on a found message will change focus of the selected message in the main application window. "
	    "Note: You can view additional information when hovering the mouse cursor over the message ID."));

	Q_ASSERT(acntId.isValid());

	/* Set accounts into combobox - account name and user name. */
	foreach (const AcntIdDb &acntIdDb, m_acntIdDbList) {
		const AcntId aId(acntIdDb);
		const QString accountName =
		    GlobInstcs::acntMapPtr->acntData(aId).accountName() +
		    " (" + acntIdDb.username() + ")";
		m_ui->accountComboBox->addItem(accountName, QVariant::fromValue(aId));
		if (acntId == aId) {
			int i = m_ui->accountComboBox->count() - 1;
			Q_ASSERT(0 <= i);
			m_ui->accountComboBox->setCurrentIndex(i);
		}
	}

	/* Only one account available. */
	if (m_acntIdDbList.count() <= 1) {
		m_ui->searchAllAcntCheckBox->setEnabled(false);
	}

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

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

	connect(m_ui->searchRcvdMsgCheckBox, SIGNAL(clicked()),
	    this, SLOT(checkInputFields()));
	connect(m_ui->searchSntMsgCheckBox, SIGNAL(clicked()),
	    this, SLOT(checkInputFields()));

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

	connect(m_ui->sndrBoxIdLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->sndrNameLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->sndrRefNumLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->sndrFileMarkLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->rcpntBoxIdLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->rcpntNameLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->rcpntRefNumLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->rcpntFileMarkLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));

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

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

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

	m_ui->resultsTableView->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(m_ui->resultsTableView, SIGNAL(customContextMenuRequested(QPoint)),
	    this, SLOT(viewMessageListContextMenu(QPoint)));
	connect(m_ui->resultsTableView, SIGNAL(doubleClicked(QModelIndex)),
	    this, SLOT(messageItemDoubleClicked(QModelIndex)));
	connect(m_ui->resultsTableView->selectionModel(),
	    SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
	    this,
	    SLOT(updateMessageSelection(QItemSelection, QItemSelection)));

	connect(m_ui->searchPushButton, SIGNAL(clicked()),
	    this, SLOT(searchMessages()));

	connect(m_ui->filterLine, SIGNAL(textChanged(QString)),
	    this, SLOT(filterFound(QString)));

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

	databaseConnectActions();
}

void DlgMsgSearch::databaseConnectActions(void)
{
	connect(GlobInstcs::msgDbsPtr, SIGNAL(messageInserted(AcntId, MsgId, int)),
	    this, SLOT(watchMessageInserted(AcntId, MsgId, int)));
	connect(GlobInstcs::msgDbsPtr, SIGNAL(messageDataDeleted(AcntId, MsgId)),
	    this, SLOT(watchMessageDataDeleted(AcntId, MsgId)));
}

int DlgMsgSearch::filledInExceptTags(void) const
{
	int cnt = 0;

	if (!m_ui->msgIdLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->subjectLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->sndrBoxIdLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->sndrNameLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->sndrRefNumLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->sndrFileMarkLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->rcpntBoxIdLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->rcpntNameLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->rcpntRefNumLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->rcpntFileMarkLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->addressLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->toHandsLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->attachNameLine->text().isEmpty()) { ++cnt; }

	return cnt;
}

void DlgMsgSearch::updateActionActivation(void)
{
	int numSelected = m_modelRowSelection.size();

	m_actionDownloadMessage->setEnabled(numSelected > 0);

	m_actionExportMessageZfo->setEnabled(numSelected > 0);
	m_actionExportAcceptanceInfoZfo->setEnabled(numSelected > 0);
	m_actionExportAcceptanceInfoPdf->setEnabled(numSelected > 0);
	m_actionExportEnvelopePdf->setEnabled(numSelected > 0);
	m_actionExportEnvelopePdfAndAttachments->setEnabled(numSelected > 0);

	m_actionEmailZfos->setEnabled(numSelected > 0);
	m_actionEmailAllAttachments->setEnabled(numSelected > 0);
}
