/*
 * 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 <cinttypes> /* PRId64 */
#include <QDir>
#include <QFileDialog>
#include <QIntValidator>
#include <QUrl>

#include "src/datovka_shared/log/log.h"
#include "src/gui/dlg_msg_box_informative.h"
#include "src/gui/dlg_tag.h"
#include "src/gui/dlg_tag_settings.h"
#include "src/gui/icon_container.h"
#include "src/io/tag_container.h"
#include "src/io/tag_db.h"
#include "src/json/srvr_profile.h"
#include "src/json/tag_assignment_hash.h"
#include "src/settings/tag_container_settings.h"
#include "src/settings/tag_container_settings_apply.h"
#include "ui_dlg_tag_settings.h"

#define WRONG_TAG_ID -1 /** TODO -- Remove. */

#define TAB_CONNECTION 0
#define TAB_TAGS 1

DlgTagSettings::DlgTagSettings(const TagContainerSettings &tagContSet,
    TagDb *localTagDb, QWidget *parent)
    : QDialog(parent),
    m_ui(new (::std::nothrow) Ui::DlgTagSettings),
    m_tagContSet(tagContSet),
    m_tagClient(),
    m_profileModel(this),
    m_localTagDb(localTagDb),
    m_localTagsDelegate(this),
    m_localTagsModel(this),
    m_serverTagsDelegate(this),
    m_serverTagsModel(this)
{
	m_ui->setupUi(this);
	/* Tab order is defined in UI file. */

	initDialogue(m_tagContSet);
}

DlgTagSettings::~DlgTagSettings(void)
{
	/* Disconnect all signals and disconnect from server. */
	m_tagClient.disconnect(Q_NULLPTR, this, Q_NULLPTR);
	m_tagClient.netDisconnect();

	delete m_ui;
}

/*!
 * @brief Get backend enum from radio buttons.
 */
static
enum TagContainer::Backend selectedBackend(QRadioButton *dbButton,
    QRadioButton *clientButton)
{
	if (dbButton->isChecked()) {
		return TagContainer::BACK_DB;
	} else if (clientButton->isChecked()) {
		return TagContainer::BACK_CLIENT;
	} else {
		return TagContainer::BACK_DB;
	}
}

bool DlgTagSettings::editTagContSettings(TagContainerSettings &tagContSet,
    TagContainer &tagCont, QWidget *parent)
{
	DlgTagSettings dlg(tagContSet, tagCont.backendDb(), parent);
	if (dlg.exec() == QDialog::Accepted) {
		bool changedBackend = false;
		bool changedConnection = false;
		bool changedProfile = false;

		/* Backend. */
		{
			const enum TagContainer::Backend newBackend = selectedBackend(
			    dlg.m_ui->localTagDbRadioButton, dlg.m_ui->tagClientRadioButton);
			if (tagContSet.backend() != newBackend) {
				changedBackend = true;
				tagContSet.setBackend(newBackend);
			}
		}

		/* Connection. */
		{
			const QString hostname = dlg.m_ui->tagSrvrNameLineEdit->text();
			if (tagContSet.clientHostname() != hostname) {
				changedConnection = true;
				tagContSet.setClientHostname(hostname);
			}
		}
		{
			bool iOk = false;
			const int port = dlg.m_ui->tagSrvrPortLineEdit->text().toInt(&iOk);
			if (iOk) {
				if (tagContSet.clientPort() != port) {
					changedConnection = true;
					tagContSet.setClientPort(port);
				}
			} else {
				/* TODO -- Handle bad input. */
				tagContSet.setClientPort(-1);
			}
		}
		{
			const QString certPath = dlg.m_ui->tagSrvrCertLineEdit->text();
			if (tagContSet.certPath() != certPath) {
				changedConnection = true;
				tagContSet.setCertPath(certPath);
			}
		}
		{
			const QString username = dlg.m_ui->usernameLineEdit->text();
			if (tagContSet.username() != username) {
				changedConnection = true;
				tagContSet.setUsername(username);
			}
		}
		{
			const QString pwd = dlg.m_ui->pwdLineEdit->text();
			if (tagContSet.password() != pwd) {
				changedConnection = true;
				tagContSet.setPassword(pwd);
				tagContSet.setPwdCode(QByteArray());
			}
		}

		/* Profile. */
		{
			if (dlg.m_profileModel.rowCount() > 0) {
				const QModelIndex index = dlg.m_profileModel.index(
				    dlg.m_ui->profileComboBox->currentIndex(), 0);

				const qint64 profileId = dlg.m_profileModel.data(
				    index, CBoxModel::ROLE_VALUE).toLongLong();
				const QString profileName = dlg.m_profileModel.data(
				    index).toString();

				if (tagContSet.profileId() != profileId) {
					changedProfile = true;
					tagContSet.setProfileId(profileId);
				}
				if (tagContSet.profileName() != profileName) {
					/* Just store the new name. */
					tagContSet.setProfileName(profileName);
				}
			} else {
				/* TODO -- No profiles available. */
				tagContSet.setProfileId(-1);
				tagContSet.setProfileName(QString());
			}
		}

		if (changedBackend || changedConnection) {
			applyCompleteTagContainerSettings(tagContSet, tagCont);
		} else if (changedProfile) {
			applyProfileSettings(tagContSet, tagCont);
		}

		/* Try reconnecting the tag container if disconnected. */
		if ((tagCont.backend() == TagContainer::BACK_CLIENT) &&
		    !tagCont.isResponding()) {
			applyCompleteTagContainerSettings(tagContSet, tagCont);
		}

		return true;
	}

	return false;
}

