// SPDX-FileCopyrightText: 2019 Melvin Keskin <melvo@olomono.de>
// SPDX-FileCopyrightText: 2019 Linus Jahn <lnj@kaidan.im>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#ifndef QXMPPREGISTRATIONMANAGER_H
#define QXMPPREGISTRATIONMANAGER_H

#include "QXmppClientExtension.h"
#include "QXmppRegisterIq.h"

class QXmppRegistrationManagerPrivate;

/*!
    \inmodule QXmpp

    \brief The QXmppRegistrationManager class manages in-band registration and
    account management tasks like changing the password as defined in
    \xep{0077}{In-Band Registration}.

    <h3 id="activation">Activating the manager</h3>

    To make use of this manager, you need to instantiate it and load it into
    the QXmppClient instance as follows:

    \code
    auto *registrationManager = new QXmppRegistrationManager;
    client->addExtension(registrationManager);
    \endcode

    <h3>Setting up service discovery correctly for this manager</h3>

    This manager automatically recognizes whether the local server supports
    \xep{0077}{In-Band Registration}. As soon as the result is retrieved, the supportedByServer() property should be
    correct and could be used to display the user whether account management tasks can be
    performed on this server.

    However, this is not relevant if you only want to
    <a href="#register-account">register a new account on a server</a>.

    <h3>Changing the account's password</h3>

    To change the password of the current account changePassword() can be used.
    Upon that either passwordChanged() or passwordChangeFailed() is emitted.

    If changing the password was successful, the new password is automatically
    set in the QXmppClient::configuration(), so reconnecting works properly.

    Example:
    \code
    auto *registrationManager = client->findExtension<QXmppRegistrationManager>();
    connect(registrationManager, &QXmppRegistrationManager::passwordChanged, [=](const QString &newPassword) {
    qDebug() << "Password changed to:" << newPassword;
    });
    connect(registrationManager, &QXmppRegistrationManager::passwordChangeFailed, [=](QXmppStanza::Error error) {
    qDebug() << "Couldn't change the password:" << error.text();
    });

    registrationManager->changePassword(client->configuration().user(), "m1cr0$0ft");
    \endcode

    <h3>Unregistration with the server</h3>

    If you want to delete your account on the server, you can do that using
    deleteAccount(). When the result is received either accountDeleted() or
    accountDeletionFailed() is emitted. In case it was successful, the manager
    automatically disconnects from the client. If the server takes too much time to confirm the
    account deletion, you may disconnect manually after a reasonable timeout.

    QXmpp periodically sends pings to the server. If the server does not respond to it within
    QXmppConfiguration::keepAliveInterval(), QXmpp disconnects from the server. Make sure to handle
    that case if it happens during account deletion. E.g., you could try to connect to the server
    again with the same account and check whether QXmpp::AuthenticationError::NotAuthorized occurs.
    In that case, the account can be considered as deleted.

    \code
    auto *registrationManager = client->findExtension<QXmppRegistrationManager>();
    connect(registrationManager, &QXmppRegistrationManager::accountDeleted, [=]() {
    qDebug() << "Account deleted successfully, the client is disconnecting now";
    });
    connect(registrationManager, &QXmppRegistrationManager::accountDeletionFailed, [=](QXmppStanza::Error error) {
    qDebug() << "Couldn't delete account:" << error.text();
    });
    registrationManager->deleteAccount();
    \endcode

    <h3 id="register-account">Registering with a server</h3>

    Registering with a server consists of multiple steps:
    \list 1
        \li Requesting the registration form from the server.
        \li Filling out the registration form.
        \li Sending the completed form to the server. On failure (e.g.
            because of a username conflict), the process continues at step 1
            again.
        \li Connecting with the newly created account.
    \endlist

    <h4>Requesting the registration form from the server</h4>

    First of all, you need to enable the registration process in the
    registration manager, which of course needs to be <a href="#activation">
    activated</a> in the client.

    \code
    auto *registrationManager = client->findExtension<QXmppRegistrationManager>();
    registrationManager->setRegisterOnConnectEnabled(true);
    \endcode

    After that you can start to connect to the server you want to register
    with. No JID is set in the QXmppConfiguration for the client and instead
    only the server is set.

    \code
    QXmppConfiguration config;
    config.setDomain("example.org");

    client->connectToServer(config);
    \endcode

    Alternatively, you can also provide a domain-only JID and no password to
    connectToServer():

    \code
    client->connectToServer("example.org", QString());
    \endcode

    Now as soon as (START)TLS was handled, the registration manager interrupts
    the normal connection process. The manager checks whether the server
    supports in-band registration and whether the server advertises this as a
    stream feature.

    If the server does not support in-band registration, the manager will abort
    the connection at this point and emit the registrationFailed() signal with
    a fixed QXmppStanza::Error of type QXmppStanza::Error::Cancel and with a
    condition of QXmppStanza::Error::FeatureNotImplemented.

    The manager will now request the registration form. This will either result
    in an error (reported by registrationFailed()) or, if everything went well,
    the registration form is reported by registrationFormReceived().

    To handle everything correctly, you need to connect to both signals:
    \code
    connect(registrationManager, &QXmppRegistrationManager::registrationFormReceived, [=](const QXmppRegisterIq &iq) {
    qDebug() << "Form received:" << iq.instructions().
    // you now need to complete the form
    });
    connect(registrationManager, &QXmppRegistrationManager::registrationFailed, [=](const QXmppStanza::Error &error) {
    qDebug() << "Requesting the registration form failed:" << error.text();
    });
    \endcode

    <h4>Filling out the registration form</h4>

    Now you need to fill out the registration form. The server can close the connection during that
    time. It is due to some servers kicking unauthorized clients after some time when the clients
    are inactive. That is often the case when user interaction is required before the completed form
    is submitted to the server. In order to support account creation for both servers closing the
    connection and servers keeping it open, you need to handle those cases appropriately.

    If the returned IQ contains a data form, that can be displayed to a user or
    can be filled out in another way.

    If the server does not support data forms, you can check the standard
    fields of the QXmppRegisterIq. You need to search the fields for empty
    (non-null) strings. All fields that contain an empty string are required
    and can be filled out. You can just set values for those fields and send
    the form as described in the next step.

    \note QXmpp currently has only implemented the most important default
    fields in the QXmppRegisterIq. The other fields are not very widespread,
    because data forms are usually used for such purposes.

    <h4>Sending the completed form to the server</h4>

    <b>Option A</b>: If the connection is still open once the form is filled out, set the form using
    setRegistrationFormToSend() and then trigger the form to be directly sent using
    sendCachedRegistrationForm().

    \code
    registrationManager->setRegistrationFormToSend(completedForm);
    registrationManager->sendCachedRegistrationForm();
    \endcode

    <b>Option B</b>: If the connection is closed before the form is filled out, set the form using
    setRegistrationFormToSend() and connect to the server again. The registration manager will
    automatically send the set form once connected.

    \code
    registrationManager->setRegistrationFormToSend(completedForm);

    // As before, you only need to provide a domain to connectToServer()
    client->connectToServer(...);
    // the registration manager sends the form automatically
    \endcode

    The form is now sent to the server. As soon as the result is received,
    either registrationSucceeded() or registrationFailed() is emitted.

    In case there was a conflict or another error, you should request a new
    form and restart the process. This is especially important, if the form can
    only be used once as with most CAPTCHA implementations.

    <h4>Connecting with the newly created account</h4>

    You need to disconnect now. The user can then enter their credentials and connect as usual.

    It is also possible to extract username and password from the sent form,
    but that does not work always. There might also be forms that have no clear
    username or password fields.

    \ingroup Managers

    \since QXmpp 1.2
*/
class QXMPP_EXPORT QXmppRegistrationManager : public QXmppClientExtension
{
    Q_OBJECT

