/*
 * 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 <QColor>
#include <QFont>

#include "src/models/accounts_settings_model.h"
#include "src/settings/accounts.h"

/*
 * For index navigation QModellIndex::internalId() is used. The value encodes
 * the index of the top account node (which is also the index into the array
 * of user names) and the actual node type.
 *
 * The 4 least significant bits hold the node type.
 */
#define TYPE_BITS 4
#define TYPE_MASK 0x0f

/*!
 * @brief Generates model index internal identifier.
 *
 * @param[in] numId Numeric equivalent of the account identifier.
 * @param[in] nodeType Node type.
 * @return Internal identifier.
 */
#define internalIdCreate(numId, nodeType) \
	((((quintptr) (numId)) << TYPE_BITS) | (nodeType))

/*!
 * @brief Change the node type in internal identifier.
 *
 * @param[in] intId Internal identifier.
 * @param[in] nodeType New type to be set.
 * @return Internal identifier with new type.
 */
#define internalIdChangeType(intId, nodeType) \
	(((intId) & ~((quintptr) TYPE_MASK)) | (nodeType))

/*!
 * @brief Obtain node type from the internal identifier.
 *
 * @param[in] intId Internal identifier.
 * @return Node type.
 */
#define internalIdNodeType(intId) \
	((enum NodeType) ((intId) & TYPE_MASK))

/*!
 * @brief Obtain numeric account identifier from internal identifier.
 *
 * @param[in] indId Internal identifier.
 * @return Numeric equivalent of the account identifier.
 */
#define internalIdNumId(intId) \
	(((unsigned) (intId)) >> TYPE_BITS)

/* Null objects - for convenience. */
static const AcntId nullAcntId;

AccountSettingsModel::AccountSettingsModel(QObject *parent)
    : QAbstractItemModel(parent),
    m_regularAccounts(Q_NULLPTR),
    m_regNumIdMap(),
    m_shadowAccounts(Q_NULLPTR),
    m_shNumIdMap()
{
}

QModelIndex AccountSettingsModel::index(int row, int column,
    const QModelIndex &parent) const
{
	if (!hasIndex(row, column, parent)) {
		return QModelIndex();
	}

	enum NodeType type = nodeUnknown;

	/* Parent type. */
	if (!parent.isValid()) {
		type = nodeRoot;
	} else {
		type = nodeType(parent);
	}

	type = childNodeType(type, row); /* Child type. */

	if (nodeUnknown == type) {
		return QModelIndex();
	}

	quintptr internalId = 0;
	switch (type) {
	case nodeRegularAccountTop:
		{
			Q_ASSERT(m_regularAccounts != Q_NULLPTR);
			Q_ASSERT((row >= 0) && (row < m_regularAccounts->acntIds().size()));
			Q_ASSERT(m_regularAccounts->acntIds().at(row).isValid());
			const int numId = m_regNumIdMap.numId(m_regularAccounts->acntIds()[row]);
			Q_ASSERT(numId >= 0);
			Q_ASSERT(m_regNumIdMap.acntId(numId).isValid());
			internalId = internalIdCreate(numId, type);
		}
		break;
	case nodeShadowAccountTop:
		{
			Q_ASSERT(m_shadowAccounts != Q_NULLPTR);
			Q_ASSERT((row >= 0) && (row < m_shadowAccounts->acntIds().size()));
			Q_ASSERT(m_shadowAccounts->acntIds().at(row).isValid());
			const int numId = m_shNumIdMap.numId(m_shadowAccounts->acntIds()[row]);
			Q_ASSERT(numId >= 0);
			Q_ASSERT(m_shNumIdMap.acntId(numId).isValid());
			internalId = internalIdCreate(numId, type);
		}
		break;
	case nodeRegularAccountInfo:
	case nodeRegularAccountUsers:
	case nodeShadowAccountInfo:
		/* Preserve top node row from parent, change type. */
		internalId = internalIdChangeType(parent.internalId(), type);
		break;
	default:
		/* Just store type. */
		internalId = internalIdCreate(0, type);
		break;
	}

	return createIndex(row, column, internalId);
}