void DlgTagSettings::activateServiceButtons(void)
{
	if (m_ui->localTagDbRadioButton->isChecked()) {
		/* Enable only when the value changes. */
		m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(
		    m_tagContSet.backend() == TagContainer::BACK_CLIENT);
	} else if (m_ui->tagClientRadioButton->isChecked()) {
		bool hostnameOk = !m_ui->tagSrvrNameLineEdit->text().isEmpty();
		bool portOk = !m_ui->tagSrvrPortLineEdit->text().isEmpty();
		if (portOk) {
			bool iOk = false;
			int port = m_ui->tagSrvrPortLineEdit->text().toInt(&iOk);
			portOk = iOk && (port >= 0) && (port <= 65535);
		}
		bool usernameOk = !m_ui->usernameLineEdit->text().isEmpty();
		bool pwdOk = !m_ui->pwdLineEdit->text().isEmpty();

		m_ui->checkConnectionButton->setEnabled(hostnameOk && portOk && usernameOk && pwdOk);

		/* The connection bust be checked before the settings can be saved. */
		m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);

		/* Clear and disable the profile list. */
		m_profileModel.removeAllRows();
		m_ui->profileComboBox->setEnabled(false);
		m_ui->refreshProfilesPushButton->setEnabled(false);

		m_ui->profileErrorLabel->setVisible(false);
		m_ui->profileErrorLabel->setText(QString());

		m_ui->tabWidget->setTabEnabled(TAB_TAGS, false);
	}
}

void DlgTagSettings::clientSelected(bool selected)
{
	m_ui->clientGroupBox->setEnabled(selected);

	m_ui->profileComboBox->setEnabled(false);
	m_ui->refreshProfilesPushButton->setEnabled(false);

	m_ui->profileErrorLabel->setVisible(false);
	m_ui->profileErrorLabel->setText(QString());

	m_ui->tabWidget->setTabEnabled(TAB_TAGS, false);
}

void DlgTagSettings::selectCertFile(void)
{
	QString dir;
	if (!m_ui->tagSrvrCertLineEdit->text().isEmpty()) {
		QFileInfo fi(QDir::fromNativeSeparators(
		    m_ui->tagSrvrCertLineEdit->text()));
		dir = fi.absoluteDir().absolutePath();
	}

	const QString certFileName(QFileDialog::getOpenFileName(this,
	    tr("Open Certificate"), dir,
	    tr("Certificate Files (*.pem *.der)")));
	if (!certFileName.isEmpty()) {
		m_ui->tagSrvrCertLineEdit->setText(
		    QDir::toNativeSeparators(certFileName));
	}
}

