// SPDX-FileCopyrightText: 2009 Manjeet Dahiya <manjeetdahiya@gmail.com>
// SPDX-FileCopyrightText: 2019 Linus Jahn <lnj@kaidan.im>
// SPDX-FileCopyrightText: 2023 Melvin Keskin <melvo@olomono.de>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#ifndef QXMPPCLIENT_H
#define QXMPPCLIENT_H

#include "QXmppConfiguration.h"
#include "QXmppLogger.h"
#include "QXmppPresence.h"
#include "QXmppSendResult.h"
#include "QXmppSendStanzaParams.h"
#include "QXmppStreamError.h"

#include <memory>
#include <variant>

#include <QAbstractSocket>
#include <QObject>
#include <QSslError>

template<typename T>
class QXmppTask;

class QXmppE2eeExtension;
class QXmppClientExtension;
class QXmppClientPrivate;
class QXmppMessage;
class QXmppOutgoingClient;
class QXmppPresence;
class QXmppIq;

// managers
class QXmppDiscoveryIq;
class QXmppRosterManager;
class QXmppVCardManager;
class QXmppVersionManager;

namespace QXmpp::Private {
struct SessionBegin;
}

///
/// \defgroup Core Core classes
///
/// Core classes include all necessary classes to build a basic client or
/// server application. This for example also includes the logging class
/// QXmppLogger.
///

///
/// \defgroup Managers Managers
///
/// Managers are used to extend the basic QXmppClient. Some of them are loaded
/// by default, others need to be added to the client using
/// QXmppClient::addExtension().
///

class QXMPP_EXPORT QXmppClient : public QXmppLoggable
{
    Q_OBJECT

    /// The QXmppLogger associated with the current QXmppClient
    Q_PROPERTY(QXmppLogger *logger READ logger WRITE setLogger NOTIFY loggerChanged)
    /// The client's current state
    Q_PROPERTY(State state READ state NOTIFY stateChanged)

public:
    using IqResult = std::variant<QDomElement, QXmppError>;
    using EmptyResult = std::variant<QXmpp::Success, QXmppError>;

    /// An enumeration for type of error.
    /// Error could come due a TCP socket or XML stream or due to various stanzas.
    enum Error {
        NoError,          ///< No error.
        SocketError,      ///< Error due to TCP socket.
        KeepAliveError,   ///< Error due to no response to a keep alive.
        XmppStreamError,  ///< Error due to XML stream.
    };
    Q_ENUM(Error)

    /// This enumeration describes a client state.
    enum State {
        DisconnectedState,  ///< Disconnected from the server.
        ConnectingState,    ///< Trying to connect to the server.
        ConnectedState      ///< Connected to the server.
    };
    Q_ENUM(State)

    /// Describes the use of \xep{0198}: Stream Management
    enum StreamManagementState {
        /// Stream Management is not used.
        NoStreamManagement,
        /// Stream Management is used and the previous stream has not been resumed.
        NewStream,
        /// Stream Management is used and the previous stream has been resumed.
        ResumedStream
    };

    /// Used to decide which extensions should be added by default.
    /// \since QXmpp 1.6
    enum InitialExtensions {
        /// Creates a client without any extensions.
        NoExtensions,
        /// Creates a client with the default set of extensions.
        BasicExtensions,
    };

    QXmppClient(InitialExtensions, QObject *parent = nullptr);
    QXmppClient(QObject *parent = nullptr);
    ~QXmppClient() override;

    bool addExtension(QXmppClientExtension *extension);
    template<typename T, typename... Args>
    T *addNewExtension(Args... args)
    {
        // it's impossible that addExtension() returns false: ext is a new object
        auto *ext = new T(args...);
        addExtension(ext);
        return ext;
    }
    bool insertExtension(int index, QXmppClientExtension *extension);
    bool removeExtension(QXmppClientExtension *extension);
    QXmppE2eeExtension *encryptionExtension() const;
    void setEncryptionExtension(QXmppE2eeExtension *);

    QList<QXmppClientExtension *> extensions() const;

