/*
 * Copyright (C) 2014-2020 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 <QApplication>
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>

#include "src/common.h"
#include "src/datovka_shared/isds/type_conversion.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/settings/pin.h"
#include "src/global.h"
#include "src/gui/dlg_create_account.h"
#include "src/gui/dlg_pin_input.h"
#include "src/gui/icon_container.h"
#include "src/gui/isds_login.h"
#include "src/io/account_db.h"
#include "src/io/account_db_tables.h"
#include "src/io/account_shadow_helper.h"
#include "src/io/dbs.h"
#include "src/io/isds_helper.h"
#include "src/io/message_db_set_container.h"
#include "src/io/message_db_set.h"
#include "src/isds/type_description.h"
#include "src/settings/accounts.h"
#include "ui_dlg_create_account.h"

#define PAGE_DESCRIPTION 0
#define PAGE_CREDENTIALS 1
#define PAGE_PRIVILEGES 2

/*!
 * @brief Login method order as they are listed in the dialogue.
 */
enum LoginMethodIndex {
	USER_NAME = 0,
	CERTIFICATE = 1,
	USER_CERTIFICATE = 2,
	HOTP = 3,
	TOTP = 4,
	MEP = 5
};

DlgCreateAccount::DlgCreateAccount(const AcntData &accountInfo,
    enum Action action, const QString &syncAllActName, QWidget *parent)
    : QDialog(parent),
    m_ui(new (std::nothrow) Ui::DlgCreateAccount),
    m_accountSettingsModel(this),
    m_regularAccounts(Q_NULLPTR),
    m_shadowAccounts(Q_NULLPTR),
    m_accountInfo(),
    m_action(action),
    m_showViewPwd((ACT_EDIT == action) && GlobInstcs::pinSetPtr->pinConfigured()),
    m_loginMethod(USER_NAME),
    m_certPath(),
    m_hidePwdTimer(this),
    m_viewPwdUpdate(100),
    m_viewPwdDuration(10000), /* 10 seconds */
    m_viewPwdRemainingCycles(0),
    m_treeViewMenu(),
    m_actionAddShadow(Q_NULLPTR),
    m_actionRemoveShadow(Q_NULLPTR)
{
	m_ui->setupUi(this);
	/* Tab order is defined in UI file. */

	m_ui->treeView->setModel(&m_accountSettingsModel);

	m_ui->splitter->setChildrenCollapsible(false);

	m_ui->addCertButton->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_PLUS));

	m_ui->loginMethodComboBox->addItem(tr("Password"));
	m_ui->loginMethodComboBox->addItem(tr("Certificate"));
	m_ui->loginMethodComboBox->addItem(tr("Certificate + Password"));
	m_ui->loginMethodComboBox->addItem(tr("Password + Security code"));
	m_ui->loginMethodComboBox->addItem(tr("Password + Security SMS"));
	m_ui->loginMethodComboBox->addItem(tr("Mobile key"));

	m_ui->viewPwdProgress->setFixedHeight(m_ui->pwdLine->height() / 4);
	m_ui->viewPwdProgress->setTextVisible(false);
	m_ui->viewPwdProgress->setVisible(false);

	m_ui->certLabel->setEnabled(false);
	m_ui->addCertButton->setEnabled(false);

	m_ui->mepLabel->setEnabled(false);
	m_ui->mepLine->setEnabled(false);

	m_ui->syncAllCheckBox->setText(
	    tr("Synchronise this account when '%1' is activated")
	        .arg(syncAllActName));

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

	connect(m_ui->loginMethodComboBox, SIGNAL(currentIndexChanged(int)),
	    this, SLOT(activateContent(int)));
	connect(m_ui->showHidePwdButton, SIGNAL(clicked()), this,
	    SLOT(togglePwdVisibility()));
	connect(m_ui->addCertButton, SIGNAL(clicked()), this,
	    SLOT(addCertificateFile()));
	connect(m_ui->buttonBox, SIGNAL(accepted()), this,
	    SLOT(collectAccountData()));
	connect(m_ui->acntNameLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->usernameLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->pwdLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->mepLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));

	connect(&m_hidePwdTimer, SIGNAL(timeout()), this,
	    SLOT(updatePwdVisibilityProgress()));

	m_ui->showHidePwdButton->setVisible(m_showViewPwd);

	/* Set dialogue content for existing account. */
	if (ACT_ADDNEW != m_action) {
		setContent(accountInfo, FC_EDITABLE);
	}
}

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

bool DlgCreateAccount::modify(AcntData &accountInfo, enum Action action,
    const QString &syncAllActName, int properties, QWidget *parent)
{
	DlgCreateAccount dlg(accountInfo, action, syncAllActName, parent);
	dlg.m_ui->treeView->setVisible(false);
	dlg.m_ui->treeViewButton->setVisible(false);
	dlg.m_ui->verticalStackedWidget->setCurrentIndex(PAGE_CREDENTIALS);
	if (properties != FC_NONE) {
		dlg.setContent(accountInfo, properties);
	}
	if (QDialog::Accepted == dlg.exec()) {
		/* Store submitted data. */
		accountInfo = dlg.m_accountInfo;
		return true;
	} else {
		return false;
	}
}

bool DlgCreateAccount::modify(AcntData &accountInfo, enum Action action,
    const QString &syncAllActName, QWidget *parent)
{
	return modify(accountInfo, action, syncAllActName, FC_NONE, parent);
}

