/*
 * Copyright (C) 2014-2024 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 <algorithm> /* ::std::sort */
#include <QList>
#include <QLocale>
#include <QSet>

#include "src/datovka_shared/compat/compiler.h" /* macroStdMove */
#include "src/datovka_shared/isds/box_interface2.h"
#include "src/datovka_shared/isds/type_description.h"
#include "src/datovka_shared/localisation/countries.h"
#include "src/datovka_shared/localisation/localisation.h"
#include "src/dimensions/dimensions.h"
#include "src/global.h"
#include "src/gui/dlg_user_ext2.h"
#include "src/settings/prefs_specific.h"
#include "ui_dlg_user_ext2.h"

#define CODE_LEN 2

/*!
 * @brief Populate state combo box model.
 *
 * @param[in,out] model Model to be populated.
 * @param[in] list Data to be loaded.
 */
static
void loadStates(CBoxModel &model, const QList< QPair<QString, int> > &list)
{
	typedef QPair<QString, int> Pair;

	foreach (const Pair &pair, list) {
		model.appendRow(pair.first, pair.second);
	}
}

/*!
 * @brief Populate user type combo box model.
 *
 * @note See pril_3/WS_sprava_datovych_schranek.pdf, chapter 2.4
 *     (description of AddDataBoxUser2) for a listing of available user types
 *     to be created for a specific data-box type.
 *
 * @param[in,out] model Model to be populated.
 * @param[in] userType Type of the edited user, may contain null value.
 * @param[in] dbType Type of data box.
 * @return False if the \a userType was not contained within the usual values.
 */
static
bool loadUserTypes(CBoxModel &model, enum Isds::Type::UserType userType,
    enum Isds::Type::DbType dbType)
{
	bool withinDefaultUserTypes = true;
	QSet<enum Isds::Type::UserType> typeSet;

	typeSet.insert(Isds::Type::UT_ENTRUSTED);
	typeSet.insert(Isds::Type::UT_ADMINISTRATOR);

	Q_UNUSED(dbType);
#if 0
	/* The web interface allows adding only entrusted and administrators. */

	switch (dbType) {
	case Isds::Type::BT_PO:
	case Isds::Type::BT_PO_ZAK:
	case Isds::Type::BT_PO_REQ:
		typeSet.insert(Isds::Type::UT_PRIMARY);
		break;
	default:
		break;
	}

	switch (dbType) {
	case Isds::Type::BT_PO:
	case Isds::Type::BT_PO_ZAK:
	case Isds::Type::BT_PO_REQ:
	case Isds::Type::BT_OVM:
	case Isds::Type::BT_OVM_REQ:
		typeSet.insert(Isds::Type::UT_LIQUIDATOR);
		typeSet.insert(Isds::Type::UT_RECEIVER);
		typeSet.insert(Isds::Type::UT_GUARDIAN);
		break;
	default:
		break;
	}
#endif

	/* Add user type if missing. */
	if ((userType != Isds::Type::UT_NULL) && (!typeSet.contains(userType))) {
		withinDefaultUserTypes = false;
		typeSet.insert(userType);
	}

	/* Sort set and populate model. */
	QList<enum Isds::Type::UserType> typeList;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
	typeList = QList<enum Isds::Type::UserType>(typeSet.begin(), typeSet.end());
#else /* < Qt-5.14.0 */
	typeList = typeSet.toList();
#endif /* >= Qt-5.14.0 */
	::std::sort(typeList.begin(), typeList.end());

	foreach (enum Isds::Type::UserType type, typeList) {
		model.appendRow(Isds::Description::descrUserType(type), type);
	}

	return withinDefaultUserTypes;
}