QModelIndex AccountSettingsModel::parent(const QModelIndex &index) const
{
	if (Q_UNLIKELY(!index.isValid())) {
		return QModelIndex();
	}

	quintptr internalId = index.internalId();

	/* Child type. */
	enum NodeType type = internalIdNodeType(internalId);

	int parentRow = -1;
	type = parentNodeType(type, &parentRow); /* Parent type. */

	if ((nodeUnknown == type) || (nodeRoot == type)) {
		return QModelIndex();
	}

	if (parentRow < 0) {
		Q_ASSERT((nodeRegularAccountTop == type) || (nodeShadowAccountTop == type));
		/* Determine the row of the account top node. */
		const int numId = internalIdNumId(internalId);
		switch (type) {
		case nodeRegularAccountTop:
			{
				const AcntId acntId = m_regNumIdMap.acntId(numId);
				int row = m_regularAccounts->acntIds().indexOf(acntId);
				if (row >= 0) {
					parentRow = numId;
				}
			}
			break;
		case nodeShadowAccountTop:
			{
				const AcntId acntId = m_shNumIdMap.acntId(numId);
				int row = m_shadowAccounts->acntIds().indexOf(acntId);
				if (row >= 0) {
					parentRow = numId;
				}
			}
			break;
		default:
			Q_ASSERT(0);
			break;
		}
	}

	Q_ASSERT(parentRow >= 0);

	return createIndex(parentRow, 0,
	    internalIdChangeType(internalId, type));
}

int AccountSettingsModel::rowCount(const QModelIndex &parent) const
{
	if (Q_UNLIKELY(parent.column() > 0)) {
		return 0;
	}

	if (!parent.isValid()) {
		/* Root. */
		return 2;
	}

	int rows = 0;

	switch (nodeType(parent)) {
	case nodeRegular:
		if (m_regularAccounts != Q_NULLPTR) {
			rows = m_regularAccounts->acntIds().size();
		}
		break;
	case nodeShadow:
		if (m_shadowAccounts != Q_NULLPTR) {
			rows = m_shadowAccounts->acntIds().size();
		}
		break;
	case nodeRegularAccountTop:
		rows = 2;
		break;
	case nodeShadowAccountTop:
		rows = 1;
		break;
	case nodeRegularAccountInfo:
	case nodeRegularAccountUsers:
	case nodeShadowAccountInfo:
		rows = 0;
		break;
	default:
		rows = 0;
		break;
	}

	return rows;
}

int AccountSettingsModel::columnCount(const QModelIndex &parent) const
{
	Q_UNUSED(parent);

	return 1;
}

/*!
 * @brief Return account name.
 *
 * @param[in] row Row number.
 * @param[in] accounts Account container.
 * @return Account name.
 */
static
QString accountName(int row, const AccountsMap *accounts)
{
	if (Q_UNLIKELY(accounts == Q_NULLPTR)) {
		Q_ASSERT(0);
		return QString();
	}

	if (Q_UNLIKELY(row < 0) || (row >= accounts->acntIds().size())) {
		Q_ASSERT(0);
		return QString();
	}

	const AcntId &aId(accounts->acntIds().at(row));
	Q_ASSERT(aId.isValid());
	const AcntData &aData(accounts->acntData(aId));
	return aData.accountName();
}