void DlgTagSettings::checkClientConnection(void)
{
	m_tagClient.netDisconnect();

	{
		QUrl url("https://" + m_ui->tagSrvrNameLineEdit->text());
		url.setPort(m_ui->tagSrvrPortLineEdit->text().toInt());
		m_tagClient.setBaseUrl(url);
	}
	{
		QUrl url("wss://" + m_ui->tagSrvrNameLineEdit->text());
		url.setPort(m_ui->tagSrvrPortLineEdit->text().toInt());
		m_tagClient.setWebSockUrl(url);
	}
	{
		const QString certPath = m_ui->tagSrvrCertLineEdit->text();
		if (!certPath.isEmpty()) {
			QSslCertificate caCert = readSslCert(certPath);
			if (!caCert.isNull()) {
				m_tagClient.setCaCertificate(caCert);
			}
		}
	}

	bool success = m_tagClient.netConnectUsernamePwd(
	    m_ui->usernameLineEdit->text(), m_ui->pwdLineEdit->text());
	if (success) {
		logInfoNL("%s", "Connected to tag server.");
	} else {
		logErrorNL("%s", "Connection to tag server failed.");
	}
}

void DlgTagSettings::downloadProfileList(void)
{
	m_profileModel.removeAllRows();

	{
		Json::SrvrProfileList profiles;
		bool success = m_tagClient.listProfiles(profiles);
		if (success) {
			for (const Json::SrvrProfile &profile : profiles) {
				m_profileModel.appendRow(profile.name(), profile.id(), true);
			}

			/* Select profile. */
			if (m_tagContSet.profileId() >= 0) {
				for (int i = 0; i < profiles.size(); ++i) {
					if (profiles.at(i).id() == m_tagContSet.profileId()) {
						m_ui->profileComboBox->setCurrentIndex(i);
						break;
					}
				}
			}
		} else {
			logErrorNL("%s", "Cannot download profile list.");
		}
	}
}

void DlgTagSettings::changeProfile(int index)
{
	if (Q_UNLIKELY(index < 0)) {
		return;
	}

	const QModelIndex idx = m_profileModel.index(index, 0);

	const qint64 profileId = m_profileModel.data(
	    idx, CBoxModel::ROLE_VALUE).toLongLong();
	const QString profileName = m_profileModel.data(idx).toString();

	m_ui->profileErrorLabel->setVisible(false);
	m_ui->profileErrorLabel->setText(QString());

	if (profileId >= 0) {
		if (m_tagClient.selectProfile(profileId)) {
			m_ui->tabWidget->setTabEnabled(TAB_TAGS, true);
			{
				/* Get all server tags. */
				Json::TagEntryList serverTags;
				m_tagClient.getAllTags(serverTags);
				m_serverTagsModel.setTagList(serverTags);
			}

			m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(
			    m_tagClient.isConnected());
		} else {
			m_serverTagsModel.setTagList(Json::TagEntryList());

			m_ui->profileErrorLabel->setVisible(true);
			m_ui->profileErrorLabel->setText(
			    tr("The profile '%1' cannot be set.").arg(profileName));

			logErrorNL(
			    "Cannot select profile '%" PRId64 "' '%s'.",
			    UGLY_QINT64_CAST profileId,
			    profileName.toUtf8().constData());

			m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
		}
	} else {
		m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
	}
}

/*!
 * @brief Get selected indexes.
 *
 * @param[in] view List view.
 * @return Selected indexes.
 */
static inline
QModelIndexList selectedIndexes(const QListView *view)
{
	if (Q_UNLIKELY(view == Q_NULLPTR)) {
		Q_ASSERT(0);
		return QModelIndexList();
	}

	return view->selectionModel()->selectedRows();
}

void DlgTagSettings::handleLocalSelectionChange(void)
{
	const QModelIndexList slctIdxs(selectedIndexes(m_ui->localTagsView));

	m_ui->updateLocalTagButton->setEnabled(slctIdxs.count() == 1);
	m_ui->toServerButton->setEnabled(!slctIdxs.isEmpty());
}