DlgUserExt2::DlgUserExt2(const Isds::DbUserInfoExt2 &user,
    const Isds::DbOwnerInfoExt2 &owner, EditableFields fields, QWidget *parent)
    : QDialog(parent),
    m_ui(new (::std::nothrow) Ui::DlgUserExt2),
    m_owner(owner),
    m_statesCBoxModel(this),
    m_userTypeCBoxModel(this),
    m_preferStateCBox(true),
    m_preferCaStateCBox(true)
{
	m_ui->setupUi(this);

	/* Populate state combo box model. */
	if (Localisation::programLocale.language() == QLocale::Czech) {
		loadStates(m_statesCBoxModel,
		    Localisation::countryNamesCz(Localisation::countryList));
	} else {
		loadStates(m_statesCBoxModel,
		    Localisation::countryNamesEn(Localisation::countryList));
	}
	m_ui->stateComboBox->setModel(&m_statesCBoxModel);
	m_ui->caStateComboBox->setModel(&m_statesCBoxModel);

	if (!loadUserTypes(m_userTypeCBoxModel, user.userType(), m_owner.dbType())) {
		m_ui->userTypeComboBox->setEnabled(false);
	}
	m_ui->userTypeComboBox->setModel(&m_userTypeCBoxModel);

	connect(m_ui->conscriptNoLine, SIGNAL(textChanged(QString)),
	    this, SLOT(excludeConscriptionVsEvidenceFileds()));
	connect(m_ui->evidenceNoLine, SIGNAL(textChanged(QString)),
	    this, SLOT(excludeConscriptionVsEvidenceFileds()));

	connect(m_ui->useResidenceRadioButton, SIGNAL(toggled(bool)),
	    this, SLOT(toggleCaAddressNone(bool)));
	connect(m_ui->useFirmRadioButton, SIGNAL(toggled(bool)),
	    this, SLOT(toggleCaAddressFirm(bool)));
	connect(m_ui->useCaRadioButton, SIGNAL(toggled(bool)),
	    this, SLOT(toggleCaAddressCustom(bool)));

	/* Default radio button selection. */
	m_ui->useResidenceRadioButton->setChecked(true);

	loadContent(user);

	enableContent(fields);

	/* Explicitly disable firm contact address when editing owner. */
	if (user.userType() == Isds::Type::UT_PRIMARY) {
		m_ui->useFirmRadioButton->setEnabled(false);
	}
}

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

bool DlgUserExt2::edit(Isds::DbUserInfoExt2 &user,
    const Isds::DbOwnerInfoExt2 &owner, EditableFields fields, QWidget *parent)
{
	DlgUserExt2 dlg(user, owner, fields, parent);

	const QString dlgName("user_ext2");
	const QSize dfltSize = dlg.size();
	{
		const QSize newSize = Dimensions::dialogueSize(&dlg,
		    PrefsSpecific::dlgSize(*GlobInstcs::prefsPtr, dlgName),
		    dfltSize);
		if (newSize.isValid()) {
			dlg.resize(newSize);
		}
	}

	int ret = dlg.exec();

	PrefsSpecific::setDlgSize(*GlobInstcs::prefsPtr, dlgName,
	    dlg.size(), dfltSize);

	if (QDialog::Accepted == ret) {
		dlg.collectContent(user);
		return true;
	}

	return false;
}

bool DlgUserExt2::isEvidenceNumber(const QString &no)
{
	return (!no.isEmpty()) && ((no.at(0).toLower() == QChar('e')));
}

QString DlgUserExt2::stripEvidenceNumber(const QString &no)
{
	if (isEvidenceNumber(no)) {
		/* Strip leading character. */
		return no.mid(1, -1);
	}

	return no;
}

void DlgUserExt2::excludeConscriptionVsEvidenceFileds(void)
{
	m_ui->conscriptNoLine->setEnabled(m_ui->evidenceNoLine->text().isEmpty());
	m_ui->evidenceNoLine->setEnabled(m_ui->conscriptNoLine->text().isEmpty());
}

void DlgUserExt2::toggleCaAddressNone(bool checked)
{
	m_ui->caAddressWidget->setVisible(!checked);
}

/*!
 * @brief Select state in combo box or set a line edit if no country code
 *     supplied.
 *
 * @param[out] preferCBox Whether to prefer the combo box over the line edit.
 * @param[in,out] cBox State combo box
 * @param[in,out] line Line edit.
 * @param[in] state String containing country code or country name.
 */
static
void selectState(bool &preferCBox, QComboBox &cBox, QLineEdit &line,
    const QString &state)
{
	if (state.isEmpty() || (state.size() == CODE_LEN)) {
		int pos = Localisation::posOfCountryCode(
		    Localisation::countryList, state);
		if (pos < 0) { /* Not found. Try CZ first. */
			pos = Localisation::posOfCountryCode(
			    Localisation::countryList, "CZ");
		}
		if (pos < 0) {
			Q_ASSERT(0);
			pos = 0;
		}
		preferCBox = true;
		cBox.setVisible(true);
		cBox.setCurrentIndex(pos);
		line.setVisible(false);
		line.setText(QString());
	} else {
		/* Handle strings instead of country codes. */
		preferCBox = false;
		cBox.setVisible(false);
		cBox.setCurrentIndex(0);
		line.setVisible(true);
		line.setText(state);
	}
}

