/*
 * Copyright (C) 2014-2022 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.
 */

#pragma once

#include <QAbstractItemModel>
#include <QList>
#include <QMap>
#include <QObject>
#include <QString>
#include <QVariant>

#include "src/datovka_shared/identifiers/account_id.h"
#include "src/models/account_unique_numid.h"

/* Login method descriptors. */
#define LIM_USERNAME "username"
#define LIM_CERT "certificate"
#define LIM_USER_CERT "user_certificate"
#define LIM_HOTP "hotp"
#define LIM_TOTP "totp"

class AccountsMap; /* Forward declaration. */
class QMimeData; /* Forward declaration. */

/*!
 * @brief Account hierarchy.
 */
class AccountModel: public QAbstractItemModel {
    Q_OBJECT

public:
	/*!
	 * @brief Additional roles.
	 */
	enum UserRoles {
		ROLE_PLAIN_DISPLAY = (Qt::UserRole + 1) /*!< Low level data. */
	};
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
	Q_ENUM(UserRoles)
#else /* < Qt-5.5 */
	Q_ENUMS(UserRoles)
#endif /* >= Qt-5.5 */

	/*
	 * nodeRoot (Invisible.)
	 * |
	 * +- nodeAccountTop (account X)
	 * |  |
	 * |  +- nodeRecentReceived
	 * |  +- nodeRecentSent
	 * |  +- nodeAll
	 * |     |
	 * |     +- nodeReceived
	 * |     |  |
	 * |     |  +- nodeReceivedYear (yyyy)
	 * |     |  +- nodeReceivedYear (zzzz)
	 * |     |  .
	 * |     |  .
	 * |     |  .
	 * |     |
	 * |     +- nodeSent
	 * |        |
	 * |        +- nodeSentYear (aaaa)
	 * |        +- nodeSentYear (bbbb)
	 * |        .
	 * |        .
	 * |        .
	 * |
	 * +- nodeAccountTop (account Y)
	 * |  |
	 * .  .
	 * .  .
	 * .  .
	 *
	 * The identifier number must fit into TYPE_BITS number of bits.
	 */
	enum NodeType {
		nodeUnknown = 0, /* Must start at 0. */
		nodeRoot,
		nodeAccountTop,
		nodeRecentReceived,
		nodeRecentSent,
		nodeAll,
		nodeReceived,
		nodeSent,
		nodeReceivedYear,
		nodeSentYear
	};

	/*!
	 * @brief Sorting of yearly nodes.
	 */
	enum Sorting {
		UNSORTED = 0,
		ASCENDING,
		DESCENDING
	};

	/*!
	 * @brief Unread message yearly counter.
	 */
	class YearCounter {
	public:
		YearCounter(void)
		    : dbOpened(false), unread(0)
		{ }

		YearCounter(bool o, unsigned u)
		    : dbOpened(o), unread(u)
		{ }

		bool dbOpened; /*!< True if underlying database has been opened. */
		unsigned unread; /*!< Number of unread messages. */
	};

	/*!
	 * @brief Constructor.
	 *
	 * @param[in] parent Pointer to parent object.
	 */
	explicit AccountModel(QObject *parent = Q_NULLPTR);