    /*!
        \property QXmppRegistrationManager::supportedByServer

        Whether support of \xep{0077}{In-band Registration} has been discovered on the server.
    */
    Q_PROPERTY(bool supportedByServer READ supportedByServer NOTIFY supportedByServerChanged)

public:
    QXmppRegistrationManager();
    ~QXmppRegistrationManager();

    QStringList discoveryFeatures() const override;

    void changePassword(const QString &newPassword);
    void deleteAccount();

    // documentation needs to be here, see https://stackoverflow.com/questions/49192523/
    /*!
        Returns whether the server supports registration.

        By default this is set to false and only changes, if you request the
        service discovery info of the connected server using
        QXmppDiscoveryManager::requestInfo().

        This is only relevant to actions that happen after authentication.

        \sa QXmppRegistrationManager::supportedByServerChanged()
    */
    bool supportedByServer() const;

    void requestRegistrationForm(const QString &service = {});

    void setRegistrationFormToSend(const QXmppRegisterIq &iq);
    void setRegistrationFormToSend(const QXmppDataForm &dataForm);
    void sendCachedRegistrationForm();

    bool registerOnConnectEnabled() const;
    void setRegisterOnConnectEnabled(bool enabled);

    bool handleStanza(const QDomElement &stanza) override;

    /*!
        Emitted, when registrationSupported() changed.

        This can happen after the service discovery info of the server was
        retrieved using QXmppDiscoveryManager::requestInfo() or on disconnect.
    */
    Q_SIGNAL void supportedByServerChanged();