/*!
 * @brief Populate user contact address form from supplied address.
 *
 * @param[in] address Full address.
 * @param[in,out] streetLine Street line edit.
 * @param[in,out] cityLine City line edit.
 * @param[in,out] zipCodeLine ZIP code line edit.
 * @param[out] preferCBox True when combo box should be preferred.
 * @param[in,out] stateComboBox State combo box.
 * @param[in,out] stateLine State line edit.
 */
static
void loadCaAddress(const Isds::AddressExt2 &address, QLineEdit &streetLine,
    QLineEdit &cityLine, QLineEdit &zipCodeLine,
    bool &preferCBox, QComboBox &stateComboBox, QLineEdit &stateLine)
{
	{
		QString street;
		if (!address.street().isEmpty()) {
			street = address.street();
			if ((!address.numberInMunicipality().isEmpty()) &&
			    (!address.numberInStreet().isEmpty())) {
				street += QChar(' ') +
				    address.numberInMunicipality() + QChar('/') +
				    address.numberInStreet();
			} else if (!address.numberInStreet().isEmpty()) {
				street += QChar(' ') + address.numberInStreet();
			} else if (!address.numberInMunicipality().isEmpty()) {
				street += QChar(' ') + address.numberInMunicipality();
			}
		} else {
			if (!address.numberInMunicipality().isEmpty()) {
				street = address.numberInMunicipality();
			} else if (!address.numberInStreet().isEmpty()) {
				street = address.numberInStreet();
			}
		}
		streetLine.setText(street);
	}

	cityLine.setText(address.street());

	zipCodeLine.setText(address.zipCode());

	selectState(preferCBox, stateComboBox, stateLine, address.state());
}

void DlgUserExt2::toggleCaAddressFirm(bool checked)
{
	if (checked) {
		loadCaAddress(m_owner.address(), *m_ui->caStreetLine,
		    *m_ui->caCityLine, *m_ui->caZipCodeLine,
		    m_preferCaStateCBox, *m_ui->caStateComboBox,
		    *m_ui->caStateLine);

		m_ui->caStreetLine->setEnabled(false);
		m_ui->caCityLine->setEnabled(false);
		m_ui->caZipCodeLine->setEnabled(false);
		m_ui->caStateComboBox->setEnabled(false);
		m_ui->caStateLine->setEnabled(false);
	}
}

void DlgUserExt2::toggleCaAddressCustom(bool checked)
{
	if (checked) {
		m_ui->caStreetLine->setEnabled(true);
		m_ui->caCityLine->setEnabled(true);
		m_ui->caZipCodeLine->setEnabled(true);
		m_ui->caStateComboBox->setEnabled(true);
		m_ui->caStateLine->setEnabled(true);
	}
}

void DlgUserExt2::enableContent(EditableFields fields)
{
	if (!(fields & EC_NAME_BIRTH)) {
		m_ui->userGroupBox->setEnabled(false);
	}

	if (!(fields & EC_ADDR)) {
		m_ui->addressGroupBox->setEnabled(false);
	}

	if (!(fields & EC_CA_ADDR)) {
		m_ui->caAddressGroupBox->setEnabled(false);
	}

	if (!(fields & (EC_USER_TYPE | EC_PERMISSIONS))) {
		/* Disable whole permission-related area. */
		m_ui->permissionsGroupBox->setEnabled(false);
	} else if (!(fields & EC_USER_TYPE)) {
		m_ui->userTypeComboBox->setEnabled(false);
	} else if (!(fields & EC_PERMISSIONS)) {
		m_ui->readNonPersonalCheckBox->setEnabled(false);
		m_ui->viewInfoCheckBox->setEnabled(false);
		m_ui->readAllCheckBox->setEnabled(false);
		m_ui->searchDbCheckBox->setEnabled(false);
		m_ui->createDmCheckBox->setEnabled(false);
		m_ui->erasevaultCheckBox->setEnabled(false);
	}
}

/*!
 * @brief Select user type in combo box.
 *
 * @param[in,out] cBox User type combo box.
 * @param[in] userType User type to be selected.
 */
static
void selectUserType(QComboBox &cBox, enum Isds::Type::UserType userType)
{
	if (userType != Isds::Type::UT_NULL) {
		CBoxModel *model = qobject_cast<CBoxModel *>(cBox.model());
		if (Q_UNLIKELY(model == Q_NULLPTR)) {
			Q_ASSERT(0);
			return;
		}

		int row =model->findRow(userType);
		if (row >= 0) {
			cBox.setCurrentIndex(row);
		}
	} else {
		cBox.setCurrentIndex(0);
	}
}