    ///
    /// \brief Returns the extension which can be cast into type T*, or 0
    /// if there is no such extension.
    ///
    /// Usage example:
    /// \code
    /// QXmppDiscoveryManager* ext = client->findExtension<QXmppDiscoveryManager>();
    /// if(ext)
    /// {
    ///     //extension found, do stuff...
    /// }
    /// \endcode
    ///
    template<typename T>
    T *findExtension() const
    {
        const QList<QXmppClientExtension *> list = extensions();
        for (auto ext : list) {
            T *extension = qobject_cast<T *>(ext);
            if (extension) {
                return extension;
            }
        }
        return nullptr;
    }

    ///
    /// \brief Returns the index of an extension
    ///
    /// Usage example:
    /// \code
    /// int index = client->indexOfExtension<QXmppDiscoveryManager>();
    /// if (index > 0) {
    ///     // extension found, do stuff...
    /// } else {
    ///     // extension not found
    /// }
    /// \endcode
    ///
    /// \since QXmpp 1.2
    ///
    template<typename T>
    int indexOfExtension() const
    {
        auto list = extensions();
        for (int i = 0; i < list.size(); ++i) {
            if (qobject_cast<T *>(list.at(i)) != nullptr) {
                return i;
            }
        }
        return -1;
    }

    bool isAuthenticated() const;
    bool isConnected() const;

    bool isActive() const;
    void setActive(bool active);

    StreamManagementState streamManagementState() const;

    QXmppPresence clientPresence() const;
    void setClientPresence(const QXmppPresence &presence);

    QXmppConfiguration &configuration();

    // documentation needs to be here, see https://stackoverflow.com/questions/49192523/
    /// Returns the QXmppLogger associated with the current QXmppClient.
    QXmppLogger *logger() const;
    void setLogger(QXmppLogger *logger);

    QAbstractSocket::SocketError socketError();
    QString socketErrorString() const;

    // documentation needs to be here, see https://stackoverflow.com/questions/49192523/
    /// Returns the client's current state.
    State state() const;
    QXmppStanza::Error::Condition xmppStreamError();

    QXmppTask<QXmpp::SendResult> sendSensitive(QXmppStanza &&, const std::optional<QXmppSendStanzaParams> & = {});
    QXmppTask<QXmpp::SendResult> send(QXmppStanza &&, const std::optional<QXmppSendStanzaParams> & = {});
    QXmppTask<QXmpp::SendResult> reply(QXmppStanza &&stanza, const std::optional<QXmppE2eeMetadata> &e2eeMetadata, const std::optional<QXmppSendStanzaParams> & = {});
    QXmppTask<IqResult> sendIq(QXmppIq &&, const std::optional<QXmppSendStanzaParams> & = {});
    QXmppTask<IqResult> sendSensitiveIq(QXmppIq &&, const std::optional<QXmppSendStanzaParams> & = {});
    QXmppTask<EmptyResult> sendGenericIq(QXmppIq &&, const std::optional<QXmppSendStanzaParams> & = {});

    /// This signal is emitted when the client connects successfully to the
    /// XMPP server i.e. when a successful XMPP connection is established.
    /// XMPP Connection involves following sequential steps:
    ///     - TCP socket connection
    ///     - Client sends start stream
    ///     - Server sends start stream
    ///     - TLS negotiation (encryption)
    ///     - Authentication
    ///     - Resource binding
    ///     - Session establishment
    ///
    /// After all these steps a successful XMPP connection is established and
    /// connected() signal is emitted.
    ///
    /// After the connected() signal is emitted QXmpp will send the roster
    /// request to the server. On receiving the roster, QXmpp will emit
    /// QXmppRosterManager::rosterReceived(). After this signal,
    /// QXmppRosterManager object gets populated and you can use
    /// \c findExtension<QXmppRosterManager>() to get the handle of
    /// QXmppRosterManager object.
    Q_SIGNAL void connected();

    /// This signal is emitted when the XMPP connection disconnects.
    Q_SIGNAL void disconnected();

    /// This signal is emitted when the XMPP connection encounters any error.
    /// The QXmppClient::Error parameter specifies the type of error occurred.
    /// It could be due to TCP socket or the xml stream or the stanza.
    /// Depending upon the type of error occurred use the respective get function to
    /// know the error.
    Q_SIGNAL void error(QXmppClient::Error);