bool DlgCreateAccount::manage(AccountsMap &regularAccounts,
    AccountsMap &shadowAccounts, const QString &syncAllActName,
    QWidget *parent)
{
	if (Q_UNLIKELY(regularAccounts.isEmpty())) {
		return false;
	}

	/* Manage a copy of shadow acounts. */
	AccountsMap shadowAccountsCopy(AcntContainer::CT_SHADOW);
	shadowAccountsCopy.load(shadowAccounts);

	DlgCreateAccount dlg(AcntData(), ACT_EDIT, syncAllActName, parent);
	dlg.m_regularAccounts = &regularAccounts;
	dlg.m_shadowAccounts = &shadowAccountsCopy;
	dlg.m_accountSettingsModel.loadContent(dlg.m_regularAccounts,
	    dlg.m_shadowAccounts);
	connect(dlg.m_ui->treeView->selectionModel(),
	    SIGNAL(currentChanged(QModelIndex, QModelIndex)),
	    &dlg, SLOT(accountCurrentChanged(QModelIndex, QModelIndex)));
	dlg.m_ui->treeView->expandAll();

	dlg.m_ui->treeViewButton->setPopupMode(QToolButton::InstantPopup);
	dlg.m_actionAddShadow = dlg.m_treeViewMenu.addAction(
	    tr("Add Shadow Account"), &dlg, SLOT(addShadowAccount()));
	dlg.m_actionRemoveShadow = dlg.m_treeViewMenu.addAction(
	    tr("Remove Shadow Account"), &dlg, SLOT(removeSelectedShadowAccount()));
	dlg.m_ui->treeViewButton->setMenu(&dlg.m_treeViewMenu);

	connect(dlg.m_ui->downloadInfoButton, SIGNAL(clicked()), &dlg,
	    SLOT(refreshAccountInformation()));
	dlg.m_ui->downloadInfoButton->setToolTip(
	    tr("Refresh account information from ISDS."));

	dlg.m_ui->treeView->setCurrentIndex(dlg.m_accountSettingsModel.index(
	    AccountSettingsModel::ROW_REGULAR, 0));

	if (QDialog::Accepted == dlg.exec()) {
		/* Collect data that might have been changed. */
		const QModelIndex current(dlg.m_ui->treeView->currentIndex());
		if ((dlg.m_shadowAccounts != Q_NULLPTR) &&
		    (AccountSettingsModel::nodeShadowAccountTop ==
		     AccountSettingsModel::nodeType(current))) {
			dlg.m_shadowAccounts->updateAccount(dlg.getContent());
		}
		/* Save edited copy when dialogue accepted. */
		shadowAccounts.load(shadowAccountsCopy);
		return true;
	} else {
		return false;
	}
}

bool DlgCreateAccount::accountExists(const AcntId &acntId,
    const AccountsMap &regularAccounts, const AccountsMap &shadowAccounts)
{
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return true; /* Fake the existence of the invalid account. */
	}

	return regularAccounts.keys().contains(acntId) ||
	    shadowAccounts.acntIds().contains(acntId);
}