/*!
 * @brief Get tag id from index.
 *
 * @param[in] idx Model index.
 * @return Tag id if success else -1.
 */
static inline
qint64 getTagIdFromIndex(const QModelIndex &idx)
{
	if (Q_UNLIKELY(!idx.isValid())) {
		return WRONG_TAG_ID;
	}

	if (Q_UNLIKELY(!idx.data().canConvert<TagItem>())) {
		return WRONG_TAG_ID;
	}

	TagItem tagItem(qvariant_cast<TagItem>(idx.data()));

	return tagItem.id();
}

/*!
 * @brief Get selected index.
 *
 * @param[in] view List view.
 * @return Selected index.
 */
static inline
QModelIndex selectedIndex(const QListView *view)
{
	if (Q_UNLIKELY(view == Q_NULLPTR)) {
		Q_ASSERT(0);
		return QModelIndex();
	}

	return view->selectionModel()->currentIndex();
}

void DlgTagSettings::updateLocalTag(void)
{
	if (Q_UNLIKELY(m_localTagDb == Q_NULLPTR)) {
		return;
	}

	Json::TagEntry entry;
	{
		Json::Int64StringList ids(
		    {getTagIdFromIndex(selectedIndex(m_ui->localTagsView))});
		Json::TagEntryList entries;
		if (Q_UNLIKELY(!m_localTagDb->getTagData(ids, entries))) {
			return;
		}
		if (Q_UNLIKELY(entries.size() != 1)) {
			return;
		}
		entry = entries.at(0);
	}
	TagItem tagItem(entry);

	DlgTag::editTag(m_localTagDb, tagItem, this);
}

/*!
 * @brief Get tag id from index.
 *
 * @param[in] idx Model index.
 * @return TagItem if success else null value.
 */
static inline
TagItem getTagItemFromIndex(const QModelIndex &idx)
{
	if (Q_UNLIKELY(!idx.isValid())) {
		return TagItem();
	}

	if (Q_UNLIKELY(!idx.data().canConvert<TagItem>())) {
		return TagItem();
	}

	return qvariant_cast<TagItem>(idx.data());
}

/*!
 * @brief Compute the mapping from source tag entries to target tag entries
 *     according to tag names.
 *
 * @param[in]  tgtList Available target tags.
 * @param[in]  srcList Selected source tags.
 * @param[out] fullTgtMatch Computed mapping from source tags to target tag
 *                          identifiers for exact tag name matches.
 * @param[out] caseInsensitiveTgtMatch Computed mapping from source tags to
 *                                     target tag names when case-insensitive
 *                                     tag name match found.
 * @param[out] noTgtMatch Source tags whose matches have not been found.
 */
static
void getTgtMatches(const Json::TagEntryList &tgtList,
    const Json::TagEntryList &srcList,
    QHash<Json::TagEntry, qint64> &fullTgtMatch,
    QHash<Json::TagEntry, QString> &caseInsensitiveTgtMatch,
    QSet<Json::TagEntry> &noTgtMatch)
{
	QHash<QString, Json::TagEntry> fullNameMatch;
	QHash<QString, Json::TagEntry> caseInsensitiveNameMatch;

	/* Construct target name to TagEntry mapping. */
	for (const Json::TagEntry &te : tgtList) {
		if (Q_UNLIKELY(!te.isValid())) {
			continue;
		}
		fullNameMatch[te.name()] = te;
		caseInsensitiveNameMatch[te.name().toLower()] = te;
	}

	/* Find matches. */
	for (const Json::TagEntry &se : srcList) {
		QHash<QString, Json::TagEntry>::const_iterator it =
		    fullNameMatch.find(se.name());
		/* Found full match. */
		if (it != fullNameMatch.cend()) {
			fullTgtMatch[se] = it.value().id();
			continue;
		}
		/* Found case insensitive match. */
		it = caseInsensitiveNameMatch.find(se.name().toLower());
		if (it != caseInsensitiveNameMatch.cend()) {
			caseInsensitiveTgtMatch[se] = it.value().name();
			continue;
		}
		/* No match. */
		noTgtMatch.insert(se);
	}
}