    /// This signal is emitted when the XMPP connection encounters any fatal error.
    ///
    /// \param error Detailed error object with a description. It may contain
    ///  * QAbstractSocket::SocketError
    ///  * QXmpp::TimeoutError
    ///  * QXmpp::StreamError
    ///  * QXmpp::AuthenticationError
    ///  * QXmpp::BindError
    ///
    /// \since QXmpp 1.7
    Q_SIGNAL void errorOccurred(const QXmppError &error);

    /// This signal is emitted when the logger changes.
    Q_SIGNAL void loggerChanged(QXmppLogger *logger);

    /// Notifies that an XMPP message stanza is received. The QXmppMessage
    /// parameter contains the details of the message sent to this client.
    /// In other words whenever someone sends you a message this signal is
    /// emitted.
    Q_SIGNAL void messageReceived(const QXmppMessage &message);

    /// Notifies that an XMPP presence stanza is received. The QXmppPresence
    /// parameter contains the details of the presence sent to this client.
    /// This signal is emitted when someone login/logout or when someone's status
    /// changes Busy, Idle, Invisible etc.
    Q_SIGNAL void presenceReceived(const QXmppPresence &presence);

    /// This signal is emitted when IQs of type result or error are received by
    /// the client and no registered QXmppClientExtension could handle it.
    ///
    /// This is useful when it is only important to check whether the response
    /// of an IQ was successful. However, the recommended way is still to use an
    /// additional QXmppClientExtension for this kind of tasks.
    Q_SIGNAL void iqReceived(const QXmppIq &iq);

    /// This signal is emitted to indicate that one or more SSL errors were
    /// encountered while establishing the identity of the server.
    Q_SIGNAL void sslErrors(const QList<QSslError> &errors);

    /// This signal is emitted when the client state changes.
    Q_SIGNAL void stateChanged(QXmppClient::State state);

    /// Emitted when the credentials, e.g. tokens have changed.
    ///
    /// This means that the QXmppCredentials in the QXmppConfiguration of this client has changed.
    ///
    /// \since QXmpp 1.8
    Q_SIGNAL void credentialsChanged();

    Q_SLOT void connectToServer(const QXmppConfiguration &, const QXmppPresence &initialPresence = {});
    Q_SLOT void connectToServer(const QString &jid, const QString &password);
    Q_SLOT void disconnectFromServer();

#if QXMPP_DEPRECATED_SINCE(1, 1)
    [[deprecated("Use findExtension<QXmppRosterManager>() instead")]]
    QXmppRosterManager &rosterManager();

    [[deprecated("Use findExtension<QXmppVCardManager>() instead")]]
    QXmppVCardManager &vCardManager();

    [[deprecated("Use findExtension<QXmppVersionManager>() instead")]]
    QXmppVersionManager &versionManager();
#endif
#if QXMPP_DEPRECATED_SINCE(1, 12)
    [[deprecated("Use async send()")]]
    Q_SLOT bool sendPacket(const QXmppNonza &);
    [[deprecated("Use async send()")]]
    Q_SLOT void sendMessage(const QString &bareJid, const QString &message);
#endif

    /// \cond
    bool sendLegacy(const QXmppNonza &s)
    {
        QT_WARNING_PUSH
        QT_WARNING_DISABLE_DEPRECATED
        return sendPacket(s);
        QT_WARNING_POP
    }
    QString sendLegacyId(const QXmppStanza &s) { return sendLegacy(s) ? s.id() : QString(); }
    /// \endcond

private:
    QXmppOutgoingClient *stream() const;
    void injectIq(const QDomElement &element, const std::optional<QXmppE2eeMetadata> &e2eeMetadata);
    bool injectMessage(QXmppMessage &&message);

    void setIgnoredStreamErrors(const QVector<QXmpp::StreamError> &);

    void _q_elementReceived(const QDomElement &element, bool &handled);
    void _q_reconnect();
    void onInternalSocketStateChanged();
    void _q_streamConnected(const QXmpp::Private::SessionBegin &);
    void _q_streamDisconnected();

    const std::unique_ptr<QXmppClientPrivate> d;

    friend class QXmppClientExtension;
    friend class QXmppCarbonManagerV2;
    friend class QXmppRegistrationManager;
    friend class TestClient;
};

#endif  // QXMPPCLIENT_H