QString DlgCreateAccount::htmlAccountInfo(const AcntData &acntData,
    MessageDbSet *dbSet)
{
	Q_ASSERT(acntIdFromData(acntData).isValid());

	QString html;
	DbEntry userEntry;
	DbEntry accountEntry;

	html.append(indentDivStart);
	html.append("<h3>");


	if (acntData.isTestAccount()) {
		html.append(tr("Test environment account"));
	} else {
		html.append(tr("Production environment account"));
	}
	html.append("<br/><img src=\":/dschranka.png\" alt=\"ISDS\">");
	html.append("</h3>");

	html.append(strongAccountInfoLine(tr("Account name"),
	    acntData.accountName()));

	const QString acntDbKey(AccountDb::keyFromLogin(acntData.userName()));
	if (GlobInstcs::accntDbPtr->dbId(acntDbKey).isEmpty()) {
		/*
		 * Generate this message if no account information can
		 * be obtained.
		 */
		html.append(QString("<div><strong>") +
		    tr("Account and user information could not be acquired.") +
		    QString("</strong></div>"));
		goto lastPart;
	}

	html.append("<table cellpadding=\"5\"><tr><td>");
	html.append(QString("<div><strong>") + tr("User information") +
	    QString("</strong></div>"));
	html.append("</td><td width=\"100\"></td><td>");
	html.append(QString("<div><strong>") + tr("Databox information") +
	    QString("</strong></div>"));
	html.append("</td></tr><tr><td>");

	userEntry = GlobInstcs::accntDbPtr->userEntry(acntDbKey);
	html.append(strongAccountInfoLine(tr("User name"), acntData.userName()));
	/* Print non-empty entries. */
	for (int i = 0; i < userinfTbl.knownAttrs.size(); ++i) {
		const QString &key = userinfTbl.knownAttrs[i].first;
		if (userEntry.hasValue(key) &&
		    !userinfTbl.attrProps[key].desc.isEmpty()) {
			switch (userinfTbl.knownAttrs[i].second) {
			case DB_INTEGER:
				if (key == "ic") {
					if (userEntry.value(key).toInt() > 0) {
						html.append(strongAccountInfoLine(
						    userinfTbl.attrProps[key].desc,
						    QString("%1").arg(
						        accountEntry.value(key).toInt(), 8, 10, QLatin1Char('0'))));
					}
				} else if (key == "userPrivils") {
					html.append(strongAccountInfoLineNoEscape(
					    userinfTbl.attrProps[key].desc,
					    Isds::Description::htmlDescrPrivileges(
					        Isds::variant2Privileges(userEntry.value(key)))));
				} else {
					html.append(strongAccountInfoLine(
					    userinfTbl.attrProps[key].desc,
					    QString::number(userEntry.
					        value(key).toInt())));
				}
				break;
			case DB_TEXT:
				if (key == "userType") {
					html.append(strongAccountInfoLine(
					    userinfTbl.attrProps[key].desc,
					    Isds::Description::descrSenderType(
					        Isds::variant2SenderType(userEntry.value(key)))));
				} else {
					html.append(strongAccountInfoLine(
					    userinfTbl.attrProps[key].desc,
					    userEntry.value(key).toString()));
				}
				break;
			case DB_DATE:
				html.append(strongAccountInfoLine(
				    userinfTbl.attrProps[key].desc,
				    dateStrFromDbFormat(
				        userEntry.value(key).toString(),
				        dateDisplayFormat)));
				break;
			default:
				Q_ASSERT(0);
				break;
			}
		}
	}

	html.append("</td><td></td><td>");

	accountEntry = GlobInstcs::accntDbPtr->accountEntry(acntDbKey);
	/* Print non-empty entries. */
	for (int i = 0; i < accntinfTbl.knownAttrs.size(); ++i) {
		const QString &key = accntinfTbl.knownAttrs[i].first;
		if (accountEntry.hasValue(key) &&
		    !accntinfTbl.attrProps[key].desc.isEmpty()) {
			switch (accntinfTbl.knownAttrs[i].second) {
			case DB_INTEGER:
				if (key == "dbState") {
					html.append(strongAccountInfoLine(
					    accntinfTbl.attrProps[key].desc,
					    Isds::Description::descrDbState(
					        Isds::variant2DbState(accountEntry.value(key)))));
				} else if (key == "ic") {
					if (accountEntry.value(key).toInt() > 0) {
						html.append(strongAccountInfoLine(
						    accntinfTbl.attrProps[key].desc,
						    QString("%1").arg(
						        accountEntry.value(key).toInt(), 8, 10, QLatin1Char('0'))));
					}
				} else {
					html.append(strongAccountInfoLine(
					    accntinfTbl.attrProps[key].desc,
					    QString::number(accountEntry.
					        value(key).toInt())));
				}
				break;
			case DB_TEXT:
				html.append(strongAccountInfoLine(
				    accntinfTbl.attrProps[key].desc,
				    accountEntry.value(key).toString()));
				break;
			case DB_BOOLEAN:
				html.append(strongAccountInfoLine(
				    accntinfTbl.attrProps[key].desc,
				    accountEntry.value(key).toBool() ?
				        tr("Yes") : tr("No")));
				break;
			case DB_DATETIME:
				html.append(strongAccountInfoLine(
				    accntinfTbl.attrProps[key].desc,
				    dateTimeStrFromDbFormat(
				        accountEntry.value(key).toString(),
				        dateTimeDisplayFormat)));
				break;
			case DB_DATE:
				html.append(strongAccountInfoLine(
				    accntinfTbl.attrProps[key].desc,
				    dateStrFromDbFormat(
				        accountEntry.value(key).toString(),
				        dateDisplayFormat)));
				break;
			default:
				Q_ASSERT(0);
				break;
			}
		}
	}

	html.append("</td></tr></table>");

lastPart:

	QString info(qDateTimeToDbFormat(GlobInstcs::accntDbPtr->getPwdExpirFromDb(acntDbKey)));
	if (info.isEmpty()) {
		info = tr("unknown or without expiration");
	} else {
		info = info.split(".")[0];
	}

	html.append(strongAccountInfoLine(tr("Password expiration date"),
	    info));

	if (dbSet != Q_NULLPTR) {
		QStringList dbFilePaths = dbSet->fileNames();
		if ((dbFilePaths.size() == 1) &&
		    (SQLiteDb::memoryLocation == dbFilePaths.first())) {
			QString dbFilePath = tr("Database is stored in memory. "
			    "Data will be lost on application exit.");
			html.append(strongAccountInfoLine(
			    tr("Local database file location"), dbFilePath));
		} else {
			foreach (const QString &path, dbFilePaths) {
				html.append(strongAccountInfoLine(
				    tr("Local database file location"), path));
			}
		}
	} else {
		html.append(strongAccountInfoLine2(
		    tr("Account has no own message database associated.")));
	}

	html.append(divEnd);

	return html;
}

void DlgCreateAccount::activateContent(int loginMethodIdx)
{
	m_loginMethod = loginMethodIdx;

	switch (m_loginMethod) {
	case CERTIFICATE:
		m_ui->pwdLabel->setEnabled(false);
		m_ui->pwdLine->setEnabled(false);
		m_ui->showHidePwdButton->setEnabled(false);
		m_ui->rememberPwdCheckBox->setEnabled(false);
		m_ui->certLabel->setEnabled(true);
		m_ui->addCertButton->setEnabled(true);
		m_ui->mepLabel->setEnabled(false);
		m_ui->mepLine->setEnabled(false);
		break;
	case USER_CERTIFICATE:
		m_ui->pwdLabel->setEnabled(true);
		m_ui->pwdLine->setEnabled(true);
		m_ui->showHidePwdButton->setEnabled(m_showViewPwd);
		m_ui->rememberPwdCheckBox->setEnabled(true);
		m_ui->certLabel->setEnabled(true);
		m_ui->addCertButton->setEnabled(true);
		m_ui->mepLabel->setEnabled(false);
		m_ui->mepLine->setEnabled(false);
		break;
	case MEP:
		m_ui->pwdLabel->setEnabled(false);
		m_ui->pwdLine->setEnabled(false);
		m_ui->showHidePwdButton->setEnabled(false);
		m_ui->rememberPwdCheckBox->setEnabled(false);
		m_ui->certLabel->setEnabled(false);
		m_ui->addCertButton->setEnabled(false);
		m_ui->mepLabel->setEnabled(true);
		m_ui->mepLine->setEnabled(true);
		break;
	default:
		m_ui->pwdLabel->setEnabled(true);
		m_ui->pwdLine->setEnabled(true);
		m_ui->showHidePwdButton->setEnabled(m_showViewPwd);
		m_ui->rememberPwdCheckBox->setEnabled(true);
		m_ui->certLabel->setEnabled(false);
		m_ui->addCertButton->setEnabled(false);
		m_ui->mepLabel->setEnabled(false);
		m_ui->mepLine->setEnabled(false);
		break;
	}

	checkInputFields();
}