	/*!
	 * @brief Return index specified by supplied parameters.
	 *
	 * @param[in] row    Item row.
	 * @param[in] column Parent column.
	 * @param[in] parent Parent index.
	 * @return Index to desired element or invalid index on error.
	 */
	virtual
	QModelIndex index(int row, int column,
	    const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;

	/*!
	 * @brief Return parent index of the item with the given index.
	 *
	 * @param[in] index Child node index.
	 * @return Index of the parent node or invalid index on error.
	 */
	virtual
	QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;

	/*!
	 * @brief Return number of rows under the given parent.
	 *
	 * @param[in] parent Parent node index.
	 * @return Number of rows.
	 */
	virtual
	int rowCount(const QModelIndex &parent = QModelIndex()) const
	    Q_DECL_OVERRIDE;

	/*!
	 * @brief Return the number of columns for the children of given parent.
	 *
	 * @param[in] parent Parent node index.
	 * @return Number of columns.
	 */
	virtual
	int columnCount(const QModelIndex &parent = QModelIndex()) const
	    Q_DECL_OVERRIDE;

	/*!
	 * @brief Return data stored in given location under given role.
	 *
	 * @param[in] index Index specifying the item.
	 * @param[in] role  Data role.
	 * @return Data from model.
	 */
	virtual
	QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;

	/*!
	 * @brief Returns header data in given location under given role.
	 *
	 * @param[in] section     Header position.
	 * @param[in] orientation Header orientation.
	 * @param[in] role        Data role.
	 * @return Header data from model.
	 */
	virtual
	QVariant headerData(int section, Qt::Orientation orientation,
	    int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;

	/*!
	 * @brief Move rows.
	 *
	 * @param[in] sourceParent Source parent.
	 * @param[in] sourceRow Source row.
	 * @param[in] count Number of rows to be moved.
	 * @param[in] destinationParent Destination parent.
	 * @param[in] destinationChild Row to move data into.
	 * @return If move performed.
	 */
	virtual
	bool moveRows(const QModelIndex &sourceParent, int sourceRow,
	    int count, const QModelIndex &destinationParent,
	    int destinationChild) Q_DECL_OVERRIDE;

	/*!
	 * @brief Remove rows.
	 *
	 * @param[in] row Starting row.
	 * @param[in] count Number of rows to be removed.
	 * @param[in] parent Parent item the row is relative to.
	 * @return True if the rows were successfully removed.
	 */
	virtual
	bool removeRows(int row, int count,
	    const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE;

	/*!
	 * @brief Returns the drop actions supported by this model.
	 *
	 * @return Supported drop actions.
	 */
	virtual
	Qt::DropActions supportedDropActions(void) const Q_DECL_OVERRIDE;

	/*!
	 * @brief Returns item flags for given index.
	 *
	 * @param[in] index Index specifying the item.
	 * @return Item flags.
	 */
	virtual
	Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;

	/*!
	 * @brief Returns the list of allowed MIME types.
	 *
	 * @return List of MIME types.
	 */
	virtual
	QStringList mimeTypes(void) const Q_DECL_OVERRIDE;

	/*!
	 * @brief Returns object containing serialised attachment data.
	 *
	 * @param[in] indexes List of indexes.
	 * @return Pointer to newly allocated MIME data object, Q_NULLPTR on error.
	 */
	virtual
	QMimeData *mimeData(
	    const QModelIndexList &indexes) const Q_DECL_OVERRIDE;

	/*!
	 * @brief Returns whether the model accepts drops of given MIME data.
	 *
	 * @param[in] data Data to be dropped.
	 * @param[in] action Type of drop action.
	 * @param[in] row Target row.
	 * @param[in] column Target column.
	 * @param[in] parent Parent index.
	 * @return True if drop is accepted.
	 */
	virtual
	bool canDropMimeData(const QMimeData *data, Qt::DropAction action,
	    int row, int column,
	    const QModelIndex &parent) const Q_DECL_OVERRIDE;

	/*!
	 * @brief Handles data supplied by drop operation.
	 *
	 * @param[in] data Data to be dropped.
	 * @param[in] action Type of drop action.
	 * @param[in] row Target row.
	 * @param[in] column Target column.
	 * @param[in] parent Parent index.
	 * @return True if data are handled by the model.
	 */
	virtual
	bool dropMimeData(const QMimeData *data, Qt::DropAction action,
	    int row, int column,
	    const QModelIndex &parent) Q_DECL_OVERRIDE;

	/*!
	 * @brief Load content.
	 *
	 * @param[in] regularAccounts Regular accounts.
	 */
	void loadContent(AccountsMap *regularAccounts);

	/*!
	 * @brief Returns account identifier for given node.
	 *
	 * @param[in] index Data index.
	 * @return Account identifier which the node belongs to or a null
	 *     identifier on error.
	 */
	const AcntId &acntId(const QModelIndex &index) const;

	/*!
	 * @brief Returns top node related to user name.
	 *
	 * @param[in] acntId Account identifier.
	 * @return Top node index or invalid index if no such name found.
	 */
	QModelIndex topAcntIndex(const AcntId &acntId) const;

	/*!
	 * @brief Returns node type.
	 *
	 * @param[in] index Data index.
	 * @return Node type related to the supplied index.
	 */
	static
	enum NodeType nodeType(const QModelIndex &index);

	/*!
	 * @brief Checks whether node type refers to a received type.
	 *
	 * @param[in] index Data index.
	 * @return True if node type is received.
	 */
	static
	bool nodeTypeIsReceived(enum NodeType nodeType);

	/*!
	 * @brief Checks whether node type refers to a sent type.
	 *
	 * @param[in] index Data index.
	 * @return True if node type is received.
	 */
	static
	bool nodeTypeIsSent(enum NodeType nodeType);

	/*!
	 * @brief Checks whether node is of received type.
	 *
	 * @param[in] index Data index.
	 * @return True if node type is received.
	 */
	static
	bool nodeIsReceived(const QModelIndex &index);

	/*!
	 * @brief Checks whether node is of sent type.
	 *
	 * @param[in] index Data index.
	 * @return True if node type is received.
	 */
	static
	bool nodeIsSent(const QModelIndex &index);

	/*!
	 * @brief Set number of unread messages in recent model nodes.
	 *
	 * @param[in] acntId Account identifier.
	 * @param[in] nodeType May be nodeRecentReceived or nodeRecentSent.
	 * @param[in] unreadMsgs Number of unread messages.
	 * @return True on success.
	 */
	bool updateRecentUnread(const AcntId &acntId,
	    enum NodeType nodeType, unsigned unreadMsgs = 0);

	/*!
	 * @brief Update year nodes.
	 *
	 * @param[in] acntId Account identifier.
	 * @param[in] nodeType May be nodeReceivedYear or nodeSentYear.
	 * @param[in] yearlyUnreadList List of paired years and unread messages
	 *                             numbers.
	 * @param[in] sorting Sorting.
	 * @param[in] prohibitYearRemoval Set to true if year nodes should not be removed.
	 * @return True on success.
	 */
	bool updateYearNodes(const AcntId &acntId, enum NodeType nodeType,
	    const QList< QPair<QString, YearCounter> > &yearlyUnreadList,
	    enum Sorting sorting, bool prohibitYearRemoval = false);

	/*!
	 * @brief Update existing year node in account.
	 *
	 * @param[in] acntId Account identifier.
	 * @param[in] nodeType May be nodeReceivedYear or nodeSentYear.
	 * @param[in] year Year string.
	 * @param[in] yCounter Whether database is opened and number of unread messages.
	 * @return True on success.
	 */
	bool updateYear(const AcntId &acntId, enum NodeType nodeType,
	    const QString &year, const YearCounter &yCounter);

	/*!
	 * @brief Delete all year-related nodes in model.
	 */
	void removeAllYearNodes(void);

	/*!
	 * @brief Move related data by given number of of positions.
	 *
	 * @param[in] acntId Account identifier.
	 * @param[in] shunt Amount of positions the account should be moved.
	 *                  Negative values move towards begin positive to
	 *                  the end.
	 * @return True on success.
	 */
	bool changePosition(const AcntId &acntId, int shunt);

private slots:
	/*!
	 * @brief Notify before a regular account is going to be added.
	 */
	void regularAccountAboutToBeAdded(const AcntId &acntId, int row);

	/*!
	 * @brief Update the model after a regular account has been added.
	 */
	void regularAccountAdded(const AcntId &acntId, int row);

	/*!
	 * @brief Notify before a regular account is going to be removed.
	 */
	void regularAccountAboutToBeRemoved(const AcntId &acntId, int row);

	/*!
	 * @brief Update the model after a regular account has been removed.
	 */
	void regularAccountRemoved(const AcntId &acntId, int row);

	/*!
	 * @brief Notify before regular accounts are going to be moved.
	 */
	void regularAccountsAboutToBeMoved(int srcFrst, int srcLst, int dst);

	/*!
	 * @brief Update the model after regular accounts have been moved.
	 */
	void regularAccountsMoved(int srcFrst, int srcLst, int dst);

	/*!
	 * @brief This slot handles changes of username.
	 */
	void regularUsernameChanged(const AcntId &oldId, const AcntId &newId, int index);

	/*!
	 * @brief Update the model after regular account has been modified.
	 */
	void regularAcountChanged(const AcntId &acntId);

private:
	/*!
	 * @brief Delete year-related nodes in model for given account.
	 */
	void removeYearNodes(const QModelIndex &topIndex);

	/*!
	 * @brief Returns child node type for given row.
	 *
	 * @param[in] parentType Parent node type.
	 * @param[in] childRow   Child row.
	 * @return Child node type; unknown type on error.
	 */
	static
	enum NodeType childNodeType(enum NodeType parentType, int childRow);

	/*!
	 * @brief Returns parent node type for given child type.
	 *
	 * @note The row is set when it can be clearly determined from the node
	 *     type. If it cannot be determined then it is set to -1.
	 *
	 * @param[in]  childType Child node type.
	 * @param[out] parentRow Pointer to row value that should be set.
	 * @return Parent node type; unknown type on error.
	 */
	static
	enum NodeType parentNodeType(enum NodeType childType, int *parentRow);

	/*!
	 * @brief Returns the row of the top node related to the account.
	 *
	 * @param[in] acntId Account identifier.
	 * @return Top node row or -1 if no such name found.
	 */
	int topAcntRow(const AcntId &acntId) const;

	AccountsMap *m_regularAccounts; /*!< Pointer to account container. */

	AcntNumId m_numIdMapping; /*!< Provides int <-> AcntId mapping for this model. */

	/*!
	 * @brief Holds additional information about the displayed data.
	 */
	class AccountCounters {
	public:
		/*!
		 * @brief Constructor.
		 */
		AccountCounters(void)
		    : unreadRecentReceived(0), unreadRecentSent(0),
		    receivedGroups(), sentGroups(),
		    unreadReceivedGroups(), unreadSentGroups()
		{
		}

		unsigned unreadRecentReceived; /*!< Number of unread recent received messages. */
		unsigned unreadRecentSent; /*!< Number of unread recent sent messages. */
		QList<QString> receivedGroups; /*!< Groups of unread messages. */
		QList<QString> sentGroups; /*!< Groups of unread messages. */
		QMap<QString, YearCounter> unreadReceivedGroups; /*< Number of unread messages. */
		QMap<QString, YearCounter> unreadSentGroups; /*!< Number of unread messages. */
	};

	QMap<AcntId, AccountCounters> m_countersMap; /*!< Unread counters. */
};