/*!
 * @brief Sends all message assignments for supplied full match from source to
 *     target.
 *
 * @param[in]  fullSrcToTgtMatch Full match mapping from source tag entries onto
 *                               target tag identifiers.
 * @param[in]  srcTagDb Source tag database.
 * @param[out] tgtTagClient Target tag client.
 * @return True on success, false else.
 */
static
bool sendFullMatchToTgt(const QHash<Json::TagEntry, qint64> &fullSrcToTgtMatch,
    const TagDb &srcTagDb, TagClient &tgtTagClient)
{
	Json::TagAssignmentCommand tagAssignmentCommand;

	{
		/* Query all messages assigned to local tags. */
		Json::TagIdToMsgIdListHash localTagIdToMsgIds;
		{
			Json::Int64StringList srcTagIds;
			for (const Json::TagEntry &se : fullSrcToTgtMatch.keys()) {
				srcTagIds.append(se.id());
			}
			if (Q_UNLIKELY(!srcTagDb.getTagMessages(srcTagIds, localTagIdToMsgIds))) {
				logErrorNL("%s", "Cannot acquire message to tags assignment.");
				return false;
			}
		}

		/* Construct new assignment. */
		for (const Json::TagEntry &se : fullSrcToTgtMatch.keys()) {
			qint64 tgtTagId = fullSrcToTgtMatch.value(se);
			for (const Json::TagMsgId &msgId : localTagIdToMsgIds.value(se.id())) {
				tagAssignmentCommand[msgId].append(tgtTagId);
			}
		}
	}

	/* Send new assignment to server. */
	return tgtTagClient.assignTagsToMsgs(tagAssignmentCommand);
}

