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

#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 "ui_dlg_create_account.h"

/*!
 * @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_accountInfo(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_ui->setupUi(this);
	/* Tab order is defined in UI file. */

	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(m_accountInfo);
	}
}

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

bool DlgCreateAccount::modify(AcntData &accountInfo, enum Action action,
    const QString &syncAllActName, QWidget *parent)
{
	DlgCreateAccount dlg(accountInfo, action, syncAllActName, parent);
	if (QDialog::Accepted == dlg.exec()) {
		/* Store submitted data. */
		accountInfo = dlg.m_accountInfo;
		return true;
	} else {
		return false;
	}
}

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(certFileName);
		m_ui->addCertButton->setIcon(IconContainer::construcIcon(
		    IconContainer::ICON_DATOVKA_STOCK_KEY));
		m_ui->addCertButton->setToolTip(QString());
		m_certPath = certFileName;
		checkInputFields();
	}
}

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

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

/*!
 * @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)
{
	if (acntData.userName().isEmpty()) {
		Q_ASSERT(0);
		return;
	}

	QString windowTitle(this->windowTitle());

	switch (m_action) {
	case ACT_EDIT:
		windowTitle = tr("Update account %1")
		    .arg(acntData.accountName());
		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(acntData.syncWithAll());

	if (!acntData.p12File().isEmpty()) {
		m_ui->addCertButton->setText(
		    QDir::toNativeSeparators(acntData.p12File()));
		QFileInfo fileInfo(acntData.p12File());
		if (fileInfo.exists() && fileInfo.isFile() &&
		    fileInfo.isReadable()) {
			m_ui->addCertButton->setIcon(IconContainer::construcIcon(
			    IconContainer::ICON_DATOVKA_STOCK_KEY));
		} 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());
	}

	checkInputFields();
}

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;
}