void DlgUserExt2::loadContent(const Isds::DbUserInfoExt2 &user)
{
	/* aifoIsds -- Ignored for now. */
	/* isdsID -- Ignored. */
	/* ic, firmName -- Ignored. TODO -- Check whether needed. */

	{
		const Isds::PersonName2 &personName = user.personName();
		m_ui->namesLine->setText(personName.givenNames());
		m_ui->surnameLine->setText(personName.lastName());
	}

	{
		const QDate &biDate = user.biDate();
		if (biDate.isValid()) {
			m_ui->birthDateEdit->setDate(biDate);
		}
	}

	{
		const Isds::AddressExt2 &address = user.address();
		/* adCode -- Ignored for now. */
		m_ui->streetLine->setText(address.street());
		m_ui->noInStreetLine->setText(address.numberInStreet());
		m_ui->districtLine->setText(address.district());
		{
			const QString &number = address.numberInMunicipality();
			if (!isEvidenceNumber(number)) {
				m_ui->conscriptNoLine->setText(number);
			} else {
				m_ui->evidenceNoLine->setText(
				    stripEvidenceNumber(number));
			}
		}
		m_ui->cityLine->setText(address.city());
		m_ui->zipCodeLine->setText(address.zipCode());
		selectState(m_preferStateCBox, *m_ui->stateComboBox,
		    *m_ui->stateLine, address.state());
	}

	{
		const QString &caStreet = user.caStreet();
		const QString &caCity = user.caCity();
		const QString &caZipCode = user.caZipCode();
		const QString &caState = user.caState();
		if ((!caStreet.isEmpty()) || (!caCity.isEmpty()) ||
		    (!caZipCode.isEmpty()) || (!caState.isEmpty())) {
			m_ui->useCaRadioButton->setChecked(true);

			m_ui->caStreetLine->setText(caStreet);
			m_ui->caCityLine->setText(caCity);
			m_ui->caZipCodeLine->setText(caZipCode);
		}
		selectState(m_preferCaStateCBox, *m_ui->caStateComboBox,
		    *m_ui->caStateLine, caState);
	}

	selectUserType(*m_ui->userTypeComboBox, user.userType());

	{
		const Isds::Type::Privileges privils = user.userPrivils();
		m_ui->readNonPersonalCheckBox->setChecked(privils & Isds::Type::PRIVIL_READ_NON_PERSONAL);
		m_ui->viewInfoCheckBox->setChecked(privils & Isds::Type::PRIVIL_VIEW_INFO);
		m_ui->readAllCheckBox->setChecked(privils & Isds::Type::PRIVIL_READ_ALL);
		m_ui->searchDbCheckBox->setChecked(privils & Isds::Type::PRIVIL_SEARCH_DB);
		m_ui->createDmCheckBox->setChecked(privils & Isds::Type::PRIVIL_CREATE_DM);
		m_ui->erasevaultCheckBox->setChecked(privils & Isds::Type::PRIVIL_ERASE_VAULT);
		/* PRIVIL_OWNER_ADM -- Automatically for administrators. */
	}
}

/*!
 * @brief Get country code from combo box or state from line edit.
 *
 * @param[in] preferCBox Whether to prefer the combo box over the line edit.
 * @param[in] cBox State combo box
 * @param[in] line Line edit.
 * @return String containing a country code or state name. Empty string on error.
 */
static
QString collectState(bool preferCBox, const QComboBox &cBox,
    const QLineEdit &line)
{
	if (preferCBox) {
		int pos = cBox.currentData(CBoxModel::ROLE_VALUE).toInt();
		if (Q_UNLIKELY(pos < 0)) {
			Q_ASSERT(0);
			return QString();
		}
		return Localisation::countryCodeAt(Localisation::countryList,
		    pos);
	} else {
		return line.text();
	}
}

/*!
 * @brief Get country code from combo box or state from line edit.
 *
 * @param[in] cBox State combo box
 * @return User type.
 */
static
enum Isds::Type::UserType collectUserType(const QComboBox &cBox)
{
	int type = cBox.currentData(CBoxModel::ROLE_VALUE).toInt();
	return (enum Isds::Type::UserType)type;
}