void DlgTagSettings::sendSelectedTagsToServer(void)
{
	if (Q_UNLIKELY(m_localTagDb == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	QHash<Json::TagEntry, qint64> fullLocalToServerMatch;
	QHash<Json::TagEntry, QString> partialLocalToServerMatch;
	QSet<Json::TagEntry> noLocalToServerMatch;

	{
		Json::TagEntryList localTagEntries;
		{
			const QModelIndexList slctIdxs(selectedIndexes(m_ui->localTagsView));
			if (Q_UNLIKELY(slctIdxs.isEmpty())) {
				return;
			}
			for (const QModelIndex &idx : slctIdxs) {
				TagItem localTagItem = getTagItemFromIndex(idx);
				if (!localTagItem.isNull()) {
					localTagEntries.append(localTagItem);
				}
			}
		}

		Json::TagEntryList serverTagEntries;
		{
			for (int row = 0; row < m_serverTagsModel.rowCount(); ++row) {
				TagItem serverTagItem = getTagItemFromIndex(
				    m_serverTagsModel.index(row, 0));
				if (!serverTagItem.isNull()) {
					serverTagEntries.append(serverTagItem);
				}
			}
		}
		getTgtMatches(serverTagEntries, localTagEntries, fullLocalToServerMatch,
		    partialLocalToServerMatch, noLocalToServerMatch);
	}

	if (!partialLocalToServerMatch.isEmpty()) {
		/*
		 * Warn user about partial match and ignore those values
		 * further.
		 */
		QString detailText;
		for (const Json::TagEntry &se : partialLocalToServerMatch.keys()) {
			detailText += tr("Tag '%1' is similar to '%2'.").arg(se.name()).arg(partialLocalToServerMatch[se])
			    + QStringLiteral("\n");
		}
		DlgMsgBox::message(this, QMessageBox::Information,
		    tr("Similar Tag Names"),
		    tr("The following tag entries have similar but not exactly same names with names on the server. "
		        "You may rename those tags to achieve a name match."),
		    tr("Some locally stored tag entries won't be transferred to the server because of the listed similarities. "
		        "See the details to view a listing."),
		    detailText);
	}

	if (!fullLocalToServerMatch.isEmpty()) {
		if (Q_UNLIKELY(!sendFullMatchToTgt(fullLocalToServerMatch,
		        *m_localTagDb, m_tagClient))) {
			QMessageBox::warning(this, tr("Message Assignment Failed"),
			    tr("Could not copy tag assignment to server."));
		}
	}
	fullLocalToServerMatch.clear(); /* Clear this. */

	if (!noLocalToServerMatch.isEmpty()) {
		/* Create new tags on server. */
		{
			Json::TagEntryList toBeCreated;
			for (const Json::TagEntry &e : noLocalToServerMatch) {
				Json::TagEntry newEntry = e;
				newEntry.setId(0);
				toBeCreated.append(newEntry);
			}
			if (Q_UNLIKELY(!m_tagClient.insertTags(toBeCreated))) {
				QMessageBox::warning(this, tr("Tag Creation Failed"),
				    tr("Could not create new tags on server."));
				return;
			}
		}
		/* Create mapping from local to newly created tags. */
		{
			Json::TagEntryList serverTags;
			if (Q_UNLIKELY(!m_tagClient.getAllTags(serverTags))) {
				QMessageBox::warning(this, tr("Tag Creation Failed"),
				    tr("Could list tags from server."));
				return;
			}
			QHash<QString, qint64> serverNameMatch;
			/* Construct name to server identifier mapping. */
			for (const Json::TagEntry &se : serverTags) {
				serverNameMatch[se.name()] = se.id();
			}
			/* Construct full matches. */
			for (const Json::TagEntry &le : noLocalToServerMatch) {
				QHash<QString, qint64>::const_iterator it =
				    serverNameMatch.find(le.name());
				if (it != serverNameMatch.cend()) {
					fullLocalToServerMatch[le] = it.value();
				} else {
					Q_ASSERT(0);
				}
			}
		}
	}
	if (!fullLocalToServerMatch.isEmpty()) {
		if (Q_UNLIKELY(!sendFullMatchToTgt(fullLocalToServerMatch,
		        *m_localTagDb, m_tagClient))) {
			QMessageBox::warning(this, tr("Message Assignment Failed"),
			    tr("Could not copy new tag assignment to server."));
		}
	}
}

void DlgTagSettings::handleServerSelectionChange(void)
{
	const QModelIndexList slctIdxs(selectedIndexes(m_ui->serverTagsView));

	m_ui->updateServerTagButton->setEnabled(slctIdxs.count() == 1);
}

void DlgTagSettings::updateServerTag(void)
{
	Json::TagEntry entry;
	{
		Json::Int64StringList ids(
		    {getTagIdFromIndex(selectedIndex(m_ui->serverTagsView))});
		Json::TagEntryList entries;
		if (Q_UNLIKELY(!m_tagClient.getTagData(ids, entries))) {
			return;
		}
		if (Q_UNLIKELY(entries.size() != 1)) {
			return;
		}
		entry = entries.at(0);
	}
	TagItem tagItem(entry);

	DlgTag::editTag(&m_tagClient, tagItem, this);
}

void DlgTagSettings::watchConnected(void)
{
	m_ui->connectionStatusLabel->setText(tr("Connected to server."));

	downloadProfileList();

	m_ui->profileComboBox->setEnabled(true);
	m_ui->refreshProfilesPushButton->setEnabled(true);
}

void DlgTagSettings::watchDisconnected(void)
{
	m_ui->connectionStatusLabel->setText(tr("Not connected to server."));

	m_profileModel.removeAllRows();

	m_ui->profileComboBox->setEnabled(false);
	m_ui->refreshProfilesPushButton->setEnabled(false);

	m_ui->profileErrorLabel->setVisible(false);
	m_ui->profileErrorLabel->setText(QString());

	m_ui->tabWidget->setTabEnabled(TAB_TAGS, false);
}

void DlgTagSettings::watchLocalTagsInserted(const Json::TagEntryList &entries)
{
	for (const Json::TagEntry &entry : entries) {
		if (Q_UNLIKELY(!entry.isValid())) {
			continue;
		}

		m_localTagsModel.insertTag(entry);
	}
}

void DlgTagSettings::watchLocalTagsUpdated(const Json::TagEntryList &entries)
{
	for (const Json::TagEntry &entry : entries) {
		if (Q_UNLIKELY(!entry.isValid())) {
			continue;
		}

		m_localTagsModel.updateTag(entry);
	}
}

void DlgTagSettings::watchLocalTagsDeleted(const Json::Int64StringList &ids)
{
	for (qint64 id : ids) {
		if (Q_UNLIKELY(id < 0)) {
			continue;
		}

		m_localTagsModel.deleteTag(id);
	}
}

void DlgTagSettings::watchServerTagsInserted(const Json::TagEntryList &entries)
{
	for (const Json::TagEntry &entry : entries) {
		if (Q_UNLIKELY(!entry.isValid())) {
			continue;
		}

		m_serverTagsModel.insertTag(entry);
	}
}

void DlgTagSettings::watchServerTagsUpdated(const Json::TagEntryList &entries)
{
	for (const Json::TagEntry &entry : entries) {
		if (Q_UNLIKELY(!entry.isValid())) {
			continue;
		}

		m_serverTagsModel.updateTag(entry);
	}
}

void DlgTagSettings::watchServerTagsDeleted(const Json::Int64StringList &ids)
{
	for (qint64 id : ids) {
		if (Q_UNLIKELY(id < 0)) {
			continue;
		}

		m_serverTagsModel.deleteTag(id);
	}
}

void DlgTagSettings::initDialogue(const TagContainerSettings &tagContSet)
{
	setIcons();

	/* Server connection tab. */
	m_ui->tagSrvrPortLineEdit->setValidator(
	    new (::std::nothrow) QIntValidator(0, 65535, m_ui->tagSrvrPortLineEdit));

	connect(m_ui->tagClientRadioButton, SIGNAL(toggled(bool)),
	    this, SLOT(activateServiceButtons()));
	connect(m_ui->tagSrvrNameLineEdit, SIGNAL(textChanged(QString)),
	    this, SLOT(activateServiceButtons()));
	connect(m_ui->tagSrvrPortLineEdit, SIGNAL(textChanged(QString)),
	    this, SLOT(activateServiceButtons()));
	connect(m_ui->tagSrvrCertLineEdit, SIGNAL(textChanged(QString)),
	    this, SLOT(activateServiceButtons()));
	connect(m_ui->usernameLineEdit, SIGNAL(textChanged(QString)),
	    this, SLOT(activateServiceButtons()));
	connect(m_ui->pwdLineEdit, SIGNAL(textChanged(QString)),
	    this, SLOT(activateServiceButtons()));

	connect(m_ui->tagClientRadioButton, SIGNAL(toggled(bool)),
	    this, SLOT(clientSelected(bool)));

	connect(m_ui->tagSrvrCertButton, SIGNAL(clicked(bool)),
	    this, SLOT(selectCertFile()));
	connect(m_ui->checkConnectionButton, SIGNAL(clicked(bool)),
	    this, SLOT(checkClientConnection()));
	connect(m_ui->profileComboBox, SIGNAL(currentIndexChanged(int)),
	    this, SLOT(changeProfile(int)));
	connect(m_ui->refreshProfilesPushButton, SIGNAL(clicked(bool)),
	    this, SLOT(downloadProfileList()));

	connect(&m_tagClient, SIGNAL(connected()),
	    this, SLOT(watchConnected()));
	connect(&m_tagClient, SIGNAL(disconnected()),
	    this, SLOT(watchDisconnected()));

	m_ui->localTagDbRadioButton->setChecked(tagContSet.backend() == TagContainer::BACK_DB);
	m_ui->tagClientRadioButton->setChecked(tagContSet.backend() == TagContainer::BACK_CLIENT);

	m_ui->tagSrvrNameLineEdit->setText(tagContSet.clientHostname());
	{
		int port = tagContSet.clientPort();
		if ((port < 0) || (port > 65535)) {
			port = -1;
		}
		m_ui->tagSrvrPortLineEdit->setText((port >= 0) ? QString::number(port) : QString());
	}
	m_ui->tagSrvrCertLineEdit->setText(tagContSet.certPath());
	m_ui->usernameLineEdit->setText(tagContSet.username());
	m_ui->pwdLineEdit->setText(tagContSet.password());

	m_ui->connectionStatusLabel->setText(tr("Not connected to server."));

	m_ui->profileErrorLabel->setVisible(false);
	m_ui->profileErrorLabel->setText(QString());

	m_ui->profileComboBox->setModel(&m_profileModel);

	clientSelected(tagContSet.backend() == TagContainer::BACK_CLIENT);

	/* Enable only after dialogue content has been changed. */
	m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);

	if (m_ui->checkConnectionButton->isEnabled()) {
		checkClientConnection();
	}
	if (m_tagClient.isConnected()) {
		downloadProfileList();
	}

	/* Tags tab. */
	m_ui->localTagsView->setItemDelegate(&m_localTagsDelegate);
	m_ui->localTagsView->setModel(&m_localTagsModel);
	m_ui->localTagsView->setSelectionMode(QAbstractItemView::ExtendedSelection);
	m_ui->localTagsView->setSelectionBehavior(QAbstractItemView::SelectRows);

	connect(m_ui->localTagsView->selectionModel(),
	    SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
	    this, SLOT(handleLocalSelectionChange()));

	connect(m_ui->updateLocalTagButton, SIGNAL(clicked()), this,
	    SLOT(updateLocalTag()));

	connect(m_ui->toServerButton, SIGNAL(clicked()), this,
	    SLOT(sendSelectedTagsToServer()));

	m_ui->updateLocalTagButton->setEnabled(false);
	m_ui->toServerButton->setEnabled(false);

	if (m_localTagDb != Q_NULLPTR) {
		connect(m_localTagDb, SIGNAL(tagsInserted(Json::TagEntryList)),
		    this, SLOT(watchLocalTagsInserted(Json::TagEntryList)));
		connect(m_localTagDb, SIGNAL(tagsUpdated(Json::TagEntryList)),
		    this, SLOT(watchLocalTagsUpdated(Json::TagEntryList)));
		connect(m_localTagDb, SIGNAL(tagsDeleted(Json::Int64StringList)),
		    this, SLOT(watchLocalTagsDeleted(Json::Int64StringList)));

		/* Get all local tags. */
		Json::TagEntryList localTags;
		m_localTagDb->getAllTags(localTags);
		m_localTagsModel.setTagList(localTags);
	}

	m_ui->serverTagsView->setItemDelegate(&m_serverTagsDelegate);
	m_ui->serverTagsView->setModel(&m_serverTagsModel);
	m_ui->serverTagsView->setSelectionMode(QAbstractItemView::ExtendedSelection);
	m_ui->serverTagsView->setSelectionBehavior(QAbstractItemView::SelectRows);

	connect(m_ui->serverTagsView->selectionModel(),
	    SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
	    this, SLOT(handleServerSelectionChange()));

	connect(m_ui->updateServerTagButton, SIGNAL(clicked()), this,
	    SLOT(updateServerTag()));

	{
		connect(&m_tagClient, SIGNAL(tagsInserted(Json::TagEntryList)),
		    this, SLOT(watchServerTagsInserted(Json::TagEntryList)));
		connect(&m_tagClient, SIGNAL(tagsUpdated(Json::TagEntryList)),
		    this, SLOT(watchServerTagsUpdated(Json::TagEntryList)));
		connect(&m_tagClient, SIGNAL(tagsDeleted(Json::Int64StringList)),
		    this, SLOT(watchServerTagsDeleted(Json::Int64StringList)));
	}

	m_ui->updateServerTagButton->setEnabled(false);
}

void DlgTagSettings::setIcons(void)
{
	m_ui->updateLocalTagButton->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_PENCIL));

	m_ui->toServerButton->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_RIGHT));

	m_ui->updateServerTagButton->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_PENCIL));
}