void DlgCreateAccount::checkInputFields(void)
{
	bool enabled = false;

	switch (m_loginMethod) {
	case CERTIFICATE:
		enabled = !m_ui->acntNameLine->text().trimmed().isEmpty()
		    && !m_ui->usernameLine->text().trimmed().isEmpty()
		    && !m_certPath.isEmpty();
		break;
	case USER_CERTIFICATE:
		enabled = !m_ui->acntNameLine->text().trimmed().isEmpty()
		    && !m_ui->usernameLine->text().trimmed().isEmpty()
		    && !m_ui->pwdLine->text().isEmpty()
		    && !m_certPath.isEmpty();
		break;
	case MEP:
		enabled = !m_ui->acntNameLine->text().trimmed().isEmpty()
		    && !m_ui->usernameLine->text().trimmed().isEmpty()
		    && !m_ui->mepLine->text().isEmpty();
		break;
	default:
		enabled = !m_ui->acntNameLine->text().trimmed().isEmpty()
		    && !m_ui->usernameLine->text().trimmed().isEmpty()
		    && !m_ui->pwdLine->text().isEmpty();
		break;
	}

	m_ui->showHidePwdButton->setEnabled(
	     m_showViewPwd && !m_ui->pwdLine->text().isEmpty());
	m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enabled);
}

void DlgCreateAccount::togglePwdVisibility(void)
{
	enum QLineEdit::EchoMode newEchoMode = QLineEdit::Password;

	switch (m_ui->pwdLine->echoMode()) {
	case QLineEdit::Normal:
		break;
	case QLineEdit::Password:
		if (!DlgPinInput::queryPin(*GlobInstcs::pinSetPtr, false, this)) {
			return;
		}
		/* Pin was entered correctly. */
		newEchoMode = QLineEdit::Normal;
		break;
	default:
		Q_ASSERT(0);
		break;
	}

	setPwdLineEchoMode(newEchoMode);
}

void DlgCreateAccount::updatePwdVisibilityProgress(void)
{
	--m_viewPwdRemainingCycles;

	if (Q_UNLIKELY(m_viewPwdRemainingCycles < 0)) {
		Q_ASSERT(0);
		m_viewPwdRemainingCycles = 0;
	}

	m_ui->viewPwdProgress->setValue(m_viewPwdRemainingCycles);

	if (Q_UNLIKELY(m_viewPwdRemainingCycles == 0)) {
		setPwdLineEchoMode(QLineEdit::Password);
	}
}

void DlgCreateAccount::addCertificateFile(void)
{
	const QString certFileName(QFileDialog::getOpenFileName(this,
	    tr("Open Certificate"), QString(),
	    tr("Certificate Files (*.pfx *.p12 *.pem)")));
	if (!certFileName.isEmpty()) {
		m_ui->addCertButton->setText(QFileInfo(certFileName).fileName());
		m_ui->addCertButton->setIcon(IconContainer::construcIcon(
		    IconContainer::ICON_DATOVKA_STOCK_KEY));
		m_ui->addCertButton->setToolTip(certFileName);
		m_certPath = certFileName;
		checkInputFields();
	}
}

void DlgCreateAccount::collectAccountData(void)
{
	debugSlotCall();

	/* Store the submitted settings. */
	m_accountInfo = getContent();
}