QVariant AccountSettingsModel::data(const QModelIndex &index, int role) const
{
	if (Q_UNLIKELY(!index.isValid())) {
		return QVariant();
	}

	const enum NodeType type = internalIdNodeType(index.internalId());

	switch (role) {
	case Qt::DisplayRole:
		switch (type) {
		case nodeRegular:
			return tr("Regular Data Boxes");
			break;
		case nodeShadow:
			return tr("Shadow Data Boxes");
			break;
		case nodeRegularAccountTop:
			return accountName(index.row(), m_regularAccounts);
			break;
		case nodeShadowAccountTop:
			return accountName(index.row(), m_shadowAccounts);
			break;
		case nodeRegularAccountUsers:
			return tr("Data-Box Users");
			break;
		case nodeRegularAccountInfo:
		case nodeShadowAccountInfo:
			return tr("Data-Box Information");
			break;
		default:
			Q_ASSERT(0);
			return QVariant();
			break;
		}
		break;

	case Qt::FontRole:
		switch (type) {
		case nodeRegular:
		case nodeShadow:
			{
				QFont retFont;
				retFont.setBold(true);
				return retFont;
			}
			break;
		default:
			return QVariant();
			break;
		}
		break;

	case Qt::ForegroundRole:
		switch (type) {
		case nodeRegular:
		case nodeRegularAccountTop:
		case nodeRegularAccountInfo:
		case nodeRegularAccountUsers:
			return QColor(Qt::darkGray);
			break;
		default:
			return QVariant();
			break;
		}
		break;

	case Qt::AccessibleTextRole:
		switch (type) {
		case nodeRegular:
			return tr("Available regular data boxes");
			break;
		case nodeShadow:
			return tr("Available shadow data boxes");
			break;
		case nodeRegularAccountTop:
			return tr("Data box") + " " +
			    accountName(index.row(), m_regularAccounts);
			break;
		case nodeShadowAccountTop:
			return tr("Shadow data box") + " " +
			    accountName(index.row(), m_shadowAccounts);
			break;
		case nodeRegularAccountInfo:
			return tr("Information about data box") + " " +
			    accountName(index.parent().row(), m_regularAccounts);
			break;
		case nodeRegularAccountUsers:
			return tr("Users with access to data box"); // TODO
			break;
		case nodeShadowAccountInfo:
			return tr("Information about data box") + " " +
			    accountName(index.parent().row(), m_shadowAccounts);
			break;
		default:
			return QVariant();
			break;
		}

	default:
		return QVariant();
		break;
	}
}

QVariant AccountSettingsModel::headerData(int section,
    Qt::Orientation orientation, int role) const
{
	if ((Qt::Horizontal == orientation) && (Qt::DisplayRole == role) &&
	    (0 == section)) {
		return tr("Data Boxes");
	}

	return QVariant();
}

void AccountSettingsModel::loadContent(AccountsMap *regularAccounts,
    AccountsMap *shadowAccounts)
{
	loadListContent(regularAccounts, false);
	loadListContent(shadowAccounts, true);
}

const AcntId &AccountSettingsModel::acntId(const QModelIndex &index) const
{
	if (Q_UNLIKELY(!index.isValid())) {
		return nullAcntId;
	}

	enum NodeType type = nodeType(index);
	switch (type) {
	case nodeUnknown:
	case nodeRoot:
	case nodeRegular:
	case nodeShadow:
		return nullAcntId;
		break;
	default:
		break;
	}

	int numId = internalIdNumId(index.internalId());

	switch (type) {
	case nodeRegularAccountTop:
	case nodeRegularAccountInfo:
	case nodeRegularAccountUsers:
		if ((m_regularAccounts != Q_NULLPTR) &&
		    (numId < m_regularAccounts->acntIds().size())) {
			return m_regNumIdMap.acntId(numId);
		}
		break;
	case nodeShadowAccountTop:
	case nodeShadowAccountInfo:
		if ((m_shadowAccounts != Q_NULLPTR) &&
		    (numId < m_shadowAccounts->acntIds().size())) {
			return m_shNumIdMap.acntId(numId);
		}
		break;
	default:
		break;
	}

	return nullAcntId;
}

QModelIndex AccountSettingsModel::indexShadowAccountTop(const AcntId &acntId) const
{
	if (Q_UNLIKELY(m_shadowAccounts == Q_NULLPTR)) {
		return QModelIndex();
	}

	int row = m_shadowAccounts->acntIds().indexOf(acntId);
	if (Q_UNLIKELY(row < 0)) {
		return QModelIndex();
	}

	return index(row, 0, index(ROW_SHADOW, 0));
}

enum AccountSettingsModel::NodeType AccountSettingsModel::nodeType(
    const QModelIndex &index)
{
	if (!index.isValid()) {
		return nodeUnknown; /* nodeRoot ? */
	}

	/* TODO -- Add runtime value check? */
	return internalIdNodeType(index.internalId());
}