void DlgUserExt2::collectContent(Isds::DbUserInfoExt2 &user) const
{
	/* aifoIsds -- Ignored for now. */
	/* isdsID -- Ignored. */
	/* ic, firmName -- Ignored. TODO -- Check whether needed. */

	{
		Isds::PersonName2 personName;
		QString names = m_ui->namesLine->text();
		if (!names.isEmpty()) {
			personName.setGivenNames(macroStdMove(names));
		}
		QString surname = m_ui->surnameLine->text();
		if (!surname.isEmpty()) {
			personName.setLastName(macroStdMove(surname));
		}
		user.setPersonName(macroStdMove(personName));
	}

	user.setBiDate(m_ui->birthDateEdit->date());

	{
		Isds::AddressExt2 address;
		QString code = user.address().code(); /* Take from user. */
		if (!code.isEmpty()) {
			address.setCode(macroStdMove(code));
		}
		QString city = m_ui->cityLine->text();
		if (!city.isEmpty()) {
			address.setCity(macroStdMove(city));
		}
		QString district = m_ui->districtLine->text();
		if (!district.isEmpty()) {
			address.setDistrict(macroStdMove(district));
		}
		QString street = m_ui->streetLine->text();
		if (!street.isEmpty()) {
			address.setStreet(macroStdMove(street));
		}
		QString numberInStreet = m_ui->noInStreetLine->text();
		if (!numberInStreet.isEmpty()) {
			address.setNumberInStreet(macroStdMove(numberInStreet));
		}
		QString conscriptionNo = m_ui->conscriptNoLine->text();
		QString evidenceNo = m_ui->evidenceNoLine->text();
		if (!conscriptionNo.isEmpty()) {
			address.setNumberInMunicipality(macroStdMove(conscriptionNo));
		} else if (!evidenceNo.isEmpty()) {
			/* Prepend letter 'e' before the number. */
			address.setNumberInMunicipality(QChar('e') + evidenceNo);
		}
		QString zipCode = m_ui->zipCodeLine->text();
		if (!zipCode.isEmpty()) {
			address.setZipCode(macroStdMove(zipCode));
		}
		QString state = collectState(m_preferStateCBox,
		    *m_ui->stateComboBox, *m_ui->stateLine);
		if (!state.isEmpty()) {
			/*
			 * State may be set by default.
			 * Don't set "CZ" when having null address.
			 */
			if ((!address.isNull()) || (state != "CZ")) {
				address.setState(macroStdMove(state));
			}
		}
		user.setAddress(macroStdMove(address));
	}

	{
		QString caStreet;
		QString caCity;
		QString caZipCode;
		QString caState;
		if (m_ui->useFirmRadioButton->isChecked() ||
		    m_ui->useCaRadioButton->isChecked()) {
			caStreet = m_ui->caStreetLine->text();
			caCity = m_ui->caCityLine->text();
			caZipCode = m_ui->caCityLine->text();
			caState = collectState(m_preferCaStateCBox,
			    *m_ui->caStateComboBox, *m_ui->caStateLine);
			/*
			 * State may be set by default.
			 * Don't set "CZ" when having no address.
			 */
			if (caStreet.isEmpty() && caCity.isEmpty() &&
			    caZipCode.isEmpty() && (caState == "CZ")) {
				caState = QString();
			}
		}
		user.setCaStreet(macroStdMove(caStreet));
		user.setCaCity(macroStdMove(caCity));
		user.setCaZipCode(macroStdMove(caZipCode));
		user.setCaState(macroStdMove(caState));
	}

	user.setUserType(collectUserType(*m_ui->userTypeComboBox));

	{
		Isds::Type::Privileges privils = Isds::Type::PRIVIL_NONE;
		if (m_ui->readNonPersonalCheckBox->isChecked()) {
			privils |= Isds::Type::PRIVIL_READ_NON_PERSONAL;
		}
		if (m_ui->viewInfoCheckBox->isChecked()) {
			privils |= Isds::Type::PRIVIL_VIEW_INFO;
		}
		if (m_ui->readAllCheckBox->isChecked()) {
			privils |= Isds::Type::PRIVIL_READ_ALL;
		}
		if (m_ui->searchDbCheckBox->isChecked()) {
			privils |= Isds::Type::PRIVIL_SEARCH_DB;
		}
		if (m_ui->createDmCheckBox->isChecked()) {
			privils |= Isds::Type::PRIVIL_CREATE_DM;
		}
		if (m_ui->erasevaultCheckBox->isChecked()) {
			privils |= Isds::Type::PRIVIL_ERASE_VAULT;
		}
		/* PRIVIL_OWNER_ADM -- Automatically for administrators. */
		user.setUserPrivils(privils);
	}
}