void DlgCreateAccount::accountCurrentChanged(const QModelIndex &current,
    const QModelIndex &previous)
{
	enum AccountSettingsModel::NodeType previousType =
	    AccountSettingsModel::nodeType(previous);

	switch (previousType) {
	case AccountSettingsModel::nodeShadowAccountTop:
		if (m_shadowAccounts != Q_NULLPTR) {
			m_shadowAccounts->updateAccount(getContent());
		}
		break;
	default:
		break;
	}

	enum AccountSettingsModel::NodeType currentType =
	    AccountSettingsModel::nodeType(current);

	switch (currentType) {
	case AccountSettingsModel::nodeUnknown:
	case AccountSettingsModel::nodeRoot:
		Q_ASSERT(0);
		m_ui->verticalStackedWidget->setCurrentIndex(PAGE_DESCRIPTION);
		m_ui->descriptionLabel->setText(QString());
		break;
	case AccountSettingsModel::nodeRegular:
		m_ui->verticalStackedWidget->setCurrentIndex(PAGE_DESCRIPTION);
		setWindowTitle(tr("Regular accounts"));
		m_ui->descriptionLabel->setText(tr(
		    "Regular account are accounts which you can see listed in the main window of the application.\n\n"
		    "Database files containing message data are associated with these accounts."));
		break;
	case AccountSettingsModel::nodeShadow:
		m_ui->verticalStackedWidget->setCurrentIndex(PAGE_DESCRIPTION);
		setWindowTitle(tr("Shadow accounts"));
		m_ui->descriptionLabel->setText(tr(
		    "Shadow accounts are accounts which exist in the background of the application.\n\n"
		    "Shadow accounts have low access privileges and don't have any databases associated to them. "
		    "They become useful when you have access to a data box to which multiple user accounts have access to. "
		    "In this case an account which can only download message lists (and send messages) can be used as a shadow account.\n\n"
		    "Accounts which can only download message lists (and send messages) can't cause the acceptance of delivered messages. "
		    "These accounts can then be used to automatically check whether there are newly delivered messages without accepting them."));
		break;
	case AccountSettingsModel::nodeRegularAccountTop:
		if (m_regularAccounts != Q_NULLPTR) {
			m_ui->verticalStackedWidget->setCurrentIndex(PAGE_CREDENTIALS);
			setContent(
			    m_regularAccounts->acntData(m_accountSettingsModel.acntId(current)),
			    FC_NONE);
		}
		break;
	case AccountSettingsModel::nodeShadowAccountTop:
		if (m_shadowAccounts != Q_NULLPTR) {
			m_ui->verticalStackedWidget->setCurrentIndex(PAGE_CREDENTIALS);
			setContent(
			    m_shadowAccounts->acntData(m_accountSettingsModel.acntId(current)),
			    FC_SHADOW_ACNT | FC_EDITABLE);
			/*
			 * Disable username editing.
			 * TODO -- Make it less crude.
			 */
			m_ui->usernameLine->setEnabled(false);
		}
		break;
	case AccountSettingsModel::nodeRegularAccountInfo:
		{
			m_ui->verticalStackedWidget->setCurrentIndex(PAGE_PRIVILEGES);
			const AcntId acntId(m_accountSettingsModel.acntId(current));
			loadPrivilegesContent(acntId, currentType);
			setWindowTitle(tr("Account info %1").arg(
			    m_regularAccounts->acntData(acntId).accountName()));
		}
		break;
	case AccountSettingsModel::nodeShadowAccountInfo:
		{
			m_ui->verticalStackedWidget->setCurrentIndex(PAGE_PRIVILEGES);
			const AcntId acntId(m_accountSettingsModel.acntId(current));
			loadPrivilegesContent(acntId, currentType);
			setWindowTitle(tr("Account info %1").arg(
			    m_shadowAccounts->acntData(acntId).accountName()));
		}
		break;
	default:
		break;
	}

	m_actionRemoveShadow->setEnabled(
	    currentType == AccountSettingsModel::nodeShadowAccountTop);
}

/*!
 * @brief Download account information from ISDS and update account database.
 *
 * @param[in,out] acntData Account data may be changed when login credentials
 *                         are not sufficient or are wrong.
 * @param[in]     syncAllActionName Synchronise all action name.
 * @param[in]     parent Parent widget.
 */
static
void downloadAccountInformation(AcntData &acntData,
    const QString &syncAllActionName, QWidget *parent)
{
	{
		Gui::LogInCycle login(syncAllActionName, parent);
		bool ret = login.logIn(*GlobInstcs::isdsSessionsPtr, acntData);
		if (!ret) {
			return;
		}
	}

	const AcntId acntId(acntIdFromData(acntData));

	if (!IsdsHelper::getOwnerInfoFromLogin(acntId)) {
		//TODO: return false;
	}
	if (!IsdsHelper::getUserInfoFromLogin(acntId)) {
		//TODO: return false;
	}
	if (!IsdsHelper::getPasswordInfoFromLogin(acntId)) {
		//TODO: return false;
	}
}

void DlgCreateAccount::addShadowAccount(void)
{
	AcntData newShadowSettings;
	if (!DlgCreateAccount::modify(newShadowSettings,
	        DlgCreateAccount::ACT_ADDNEW, m_ui->syncAllCheckBox->text(),
	        FC_SHADOW_ACNT, this)) {
		return;
	}

	const AcntId newShadowAcntId(acntIdFromData(newShadowSettings));
	if (accountExists(newShadowAcntId, *m_regularAccounts, *m_shadowAccounts)) {
		QMessageBox::warning(this, tr("Adding new shadow account failed"),
		    tr("An account using the supplied username already exists. Account has not been added."),
		    QMessageBox::Ok);
		return;
	}

	downloadAccountInformation(newShadowSettings,
	    m_ui->syncAllCheckBox->text(), this);

	m_shadowAccounts->addAccount(newShadowSettings);

	const AcntId newAcntId = acntIdFromData(newShadowSettings);
	QModelIndex index = m_accountSettingsModel.indexShadowAccountTop(newAcntId);
	if (index.isValid()) {
		m_ui->treeView->selectionModel()->setCurrentIndex(index,
		    QItemSelectionModel::ClearAndSelect);
		m_ui->treeView->expand(index);
	}

	if (!ShadowHelper::isUsableBackgroundAccount(newAcntId,
	        *m_shadowAccounts)) {
		QMessageBox::warning(this, tr("Unusable Shadow Account"),
		    tr("This account won't be used to download message lists on background. View the account information for more detail."),
		    QMessageBox::Ok);
	}
}