void AccountSettingsModel::regularAccountAboutToBeAdded(const AcntId &acntId,
    int row)
{
	beginInsertRows(index(ROW_REGULAR, 0), row, row);

	m_regNumIdMap.addAcntId(acntId);
}

void AccountSettingsModel::shadowAccountAboutToBeAdded(const AcntId &acntId,
    int row)
{
	beginInsertRows(index(ROW_SHADOW, 0), row, row);

	m_shNumIdMap.addAcntId(acntId);
}

void AccountSettingsModel::regularAccountAdded(const AcntId &acntId, int row)
{
	Q_UNUSED(acntId);
	Q_UNUSED(row);

	endInsertRows();
}

void AccountSettingsModel::shadowAccountAdded(const AcntId &acntId, int row)
{
	Q_UNUSED(acntId);
	Q_UNUSED(row);

	endInsertRows();
}

void AccountSettingsModel::regularAccountAboutToBeRemoved(const AcntId &acntId,
    int row)
{
	beginRemoveRows(index(ROW_REGULAR, 0), row, row);

	m_regNumIdMap.removeAcntId(acntId);
}

void AccountSettingsModel::shadowAccountAboutToBeRemoved(const AcntId &acntId,
    int row)
{
	beginRemoveRows(index(ROW_SHADOW, 0), row, row);

	m_shNumIdMap.removeAcntId(acntId);
}

void AccountSettingsModel::regularAccountRemoved(const AcntId &acntId, int row)
{
	Q_UNUSED(acntId);
	Q_UNUSED(row);

	endRemoveRows();
}

void AccountSettingsModel::shadowAccountRemoved(const AcntId &acntId, int row)
{
	Q_UNUSED(acntId);
	Q_UNUSED(row);

	endRemoveRows();
}

void AccountSettingsModel::regularAccountsAboutToBeMoved(int srcFrst, int srcLst,
    int dst)
{
	QModelIndex parent(index(ROW_REGULAR, 0));

	beginMoveRows(parent, srcFrst, srcLst, parent, dst);
}

void AccountSettingsModel::shadowAccountsAboutToBeMoved(int srcFrst, int srcLst,
    int dst)
{
	QModelIndex parent(index(ROW_SHADOW, 0));

	beginMoveRows(parent, srcFrst, srcLst, parent, dst);
}

void AccountSettingsModel::regularAccountsMoved(int srcFrst, int srcLst, int dst)
{
	Q_UNUSED(srcFrst);
	Q_UNUSED(srcLst);
	Q_UNUSED(dst);

	endMoveRows();
}

void AccountSettingsModel::shadowAccountsMoved(int srcFrst, int srcLst, int dst)
{
	Q_UNUSED(srcFrst);
	Q_UNUSED(srcLst);
	Q_UNUSED(dst);

	endMoveRows();
}

void AccountSettingsModel::regularUsernameChanged(const AcntId &oldId,
    const AcntId &newId, int index)
{
	m_regNumIdMap.replaceAcntId(oldId, newId);

	if (Q_UNLIKELY(index < 0)) {
		Q_ASSERT(0);
		return;
	}

	QModelIndex idx(AccountSettingsModel::index(index, 0,
	    AccountSettingsModel::index(ROW_REGULAR, 0)));
	if (idx.isValid()) {
		emit dataChanged(idx, idx);
	}
}

void AccountSettingsModel::shadowUsernameChanged(const AcntId &oldId,
    const AcntId &newId, int index)
{
	m_shNumIdMap.replaceAcntId(oldId, newId);

	if (Q_UNLIKELY(index < 0)) {
		Q_ASSERT(0);
		return;
	}

	QModelIndex idx(AccountSettingsModel::index(index, 0,
	    AccountSettingsModel::index(ROW_SHADOW, 0)));
	if (idx.isValid()) {
		emit dataChanged(idx, idx);
	}
}

