/*
 * 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 "src/datovka_shared/compat_qt/misc.h" /* qsizetype */
#include "src/datovka_shared/identifiers/account_id.h"
#include "src/settings/accounts.h"

#include "src/datovka_shared/settings/account_container.cpp" /* Intentionally including a cpp file. */

void AccountsMap::load(const AccountsMap &other)
{
	if (Q_UNLIKELY(m_credType != other.m_credType)) {
		Q_ASSERT(0);
		return;
	}

	emit contentAboutToBeReset();

	AcntSettingsMap<AcntData>::clear();
	foreach (const AcntId &acntId, other.m_acntIds) {
		(*this)[acntId] = other[acntId];
	}
	m_acntIds = other.m_acntIds;

	emit contentReset();
}

void AccountsMap::loadFromSettings(const QString &confDir,
    const QSettings &settings)
{
	emit contentAboutToBeReset();

	m_acntIds.clear();
	AcntSettingsMap<AcntData>::loadFromSettings(confDir, settings);

	foreach(const QString &group,
	    (m_credType == CT_REGULAR) ? sortedCredentialGroups(settings) : sortedShadowGroups(settings)) {
		AcntId acntId(
		    settings.value(group + "/" + CredNames::userName, QString()).toString(),
		    settings.value(group + "/" + CredNames::testAcnt, QString()).toBool());

		m_acntIds.append(acntId);
	}

	emit contentReset();
}

void AccountsMap::saveToSettings(const QString &pinVal, const QString &confDir,
    QSettings &settings) const
{
	for (int idx = 0; idx < m_acntIds.size(); ++idx) {
		const AcntId &aId(m_acntIds.at(idx));
		const AcntData &aData((*this)[aId]);

		aData.saveToSettings(pinVal, confDir, settings,
		    (m_credType == CT_REGULAR) ? credentialGroupName(idx + 1) : shadowGroupName(idx + 1));
	}
}

const QList<AcntId> &AccountsMap::acntIds(void) const
{
	return m_acntIds;
}

AcntData AccountsMap::acntData(const AcntId &acntId) const
{
	if (Q_UNLIKELY(!m_acntIds.contains(acntId))) {
		return AcntData();
	}

	return (*this)[acntId];
}

int AccountsMap::addAccount(const AcntData &acntData)
{
	const AcntId acntId(acntData.userName(), acntData.isTestAccount());
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return -1;
	}

	if (Q_UNLIKELY(m_acntIds.contains(acntId))) {
		return -2;
	}

	int index = m_acntIds.size();

	emit accountAboutToBeAdded(acntId, index);
	(*this)[acntId] = acntData;
	m_acntIds.append(acntId);
	emit accountAdded(acntId, index);

	return 0;
}

void AccountsMap::updateAccount(const AcntData &acntData)
{
	if (Q_UNLIKELY(acntData.userName().isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	const AcntId acntId(acntData.userName(), acntData.isTestAccount());
	Q_ASSERT(acntId.isValid());

	if (Q_UNLIKELY(!m_acntIds.contains(acntId))) {
		return;
	}

	if ((*this)[acntId] != acntData) {
		(*this)[acntId] = acntData;
		emit accountDataChanged(acntId);
	}
}

void AccountsMap::updateUsernameAccount(const AcntId &oldId,
    const AcntData &newData)
{
	if (Q_UNLIKELY(newData.userName().isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	const AcntId newId(newData.userName(), newData.isTestAccount());
	Q_ASSERT(newId.isValid());

	if (Q_UNLIKELY(oldId.testing() != newId.testing())) {
		Q_ASSERT(0);
		return;
	}

	if (Q_UNLIKELY((!m_acntIds.contains(oldId)) || (m_acntIds.contains(newId)))) {
		return;
	}

	if ((*this)[oldId] != newData) {
		const qsizetype index = m_acntIds.indexOf(oldId);
		if (Q_UNLIKELY(index < 0)) {
			Q_ASSERT(0);
			return;
		}
		this->remove(oldId);
		(*this)[newId] = newData;
		m_acntIds[index] = newId;
		emit accountUsernameChanged(oldId, newId, index);
	}
}

void AccountsMap::removeAccount(const AcntId acntId)
{
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}

	const qsizetype index = m_acntIds.indexOf(acntId);
	if (index >= 0) {
		emit accountAboutToBeRemoved(acntId, index);
		m_acntIds.removeAt(index);
		this->remove(acntId);
		emit accountRemoved(acntId, index);
	}
}

bool AccountsMap::moveAccounts(int srcIdx, int srcCnt, int dstIdx)
{
	if (Q_UNLIKELY(
	        (srcIdx < 0) || (srcIdx >= m_acntIds.size()) ||
	        (srcCnt < 0) || ((srcIdx + srcCnt) > m_acntIds.size()) ||
	        (dstIdx < 0) || (dstIdx > m_acntIds.size()))) {
		/* Boundaries or location outside limits. */
		Q_ASSERT(0);
		return false;
	}

	if (Q_UNLIKELY(srcCnt == 0)) {
		return true;
	}

	if ((srcIdx <= dstIdx) && (dstIdx <= (srcIdx + srcCnt))) {
		/* Destination lies within source. */
		return true;
	}

	int newPosition; /* Position after removal. */
	if (dstIdx < srcIdx) {
		newPosition = dstIdx;
	} else if (dstIdx > (srcIdx + srcCnt)) {
		newPosition = dstIdx - srcCnt;
	} else {
		Q_ASSERT(0);
		return false;
	}

	emit accountsAboutToBeMoved(srcIdx, srcIdx + srcCnt - 1, dstIdx);

	QList<AcntId> movedIds;
	for (int row = srcIdx; row < (srcIdx + srcCnt); ++row) {
		movedIds.append(m_acntIds.takeAt(srcIdx));
	}

	for (int i = 0; i < srcCnt; ++i) {
		m_acntIds.insert(newPosition + i, movedIds.takeAt(0));
	}
	Q_ASSERT(movedIds.isEmpty());

	emit accountsMoved(srcIdx, srcIdx + srcCnt - 1, dstIdx);

	return false;
}

AcntId acntIdFromData(const AcntData &acntData)
{
	return AcntId(acntData.userName(), acntData.isTestAccount());
}

/* Instantiating template code for class AcntData. */

template AcntSettingsMap<AcntData>::AcntSettingsMap(enum CredentialType credType);

template void AcntSettingsMap<AcntData>::loadFromSettings(
    const QString &confDir, const QSettings &settings);

template void AcntSettingsMap<AcntData>::decryptAllPwds(const QString &pinVal);

template AcntId AcntSettingsMap<AcntData>::acntIdFromUsername(const QString &username) const;

template QStringList AcntSettingsMap<AcntData>::sortedCredentialGroups(
    const QSettings &settings);

template QString AcntSettingsMap<AcntData>::credentialGroupName(int num);

template void AcntSettingsMap<AcntData>::addCredentialsTorso(
    QSettings &settings, const QString &accountName, const AcntId &acntId);