void DlgCreateAccount::removeSelectedShadowAccount(void)
{
	if (QMessageBox::Yes != QMessageBox::question(this,
	        tr("Remove Shadow Account?"),
		tr("Do you want to remove the shadow account '%1'?").arg(m_ui->acntNameLine->text()),
		QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
		return;
	}

	const QModelIndex current(m_ui->treeView->currentIndex());
	if (Q_UNLIKELY(AccountSettingsModel::nodeShadowAccountTop !=
	        AccountSettingsModel::nodeType(current))) {
		Q_ASSERT(0);
		return;
	}

	m_shadowAccounts->removeAccount(m_accountSettingsModel.acntId(current));

	/* Select parent node. */
	m_ui->treeView->setCurrentIndex(m_accountSettingsModel.index(
	    AccountSettingsModel::ROW_SHADOW, 0));
}

void DlgCreateAccount::refreshAccountInformation(void)
{
	m_ui->downloadInfoButton->setEnabled(false);
	QApplication::processEvents();

	const QModelIndex current(m_ui->treeView->currentIndex());
	enum AccountSettingsModel::NodeType currentType =
	    AccountSettingsModel::nodeType(current);

	switch (currentType) {
	case AccountSettingsModel::nodeRegularAccountInfo:
	case AccountSettingsModel::nodeShadowAccountInfo:
		break;
	default:
		Q_ASSERT(0);
		m_ui->downloadInfoButton->setEnabled(true);
		return;
		break;
	}

	const AcntId acntId(m_accountSettingsModel.acntId(current));

	{
		/* Login and download account information. */

		AcntData acntData;
		switch (currentType) {
		case AccountSettingsModel::nodeRegularAccountInfo:
			acntData = m_regularAccounts->acntData(acntId);
			break;
		case AccountSettingsModel::nodeShadowAccountInfo:
			acntData = m_shadowAccounts->acntData(acntId);
			break;
		default:
			Q_ASSERT(0);
			m_ui->downloadInfoButton->setEnabled(true);
			return;
			break;
		}

		{
			AcntData newAcntData = acntData;
			downloadAccountInformation(newAcntData,
			    m_ui->syncAllCheckBox->text(), this);
			/* Store modifications. */
			if (newAcntData != acntData) {
				switch (currentType) {
				case AccountSettingsModel::nodeRegularAccountInfo:
					/* TODO -- Update regular account data. */
					break;
				case AccountSettingsModel::nodeShadowAccountInfo:
					m_shadowAccounts->updateAccount(newAcntData);
					break;
				default:
					break;
				}
			}
		}
	}

	loadPrivilegesContent(acntId, currentType);

	m_ui->downloadInfoButton->setEnabled(true);
}

/*!
 * @brief Conversion function.
 *
 * @param[in] method Login method.
 * @return Combo box index.
 */
static
enum LoginMethodIndex loginMethodToIndex(enum AcntSettings::LoginMethod method)
{
	switch (method) {
	case AcntSettings::LIM_UNAME_PWD:
		return USER_NAME;
		break;
	case AcntSettings::LIM_UNAME_CRT:
		return CERTIFICATE;
		break;
	case AcntSettings::LIM_UNAME_PWD_CRT:
		return USER_CERTIFICATE;
		break;
	case AcntSettings::LIM_UNAME_PWD_HOTP:
		return HOTP;
		break;
	case AcntSettings::LIM_UNAME_PWD_TOTP:
		return TOTP;
		break;
	case AcntSettings::LIM_UNAME_MEP:
		return MEP;
		break;
	default:
		Q_ASSERT(0);
		return USER_NAME;
		break;
	}
}

/*!
 * @brief Conversion function.
 *
 * @param[in] Combo box index.
 * @return Login method.
 */
static
enum AcntSettings::LoginMethod indexToLoginMethod(int index)
{
	switch (index) {
	case USER_NAME:
		return AcntSettings::LIM_UNAME_PWD;
		break;
	case CERTIFICATE:
		return AcntSettings::LIM_UNAME_CRT;
		break;
	case USER_CERTIFICATE:
		return AcntSettings::LIM_UNAME_PWD_CRT;
		break;
	case HOTP:
		return AcntSettings::LIM_UNAME_PWD_HOTP;
		break;
	case TOTP:
		return AcntSettings::LIM_UNAME_PWD_TOTP;
		break;
	case MEP:
		return AcntSettings::LIM_UNAME_MEP;
		break;
	default:
		Q_ASSERT(0);
		return AcntSettings::LIM_UNKNOWN;
		break;
	}
}

void DlgCreateAccount::setContent(const AcntData &acntData, int properties)
{
	m_accountInfo = acntData;

	if (Q_UNLIKELY(acntData.userName().isEmpty())) {
		m_ui->infoLabel->setEnabled(false);
		m_ui->acntNameLine->clear();
		m_ui->usernameLine->clear();
		m_ui->testAcntCheckBox->setCheckState(Qt::Unchecked);
		m_ui->loginMethodComboBox->setCurrentIndex(USER_NAME);
		m_ui->pwdLine->clear();
		m_ui->rememberPwdCheckBox->setCheckState(Qt::Unchecked);
		m_ui->mepLine->clear();
		m_ui->syncAllCheckBox->setEnabled((properties & FC_EDITABLE) && (!(properties & FC_SHADOW_ACNT)));
		m_ui->syncAllCheckBox->setCheckState(Qt::Unchecked);
		return;
	}

	m_ui->infoLabel->setEnabled(true);

	QString windowTitle(this->windowTitle());

	switch (m_action) {
	case ACT_EDIT:
		windowTitle = tr("Update account %1")
		    .arg(acntData.accountName());
		m_ui->infoLabel->setEnabled(true);
		m_ui->acntNameLine->setEnabled(true);
		break;
	case ACT_PWD:
		windowTitle = tr("Enter password for account %1")
		    .arg(acntData.accountName());
		m_ui->infoLabel->setEnabled(false);
		m_ui->acntNameLine->setEnabled(false);
		m_ui->loginMethodComboBox->setEnabled(false);
		m_ui->addCertButton->setEnabled(false);
		break;
	case ACT_CERT:
		windowTitle = tr("Set certificate for account %1")
		    .arg(acntData.accountName());
		m_ui->infoLabel->setEnabled(false);
		m_ui->acntNameLine->setEnabled(false);
		m_ui->loginMethodComboBox->setEnabled(false);
		m_ui->pwdLine->setEnabled(false);
		m_ui->showHidePwdButton->setEnabled(false);
		break;
	case ACT_CERTPWD:
		windowTitle = tr("Enter password/certificate for account %1")
		    .arg(acntData.accountName());
		m_ui->infoLabel->setEnabled(false);
		m_ui->acntNameLine->setEnabled(false);
		m_ui->loginMethodComboBox->setEnabled(false);
		break;
	case ACT_MEP:
		windowTitle = tr("Enter communication code for account %1")
		    .arg(acntData.accountName());
		m_ui->infoLabel->setEnabled(false);
		m_ui->acntNameLine->setEnabled(false);
		m_ui->loginMethodComboBox->setEnabled(false);
		break;
	default:
		Q_ASSERT(0);
		break;
	}

	setWindowTitle(windowTitle);
	m_ui->acntNameLine->setText(acntData.accountName());
	m_ui->usernameLine->setText(acntData.userName());
	m_ui->usernameLine->setEnabled((m_action == ACT_EDIT) || (m_action == ACT_ADDNEW));
	m_ui->testAcntCheckBox->setEnabled(m_action == ACT_ADDNEW);

	enum LoginMethodIndex itemIdx =
	    loginMethodToIndex(acntData.loginMethod());
	m_ui->loginMethodComboBox->setCurrentIndex(itemIdx);
	activateContent(itemIdx);

	m_ui->testAcntCheckBox->setChecked(acntData.isTestAccount());
	m_ui->pwdLine->setText(acntData.password());
	m_ui->rememberPwdCheckBox->setChecked(acntData.rememberPwd());
	m_ui->mepLine->setText(acntData.mepToken());
	m_ui->syncAllCheckBox->setChecked((!(properties & FC_SHADOW_ACNT)) && acntData.syncWithAll());

	if (!acntData.p12File().isEmpty()) {
		QFileInfo fileInfo(acntData.p12File());
		m_ui->addCertButton->setText(fileInfo.fileName());
		if (fileInfo.exists() && fileInfo.isFile() &&
		    fileInfo.isReadable()) {
			m_ui->addCertButton->setIcon(IconContainer::construcIcon(
			    IconContainer::ICON_DATOVKA_STOCK_KEY));
			m_ui->addCertButton->setToolTip(acntData.p12File());
		} else {
			m_ui->addCertButton->setIcon(IconContainer::construcIcon(
			    IconContainer::ICON_WARNING));
			m_ui->addCertButton->setToolTip(tr("File does not exists or cannot be read."));
		}
		m_certPath = QDir::toNativeSeparators(acntData.p12File());
	} else {
		m_ui->addCertButton->setIcon(IconContainer::construcIcon(
		    IconContainer::ICON_PLUS));
		m_ui->addCertButton->setText(tr("Add"));
		m_ui->addCertButton->setToolTip(tr("Select a certificate."));
	}

	if (!(properties & FC_EDITABLE)) {
		m_ui->infoLabel->setEnabled(false);
		m_ui->acntNameLine->setEnabled(false);
		m_ui->usernameLine->setEnabled(false);
		m_ui->testAcntCheckBox->setEnabled(false);
		m_ui->loginMethodComboBox->setEnabled(false);
		m_ui->pwdLine->setEnabled(false);
		m_ui->rememberPwdCheckBox->setEnabled(false);
		m_ui->mepLine->setEnabled(false);
		m_ui->addCertButton->setEnabled(false);
	}
	/* Disable for shadow accounts. */
	m_ui->syncAllCheckBox->setEnabled(
	    (properties & FC_EDITABLE) && (!(properties & FC_SHADOW_ACNT)));

	checkInputFields();
}

void DlgCreateAccount::loadPrivilegesContent(const AcntId &acntId,
    enum AccountSettingsModel::NodeType nodeType)
{
	switch (nodeType) {
	case AccountSettingsModel::nodeRegularAccountInfo:
		m_ui->accountInfoLabel->setVisible(m_shadowAccounts != Q_NULLPTR);
		m_ui->downloadInfoButton->setVisible(true);
		if (m_ui->accountInfoLabel->isVisible()) {
			QList<AcntId> shadowAcnts =
			    ShadowHelper::usableBackgroundAccounts(
			        acntId, *m_regularAccounts, *m_shadowAccounts);
			if (!shadowAcnts.isEmpty()) {
				QString text = tr("The following shadow accounts can be used by this account:");
				foreach (const AcntId &sAcntId, shadowAcnts) {
					const AcntData acntData = m_shadowAccounts->acntData(sAcntId);
					text += "\n- " + acntData.accountName() + " (" + acntData.userName() + ")";
				}
				m_ui->accountInfoLabel->setText(text);
			} else {
				m_ui->accountInfoLabel->setText(
				    tr("There is no shadow account available for this account."));
			}
		}
		m_ui->accountTextInfo->setHtml(htmlAccountInfo(
		    m_regularAccounts->acntData(acntId),
		    GlobInstcs::msgDbsPtr->accessOpenedDbSet(acntId)));
		break;
	case AccountSettingsModel::nodeShadowAccountInfo:
		m_ui->accountInfoLabel->setVisible(m_shadowAccounts != Q_NULLPTR);
		m_ui->downloadInfoButton->setVisible(true);
		if (m_ui->accountInfoLabel->isVisible()) {
			if (ShadowHelper::isUsableBackgroundAccount(acntId,
			        *m_shadowAccounts)) {
				QString text = tr(
				    "This account can be used to download message lists in background because:\n"
				    "- It cannot download any received message because of low access privileges and thus won't cause any message to be accepted.\n"
				    "- It uses a login procedure which does not require user interaction (username and password) and all login credentials are remembered by the application."
				);
				QList<AcntId> regularAcnts =
				    ShadowHelper::availableForegroundAccounts(
				        acntId, *m_regularAccounts, *m_shadowAccounts);
				if (!regularAcnts.isEmpty()) {
					text += "\n\n" + tr("The following regular accounts may utilise this account:");
					foreach (const AcntId &rAcntId, regularAcnts) {
						const AcntData acntData = m_regularAccounts->acntData(rAcntId);
						text += "\n- " + acntData.accountName() + " (" + acntData.userName() + ")";
					}
				}
				m_ui->accountInfoLabel->setText(text);
			} else {
				m_ui->accountInfoLabel->setText(
				    tr("This account won't be used to download message lists in background because it doesn't meet the following requirements:\n"
				        "- The account must have low access privileges. It must be able to download message lists but it must be prohibited from downloading received messages.\n"
				        "- It must use a login procedure which does not require user interaction (username and password) and complete login credentials must be remembered by the application."));
			}
		}
		m_ui->accountTextInfo->setHtml(htmlAccountInfo(
		    m_shadowAccounts->acntData(acntId),
		    GlobInstcs::msgDbsPtr->accessOpenedDbSet(acntId)));
		break;
	default:
		m_ui->accountInfoLabel->setVisible(false);
		m_ui->downloadInfoButton->setVisible(false);
		m_ui->accountTextInfo->setText(QString());
		m_ui->accountTextInfo->setReadOnly(true);
		return;
		break;
	}

	m_ui->accountTextInfo->setReadOnly(true);
}

void DlgCreateAccount::setPwdLineEchoMode(int echoMode)
{
	enum QLineEdit::EchoMode mode = QLineEdit::Password;
	QString buttonLabel(tr("View"));

	switch (echoMode) {
	case QLineEdit::Normal:
		mode = QLineEdit::Normal;
		buttonLabel = tr("Hide");
		break;
	case QLineEdit::Password:
		break;
	default:
		Q_ASSERT(0);
		break;
	}

	if (mode == QLineEdit::Password) {
		m_hidePwdTimer.stop();
		m_ui->viewPwdProgress->setVisible(false);
	}
	m_ui->pwdLine->setEchoMode(mode);
	m_ui->showHidePwdButton->setText(buttonLabel);
	if (mode == QLineEdit::Normal) {
		m_ui->viewPwdProgress->setVisible(true);
		m_ui->viewPwdProgress->setMinimum(0);
		m_viewPwdRemainingCycles = m_viewPwdDuration / m_viewPwdUpdate;
		m_ui->viewPwdProgress->setMaximum(m_viewPwdRemainingCycles);
		m_hidePwdTimer.start(m_viewPwdUpdate);
	}
}

AcntData DlgCreateAccount::getContent(void) const
{
	AcntData newAcntSettings;

	switch (m_action) {
	case ACT_ADDNEW:
		/*
		 * Set to true only for newly created account.
		 * Don't set it to false when account is edited because a
		 * newly created account can also be edited in case the first
		 * account creation attempt failed.
		 */
		newAcntSettings._setCreatedFromScratch(true);
		break;
	case ACT_EDIT:
	case ACT_PWD:
	case ACT_CERT:
	case ACT_CERTPWD:
	case ACT_MEP:
		Q_ASSERT(!m_accountInfo.userName().isEmpty());
		newAcntSettings = m_accountInfo;
		break;
	default:
		Q_ASSERT(0);
		break;
	}

	/* Set account entries. */
	newAcntSettings.setAccountName(m_ui->acntNameLine->text().trimmed());
	newAcntSettings.setUserName(m_ui->usernameLine->text().trimmed());
	newAcntSettings.setTestAccount(m_ui->testAcntCheckBox->isChecked());
	newAcntSettings.setPassword(m_ui->pwdLine->text());
	newAcntSettings.setRememberPwd(m_ui->rememberPwdCheckBox->isChecked());
	newAcntSettings.setSyncWithAll(m_ui->syncAllCheckBox->isChecked());

	newAcntSettings.setLoginMethod(
	    indexToLoginMethod(m_ui->loginMethodComboBox->currentIndex()));
	switch (m_ui->loginMethodComboBox->currentIndex()) {
	case CERTIFICATE:
		newAcntSettings.setPassword(QString());
		newAcntSettings.setP12File(
		    QDir::fromNativeSeparators(m_certPath));
		break;
	case USER_CERTIFICATE:
		newAcntSettings.setP12File(
		    QDir::fromNativeSeparators(m_certPath));
		break;
	case USER_NAME:
	case HOTP:
	case TOTP:
		newAcntSettings.setP12File(QString());
		break;
	case MEP:
		newAcntSettings.setMepToken(m_ui->mepLine->text());
		newAcntSettings.setP12File(QString());
		break;
	default:
		Q_ASSERT(0);
		break;
	}

	return newAcntSettings;
}