void AccountSettingsModel::regularAcountChanged(const AcntId &acntId)
{
	if (Q_UNLIKELY(m_regularAccounts == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	int position = m_regularAccounts->acntIds().indexOf(acntId);
	if (Q_UNLIKELY(position < 0)) {
		Q_ASSERT(0);
		return;
	}

	QModelIndex idx(index(position, 0, index(ROW_REGULAR, 0)));
	if (idx.isValid()) {
		emit dataChanged(idx, idx);
	}
}

void AccountSettingsModel::shadowAcountChanged(const AcntId &acntId)
{
	if (Q_UNLIKELY(m_shadowAccounts == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	int position = m_shadowAccounts->acntIds().indexOf(acntId);
	if (Q_UNLIKELY(position < 0)) {
		Q_ASSERT(0);
		return;
	}

	QModelIndex idx(index(position, 0, index(ROW_SHADOW, 0)));
	if (idx.isValid()) {
		emit dataChanged(idx, idx);
	}
}

void AccountSettingsModel::contentAboutToBeReset(void)
{
	beginResetModel();

	m_regNumIdMap.clear();
	m_shNumIdMap.clear();
}

void AccountSettingsModel::contentReset(void)
{
	if (m_regularAccounts != Q_NULLPTR) {
		foreach (const AcntId &acntId, m_regularAccounts->acntIds()) {
			m_regNumIdMap.addAcntId(acntId);
		}
	}
	if (m_shadowAccounts != Q_NULLPTR) {
		foreach (const AcntId &acntId, m_shadowAccounts->acntIds()) {
			m_shNumIdMap.addAcntId(acntId);
		}
	}

	endResetModel();
}

void AccountSettingsModel::loadListContent(AccountsMap *accounts, bool shadow)
{
	const QModelIndex parent = index((!shadow) ? ROW_REGULAR : ROW_SHADOW, 0);

	if (!shadow) {
		if (m_regularAccounts != Q_NULLPTR) {
			m_regularAccounts->disconnect(SIGNAL(accountAboutToBeAdded(AcntId, int)),
			    this, SLOT(regularAccountAboutToBeAdded(AcntId, int)));
			m_regularAccounts->disconnect(SIGNAL(accountAdded(AcntId, int)),
			    this, SLOT(regularAccountAdded(AcntId, int)));

			m_regularAccounts->disconnect(SIGNAL(accountAboutToBeRemoved(AcntId, int)),
			    this, SLOT(regularAccountAboutToBeRemoved(AcntId, int)));
			m_regularAccounts->disconnect(SIGNAL(accountRemoved(AcntId, int)),
			    this, SLOT(regularAccountRemoved(AcntId, int)));

			m_regularAccounts->disconnect(SIGNAL(accountsAboutToBeMoved(int, int, int)),
			    this, SLOT(regularAccountsAboutToBeMoved(int, int, int)));
			m_regularAccounts->disconnect(SIGNAL(accountsMoved(int, int, int)),
			    this, SLOT(regularAccountsMoved(int, int, int)));

			m_regularAccounts->disconnect(SIGNAL(accountUsernameChanged(AcntId, AcntId, int)),
			    this, SLOT(regularUsernameChanged(AcntId, AcntId, int)));
			m_regularAccounts->disconnect(SIGNAL(accountDataChanged(AcntId)),
			    this, SLOT(regularAcountChanged(AcntId)));

			m_regularAccounts->disconnect(SIGNAL(contentAboutToBeReset()),
			    this, SLOT(contentAboutToBeReset()));
			m_regularAccounts->disconnect(SIGNAL(contentReset()),
			    this, SLOT(contentReset()));
		}
	} else {
		if (m_shadowAccounts != Q_NULLPTR) {
			m_shadowAccounts->disconnect(SIGNAL(accountAboutToBeAdded(AcntId, int)),
			    this, SLOT(shadowAccountAboutToBeAdded(AcntId, int)));
			m_shadowAccounts->disconnect(SIGNAL(accountAdded(AcntId, int)),
			    this, SLOT(shadowAccountAdded(AcntId, int)));

			m_shadowAccounts->disconnect(SIGNAL(accountAboutToBeRemoved(AcntId, int)),
			    this, SLOT(shadowAccountAboutToBeRemoved(AcntId, int)));
			m_shadowAccounts->disconnect(SIGNAL(accountRemoved(AcntId, int)),
			    this, SLOT(shadowAccountRemoved(AcntId, int)));

			m_shadowAccounts->disconnect(SIGNAL(accountsAboutToBeMoved(int, int, int)),
			    this, SLOT(shadowAccountsAboutToBeMoved(int, int, int)));
			m_shadowAccounts->disconnect(SIGNAL(accountsMoved(int, int, int)),
			    this, SLOT(shadowAccountsMoved(int, int, int)));

			m_shadowAccounts->disconnect(SIGNAL(accountUsernameChanged(AcntId, AcntId, int)),
			    this, SLOT(shadowUsernameChanged(AcntId, AcntId, int)));
			m_shadowAccounts->disconnect(SIGNAL(accountDataChanged(AcntId)),
			    this, SLOT(shadowAcountChanged(AcntId)));

			m_shadowAccounts->disconnect(SIGNAL(contentAboutToBeReset()),
			    this, SLOT(contentAboutToBeReset()));
			m_shadowAccounts->disconnect(SIGNAL(contentReset()),
			    this, SLOT(contentReset()));
		}
	}

	int rows = rowCount(parent);
	if (rows > 0) {
		beginRemoveRows(parent, 0, rows - 1);
		if (!shadow) {
			m_regularAccounts = Q_NULLPTR;
			m_regNumIdMap.clear();
		} else {
			m_shadowAccounts = Q_NULLPTR;
			m_shNumIdMap.clear();
		}
		endRemoveRows();
	}

	if (accounts == Q_NULLPTR) {
		return;
	}

	if (accounts->keys().isEmpty()) {
		if (!shadow) {
			m_regularAccounts = accounts;
		} else {
			m_shadowAccounts = accounts;
		}
	} else {
		rows = accounts->acntIds().size();
		beginInsertRows(parent, 0, rows - 1);
		if (!shadow) {
			m_regularAccounts = accounts;
			foreach (const AcntId &acntId, m_regularAccounts->acntIds()) {
				m_regNumIdMap.addAcntId(acntId);
			}
		} else {
			m_shadowAccounts = accounts;
			foreach (const AcntId &acntId, m_shadowAccounts->acntIds()) {
				m_shNumIdMap.addAcntId(acntId);
			}
		}
		endInsertRows();
	}

	if (!shadow) {
		connect(m_regularAccounts, SIGNAL(accountAboutToBeAdded(AcntId, int)),
		    this, SLOT(regularAccountAboutToBeAdded(AcntId, int)));
		connect(m_regularAccounts, SIGNAL(accountAdded(AcntId, int)),
		    this, SLOT(regularAccountAdded(AcntId, int)));

		connect(m_regularAccounts, SIGNAL(accountAboutToBeRemoved(AcntId, int)),
		    this, SLOT(regularAccountAboutToBeRemoved(AcntId, int)));
		connect(m_regularAccounts, SIGNAL(accountRemoved(AcntId, int)),
		    this, SLOT(regularAccountRemoved(AcntId, int)));

		connect(m_regularAccounts, SIGNAL(accountsAboutToBeMoved(int, int, int)),
		    this, SLOT(regularAccountsAboutToBeMoved(int, int, int)));
		connect(m_regularAccounts,SIGNAL(accountsMoved(int, int, int)),
		    this, SLOT(regularAccountsMoved(int, int, int)));

		connect(m_regularAccounts,SIGNAL(accountUsernameChanged(AcntId, AcntId, int)),
		    this, SLOT(regularUsernameChanged(AcntId, AcntId, int)));
		connect(m_regularAccounts, SIGNAL(accountDataChanged(AcntId)),
		    this, SLOT(regularAcountChanged(AcntId)));

		connect(m_regularAccounts, SIGNAL(contentAboutToBeReset()),
		    this, SLOT(contentAboutToBeReset()));
		connect(m_regularAccounts, SIGNAL(contentReset()),
		    this, SLOT(contentReset()));
	} else {
		connect(m_shadowAccounts, SIGNAL(accountAboutToBeAdded(AcntId, int)),
		    this, SLOT(shadowAccountAboutToBeAdded(AcntId, int)));
		connect(m_shadowAccounts, SIGNAL(accountAdded(AcntId, int)),
		    this, SLOT(shadowAccountAdded(AcntId, int)));

		connect(m_shadowAccounts, SIGNAL(accountAboutToBeRemoved(AcntId, int)),
		    this, SLOT(shadowAccountAboutToBeRemoved(AcntId, int)));
		connect(m_shadowAccounts, SIGNAL(accountRemoved(AcntId, int)),
		    this, SLOT(shadowAccountRemoved(AcntId, int)));

		connect(m_shadowAccounts, SIGNAL(accountsAboutToBeMoved(int, int, int)),
		    this, SLOT(shadowAccountsAboutToBeMoved(int, int, int)));
		connect(m_shadowAccounts,SIGNAL(accountsMoved(int, int, int)),
		    this, SLOT(shadowAccountsMoved(int, int, int)));

		connect(m_shadowAccounts,SIGNAL(accountUsernameChanged(AcntId, AcntId, int)),
		    this, SLOT(shadowUsernameChanged(AcntId, AcntId, int)));
		connect(m_shadowAccounts, SIGNAL(accountDataChanged(AcntId)),
		    this, SLOT(shadowAcountChanged(AcntId)));

		connect(m_shadowAccounts, SIGNAL(contentAboutToBeReset()),
		    this, SLOT(contentAboutToBeReset()));
		connect(m_shadowAccounts, SIGNAL(contentReset()),
		    this, SLOT(contentReset()));
	}
}

enum AccountSettingsModel::NodeType AccountSettingsModel::childNodeType(
    enum NodeType parentType, int childRow)
{
	switch (parentType) {
	case nodeUnknown:
		return nodeUnknown;
		break;
	case nodeRoot:
		switch (childRow) {
		case ROW_REGULAR:
			return nodeRegular;
			break;
		case ROW_SHADOW:
			return nodeShadow;
			break;
		default:
			return nodeUnknown;
			break;
		}
	case nodeRegular:
		return nodeRegularAccountTop;
		break;
	case nodeShadow:
		return nodeShadowAccountTop;
		break;
	case nodeRegularAccountTop:
		switch (childRow) {
		case ROW_ACNT_INFO:
			return nodeRegularAccountInfo;
			break;
		case ROW_ACNT_USERS:
			return nodeRegularAccountUsers;
			break;
		default:
			return nodeUnknown;
			break;
		}
		break;
	case nodeShadowAccountTop:
		return nodeShadowAccountInfo;
		break;
	default:
		Q_ASSERT(0);
		return nodeUnknown;
		break;
	}
}

enum AccountSettingsModel::NodeType AccountSettingsModel::parentNodeType(
    enum NodeType childType, int *parentRow)
{
	switch (childType) {
	case nodeUnknown:
	case nodeRoot:
		if (Q_NULLPTR != parentRow) {
			*parentRow = -1;
		}
		return nodeUnknown;
		break;
	case nodeRegular:
	case nodeShadow:
		if (Q_NULLPTR != parentRow) {
			*parentRow = -1;
		}
		return nodeRoot;
		break;
	case nodeRegularAccountTop:
		if (Q_NULLPTR != parentRow) {
			*parentRow = ROW_REGULAR;
		}
		return nodeRegular;
		break;
	case nodeShadowAccountTop:
		if (Q_NULLPTR != parentRow) {
			*parentRow = ROW_SHADOW;
		}
		return nodeShadow;
		break;
	case nodeRegularAccountInfo:
		if (Q_NULLPTR != parentRow) {
			*parentRow = -1;
		}
		return nodeRegularAccountTop;
		break;
	case nodeRegularAccountUsers:
		if (Q_NULLPTR != parentRow) {
			*parentRow = -1;
		}
		return nodeRegularAccountTop;
		break;
	case nodeShadowAccountInfo:
		if (Q_NULLPTR != parentRow) {
			*parentRow = -1;
		}
		return nodeShadowAccountTop;
		break;
	default:
		Q_ASSERT(0);
		if (Q_NULLPTR != parentRow) {
			*parentRow = -1;
		}
		return nodeUnknown;
		break;
	}
}