    /*!
        Emitted, when the password of the account was changed successfully.

        The new password is automatically set in QXmppClient::configuration().

        \a newPassword is the new password that was set on the server.
    */
    Q_SIGNAL void passwordChanged(const QString &newPassword);

    /*!
        Emitted, when changing the password did not succeed. \a error is the error returned
        from the service.
    */
    Q_SIGNAL void passwordChangeFailed(QXmppStanza::Error error);

    /*!
        Emitted, when a registration form has been received.

        \a iq is the received form. If it does not contain a valid data form
        (see QXmppRegisterIq::form()), the required fields should be marked by
        empty (but not null) strings in the QXmppRegisterIq (i.e.
        QXmppRegisterIq::password().isNull() => false).
    */
    Q_SIGNAL void registrationFormReceived(const QXmppRegisterIq &iq);

    /*! Emitted, when the account was deleted successfully. */
    Q_SIGNAL void accountDeleted();

    /*!
        Emitted, when the account could not be deleted.

        \a error.
    */
    Q_SIGNAL void accountDeletionFailed(QXmppStanza::Error error);

    /*!
        Emitted, when the registration with a service completed successfully.

        To connect with the account you still need to set the correct
        credentials in QXmppClient::configuration() and reconnect.
    */
    Q_SIGNAL void registrationSucceeded();

    /*!
        Emitted, when the registration failed.

        \a error is the returned error from the service. The reported errors
        might be different from server to server, but the common ones are the
        following:
        \list
        \li type=Cancel and condition=Conflict: The username already exists.
        \li type=Cancel and condition=NotAllowed: The CAPTCHA verification
        failed.
        \li type=Modify and condition=NotAcceptable: Some required information
        was missing or the selected password was too weak.
        \li type=Modify and condition=JidMalformed: No username was provided or
        the username has an illegal format.
        \endlist
    */
    Q_SIGNAL void registrationFailed(const QXmppStanza::Error &error);

protected:
    void onRegistered(QXmppClient *client) override;
    void onUnregistered(QXmppClient *client) override;

private:
    void onConnected();
    void setSupportedByServer(bool supportedByServer);
    void handleAccountDeleted();

    const std::unique_ptr<QXmppRegistrationManagerPrivate> d;
};

#endif  // QXMPPREGISTRATIONMANAGER_H
