// SPDX-FileCopyrightText: 2010 Jeremy Lainé <jeremy.laine@m4x.org>
// SPDX-FileCopyrightText: 2022 Melvin Keskin <melvo@olomono.de>
// SPDX-FileCopyrightText: 2023 Tibor Csötönyi <work@taibsu.de>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#ifndef QXMPPJINGLEIQ_H
#define QXMPPJINGLEIQ_H

#include "QXmppConstants_p.h"
#include "QXmppIq.h"

#include <variant>

#include <QHostAddress>

class QXmppJingleCandidatePrivate;
class QXmppJingleRtpDescriptionPrivate;
class QXmppJingleIqContentPrivate;
class QXmppJingleIqReasonPrivate;
class QXmppJingleIqPrivate;
class QXmppJinglePayloadTypePrivate;
class QXmppJingleRtpCryptoElementPrivate;
class QXmppJingleRtpEncryptionPrivate;
class QXmppJingleRtpFeedbackPropertyPrivate;
class QXmppJingleRtpHeaderExtensionPropertyPrivate;
class QXmppSdpParameterPrivate;
class QXmppJingleMessageInitiationElementPrivate;
class QXmppCallInviteElementPrivate;

class QXMPP_EXPORT QXmppSdpParameter
{
public:
    QXmppSdpParameter();

    QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppSdpParameter)

    QString name() const;
    void setName(const QString &name);

    QString value() const;
    void setValue(const QString &value);

    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

    static bool isSdpParameter(const QDomElement &element);

private:
    QSharedDataPointer<QXmppSdpParameterPrivate> d;
};

class QXMPP_EXPORT QXmppJingleRtpCryptoElement
{
public:
    QXmppJingleRtpCryptoElement();

    QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpCryptoElement)

    uint32_t tag() const;
    void setTag(uint32_t tag);

    QString cryptoSuite() const;
    void setCryptoSuite(const QString &cryptoSuite);

    QString keyParams() const;
    void setKeyParams(const QString &keyParams);

    QString sessionParams() const;
    void setSessionParams(const QString &sessionParams);

    static constexpr std::tuple XmlTag = { u"crypto", QXmpp::Private::ns_jingle_rtp };
    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

    static bool isJingleRtpCryptoElement(const QDomElement &element);

private:
    QSharedDataPointer<QXmppJingleRtpCryptoElementPrivate> d;
};

class QXMPP_EXPORT QXmppJingleRtpEncryption
{
public:
    QXmppJingleRtpEncryption();

    QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpEncryption)

    bool isRequired() const;
    void setRequired(bool isRequired);

    QList<QXmppJingleRtpCryptoElement> cryptoElements() const;
    void setCryptoElements(const QList<QXmppJingleRtpCryptoElement> &cryptoElements);

    static constexpr std::tuple XmlTag = { u"encryption", QXmpp::Private::ns_jingle_rtp };
    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

    static bool isJingleRtpEncryption(const QDomElement &element);

private:
    QSharedDataPointer<QXmppJingleRtpEncryptionPrivate> d;
};

class QXMPP_EXPORT QXmppJingleRtpFeedbackProperty
{
public:
    QXmppJingleRtpFeedbackProperty();
    QXmppJingleRtpFeedbackProperty(const QString &type, const QString &subtype = {});
    QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpFeedbackProperty)

    QString type() const;
    void setType(const QString &type);

    QString subtype() const;
    void setSubtype(const QString &subtype);

    QList<QXmppSdpParameter> parameters() const;
    void setParameters(const QList<QXmppSdpParameter> &parameters);

    static constexpr std::tuple XmlTag = { u"rtcp-fb", QXmpp::Private::ns_jingle_rtcp_fb };
    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

    static bool isJingleRtpFeedbackProperty(const QDomElement &element);

private:
    QSharedDataPointer<QXmppJingleRtpFeedbackPropertyPrivate> d;
};

class QXMPP_EXPORT QXmppJingleRtpFeedbackInterval
{
public:
    QXmppJingleRtpFeedbackInterval();

    QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpFeedbackInterval)

    uint64_t value() const;
    void setValue(uint64_t value);

    static constexpr std::tuple XmlTag = { u"rtcp-fb-trr-int", QXmpp::Private::ns_jingle_rtcp_fb };
    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

    static bool isJingleRtpFeedbackInterval(const QDomElement &element);

private:
    uint64_t m_value;
};

class QXMPP_EXPORT QXmppJingleRtpHeaderExtensionProperty
{
public:
    enum Senders {
        Both,
        Initiator,
        Responder
    };

    QXmppJingleRtpHeaderExtensionProperty();
    QXmppJingleRtpHeaderExtensionProperty(const QString &uri, uint32_t id);
    QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpHeaderExtensionProperty)

    uint32_t id() const;
    void setId(uint32_t id);

    QString uri() const;
    void setUri(const QString &uri);

    Senders senders() const;
    void setSenders(Senders senders);

    QList<QXmppSdpParameter> parameters() const;
    void setParameters(const QList<QXmppSdpParameter> &parameters);

    static constexpr std::tuple XmlTag = { u"rtp-hdrext", QXmpp::Private::ns_jingle_rtp_hdrext };
    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

    static bool isJingleRtpHeaderExtensionProperty(const QDomElement &element);

private:
    QSharedDataPointer<QXmppJingleRtpHeaderExtensionPropertyPrivate> d;
};

/*!
    \inmodule QXmpp

    \brief The QXmppJinglePayloadType class represents a payload type
    as specified by \xep{0167}{Jingle RTP Sessions} and RFC 5245.
*/
class QXMPP_EXPORT QXmppJinglePayloadType
{
public:
    QXmppJinglePayloadType();
    QXmppJinglePayloadType(const QXmppJinglePayloadType &other);
    ~QXmppJinglePayloadType();

    unsigned char channels() const;
    void setChannels(unsigned char channels);

    unsigned int clockrate() const;
    void setClockrate(unsigned int clockrate);

    unsigned char id() const;
    void setId(unsigned char id);

    unsigned int maxptime() const;
    void setMaxptime(unsigned int maxptime);

    QString name() const;
    void setName(const QString &name);

    QMap<QString, QString> parameters() const;
    void setParameters(const QMap<QString, QString> &parameters);

    unsigned int ptime() const;
    void setPtime(unsigned int ptime);

    QList<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties() const;
    void setRtpFeedbackProperties(const QList<QXmppJingleRtpFeedbackProperty> &rtpFeedbackProperties);

    QList<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals() const;
    void setRtpFeedbackIntervals(const QList<QXmppJingleRtpFeedbackInterval> &rtpFeedbackIntervals);

    static constexpr std::tuple XmlTag = { u"payload-type", QXmpp::Private::ns_jingle_rtp };
    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

    QXmppJinglePayloadType &operator=(const QXmppJinglePayloadType &other);
    bool operator==(const QXmppJinglePayloadType &other) const;

private:
    QSharedDataPointer<QXmppJinglePayloadTypePrivate> d;
};

class QXMPP_EXPORT QXmppJingleRtpDescription
{
public:
    QXmppJingleRtpDescription();
    QXmppJingleRtpDescription(const QString &media, quint32 ssrc = 0, const QList<QXmppJinglePayloadType> &payloadTypes = {});
    QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpDescription)

    QString media() const;
    void setMedia(const QString &media);

    quint32 ssrc() const;
    void setSsrc(quint32 ssrc);

    void addPayloadType(const QXmppJinglePayloadType &payload);
    const QList<QXmppJinglePayloadType> &payloadTypes() const;
    void setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes);

    static constexpr std::tuple XmlTag = { u"description", QXmpp::Private::ns_jingle_rtp };
    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

private:
    QSharedDataPointer<QXmppJingleRtpDescriptionPrivate> d;
};

#if QXMPP_DEPRECATED_SINCE(1, 11)
using QXmppJingleDescription [[deprecated]] = QXmppJingleRtpDescription;
#endif

/*!
    \inmodule QXmpp

    \brief The QXmppJingleCandidate class represents a transport candidate
    as specified by \xep{0176}{Jingle ICE-UDP Transport Method}.
*/
class QXMPP_EXPORT QXmppJingleCandidate
{
public:
    /*!
        This enum is used to describe a candidate's type.

        \value HostType Host candidate, a local address/port.
        \value PeerReflexiveType Peer-reflexive candidate, the address/port as seen from the peer.
        \value ServerReflexiveType Server-reflexive candidate, the address/port as seen by the STUN server.
        \value RelayedType Relayed candidate, a candidate from a TURN relay.
    */
    enum Type {
        HostType,
        PeerReflexiveType,
        ServerReflexiveType,
        RelayedType
    };

    QXmppJingleCandidate();
    QXmppJingleCandidate(const QXmppJingleCandidate &other);
    QXmppJingleCandidate(QXmppJingleCandidate &&);
    ~QXmppJingleCandidate();

    QXmppJingleCandidate &operator=(const QXmppJingleCandidate &other);
    QXmppJingleCandidate &operator=(QXmppJingleCandidate &&);

    int component() const;
    void setComponent(int component);

    QString foundation() const;
    void setFoundation(const QString &foundation);

    int generation() const;
    void setGeneration(int generation);

    QHostAddress host() const;
    void setHost(const QHostAddress &host);

    QString id() const;
    void setId(const QString &id);

    int network() const;
    void setNetwork(int network);

    quint16 port() const;
    void setPort(quint16 port);

    int priority() const;
    void setPriority(int priority);

    QString protocol() const;
    void setProtocol(const QString &protocol);

    QXmppJingleCandidate::Type type() const;
    void setType(QXmppJingleCandidate::Type);

    bool isNull() const;

    static constexpr std::tuple XmlTag = { u"candidate", QXmpp::Private::ns_jingle_ice_udp };
    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

private:
    QSharedDataPointer<QXmppJingleCandidatePrivate> d;
};

class QXMPP_EXPORT QXmppJingleReason
{
public:
    /*!
        This enum is used to describe a reason's type.

        \value AlternativeSession The party prefers to use an existing session with the peer rather than initiate a new session; the Jingle session ID of the alternative session SHOULD be provided as the XML character data of the &lt;sid/&gt; child.
        \value Busy The party is busy and cannot accept a session.
        \value Cancel The initiator wishes to formally cancel the session initiation request.
        \value ConnectivityError The action is related to connectivity problems.
        \value Decline The party wishes to formally decline the session.
        \value Expired The session length has exceeded a pre-defined time limit (e.g., a meeting hosted at a conference service).
        \value FailedApplication The party has been unable to initialize processing related to the application type.
        \value FailedTransport The party has been unable to establish connectivity for the transport method.
        \value GeneralError The action is related to a non-specific application error.
        \value Gone The entity is going offline or is no longer available.
        \value IncompatibleParameters The party supports the offered application type but does not support the offered or negotiated parameters.
        \value MediaError The action is related to media processing problems.
        \value SecurityError The action is related to a violation of local security policies.
        \value Success The action is generated during the normal course of state management and does not reflect any error.
        \value Timeout A request has not been answered so the sender is timing out the request.
        \value UnsupportedApplications The party supports none of the offered application types.
        \value UnsupportedTransports The party supports none of the offered transport methods.

        \value None
    */
    enum Type {
        None,
        AlternativeSession,
        Busy,
        Cancel,
        ConnectivityError,
        Decline,
        Expired,
        FailedApplication,
        FailedTransport,
        GeneralError,
        Gone,
        IncompatibleParameters,
        MediaError,
        SecurityError,
        Success,
        Timeout,
        UnsupportedApplications,
        UnsupportedTransports
    };

    /*!
        Condition of an RTP-specific error
        \since QXmpp 1.5

        \value NoErrorCondition There is no error condition.
        \value InvalidCrypto The encryption offer is rejected.
        \value CryptoRequired Encryption is required but not offered.
    */
    enum RtpErrorCondition {
        NoErrorCondition,
        InvalidCrypto,
        CryptoRequired
    };

    QXmppJingleReason();
    QXmppJingleReason(Type, const QString &, std::optional<RtpErrorCondition>);
    QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleReason)

    QString text() const;
    void setText(const QString &text);

    Type type() const;
    void setType(Type type);

    RtpErrorCondition rtpErrorCondition() const;
    void setRtpErrorCondition(RtpErrorCondition rtpErrorCondition);

    static constexpr std::tuple XmlTag = { u"reason", QXmpp::Private::ns_jingle };
    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

private:
    QSharedDataPointer<QXmppJingleIqReasonPrivate> d;
};

/*!
    \inmodule QXmpp

    \brief The QXmppJingleIq class represents an IQ used for initiating media
    sessions as specified by \xep{0166}{Jingle}.

    \ingroup Stanzas
*/
class QXMPP_EXPORT QXmppJingleIq : public QXmppIq
{
public:
    /*!
        This enum is used to describe a Jingle action.

        \value ContentAccept
        \value ContentAdd
        \value ContentModify
        \value ContentReject
        \value ContentRemove
        \value DescriptionInfo
        \value SecurityInfo
        \value SessionAccept
        \value SessionInfo
        \value SessionInitiate
        \value SessionTerminate
        \value TransportAccept
        \value TransportInfo
        \value TransportReject
        \value TransportReplace
    */
    enum Action {
        ContentAccept,
        ContentAdd,
        ContentModify,
        ContentReject,
        ContentRemove,
        DescriptionInfo,
        SecurityInfo,
        SessionAccept,
        SessionInfo,
        SessionInitiate,
        SessionTerminate,
        TransportAccept,
        TransportInfo,
        TransportReject,
        TransportReplace,
    };

    enum Creator {
        Initiator,
        Responder
    };

    struct RtpSessionStateActive { };
    struct RtpSessionStateHold { };
    struct RtpSessionStateUnhold { };
    struct RtpSessionStateMuting {
        /*!
            True when temporarily not sending media to the other party but continuing to accept
            media from it, false for ending mute state
        */
        bool isMute;
        /*! Creator of the corresponding session */
        Creator creator;
        /*! Session to be muted (e.g., only audio or video) */
        QString name;
    };
    struct RtpSessionStateRinging { };

    using RtpSessionState = std::variant<RtpSessionStateActive, RtpSessionStateHold, RtpSessionStateUnhold, RtpSessionStateMuting, RtpSessionStateRinging>;

    /*!
        \typealias QXmppJingleIq::Reason

        Alias to QXmppJingleReason for compatibility.
    */
    using Reason = QXmppJingleReason;

    /*!
        \inmodule QXmpp

        \internal

        The QXmppJingleIq::Content class represents the "content" element of a
        QXmppJingleIq.
    */
    class QXMPP_EXPORT Content
    {
    public:
        Content();
        Content(const QXmppJingleIq::Content &other);
        Content(QXmppJingleIq::Content &&);
        ~Content();

        Content &operator=(const Content &other);
        Content &operator=(Content &&);

        QString creator() const;
        void setCreator(const QString &creator);

        QString name() const;
        void setName(const QString &name);

        QString senders() const;
        void setSenders(const QString &senders);

        // XEP-0167: Jingle RTP Sessions
        QXmppJingleRtpDescription description() const;
        void setDescription(const QXmppJingleRtpDescription &description);

#if QXMPP_DEPRECATED_SINCE(1, 6)
        QString descriptionMedia() const;
        void setDescriptionMedia(const QString &media);

        quint32 descriptionSsrc() const;
        void setDescriptionSsrc(quint32 ssrc);

        void addPayloadType(const QXmppJinglePayloadType &payload);
        QList<QXmppJinglePayloadType> payloadTypes() const;
        void setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes);
#endif

        bool isRtpMultiplexingSupported() const;
        void setRtpMultiplexingSupported(bool isRtpMultiplexingSupported);

        std::optional<QXmppJingleRtpEncryption> rtpEncryption() const;
        void setRtpEncryption(const std::optional<QXmppJingleRtpEncryption> &rtpEncryption);

        void addTransportCandidate(const QXmppJingleCandidate &candidate);
        QList<QXmppJingleCandidate> transportCandidates() const;
        void setTransportCandidates(const QList<QXmppJingleCandidate> &candidates);

        QString transportUser() const;
        void setTransportUser(const QString &user);

        QString transportPassword() const;
        void setTransportPassword(const QString &password);

        QList<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties() const;
        void setRtpFeedbackProperties(const QList<QXmppJingleRtpFeedbackProperty> &rtpFeedbackProperties);

        QList<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals() const;
        void setRtpFeedbackIntervals(const QList<QXmppJingleRtpFeedbackInterval> &rtpFeedbackIntervals);

        QList<QXmppJingleRtpHeaderExtensionProperty> rtpHeaderExtensionProperties() const;
        void setRtpHeaderExtensionProperties(const QList<QXmppJingleRtpHeaderExtensionProperty> &rtpHeaderExtensionProperties);

        bool isRtpHeaderExtensionMixingAllowed() const;
        void setRtpHeaderExtensionMixingAllowed(bool isRtpHeaderExtensionMixingAllowed);

        // XEP-0320: Use of DTLS-SRTP in Jingle Sessions
        QByteArray transportFingerprint() const;
        void setTransportFingerprint(const QByteArray &fingerprint);

        QString transportFingerprintHash() const;
        void setTransportFingerprintHash(const QString &hash);

        QString transportFingerprintSetup() const;
        void setTransportFingerprintSetup(const QString &setup);

        static constexpr std::tuple XmlTag = { u"content", QXmpp::Private::ns_jingle };
        void parse(const QDomElement &element);
        void toXml(QXmlStreamWriter *writer) const;

        bool parseSdp(const QString &sdp);
        QString toSdp() const;

    private:
        QSharedDataPointer<QXmppJingleIqContentPrivate> d;
    };

    QXmppJingleIq();
    QXmppJingleIq(const QXmppJingleIq &other);
    QXmppJingleIq(QXmppJingleIq &&);
    ~QXmppJingleIq() override;

    QXmppJingleIq &operator=(const QXmppJingleIq &other);
    QXmppJingleIq &operator=(QXmppJingleIq &&);

    Action action() const;
    void setAction(Action action);

    void addContent(const Content &content);
    QList<Content> contents() const;
    void setContents(const QList<Content> &contents);

    QString initiator() const;
    void setInitiator(const QString &initiator);

    std::optional<QXmppJingleReason> actionReason() const;
    void setActionReason(const std::optional<QXmppJingleReason> &);

#if QXMPP_DEPRECATED_SINCE(1, 11)
    [[deprecated("Use actionReason() instead.")]]
    QXmppJingleReason &reason();
    [[deprecated("Use actionReason() instead.")]]
    const QXmppJingleReason &reason() const;
#endif

    QString responder() const;
    void setResponder(const QString &responder);

#if QXMPP_DEPRECATED_SINCE(1, 5)
    QT_DEPRECATED_X("Use QXmpp::rtpSessionState() instead")
    bool ringing() const;
    QT_DEPRECATED_X("Use QXmpp::setRtpSessionState() instead")
    void setRinging(bool ringing);
#endif

    QString sid() const;
    void setSid(const QString &sid);

    QString mujiGroupChatJid() const;
    void setMujiGroupChatJid(const QString &mujiGroupChatJid);

    std::optional<RtpSessionState> rtpSessionState() const;
    void setRtpSessionState(const std::optional<RtpSessionState> &rtpSessionState);

    static constexpr std::tuple<QStringView, QStringView> PayloadXmlTag = { u"jingle", QXmpp::Private::ns_jingle };
    [[deprecated("Use QXmpp::isIqType()")]]
    static bool isJingleIq(const QDomElement &element);

protected:
    void parseElementFromChild(const QDomElement &element) override;
    void toXmlElementFromChild(QXmlStreamWriter *writer) const override;

private:
    QSharedDataPointer<QXmppJingleIqPrivate> d;
};

class QXMPP_EXPORT QXmppJingleMessageInitiationElement
{
public:
    enum class Type {
        None,
        Propose,
        Ringing,
        Proceed,
        Reject,
        Retract,
        Finish
    };

    QXmppJingleMessageInitiationElement();
    QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleMessageInitiationElement)

    Type type() const;
    void setType(Type type);

    QString id() const;
    void setId(const QString &id);

    std::optional<QXmppJingleRtpDescription> description() const;
    void setDescription(std::optional<QXmppJingleRtpDescription> description);
    QList<QXmppJingleRtpDescription> descriptions() const;
    void setDescriptions(const QList<QXmppJingleRtpDescription> &descriptions);

    std::optional<QXmppJingleReason> reason() const;
    void setReason(std::optional<QXmppJingleReason> reason);

    bool containsTieBreak() const;
    void setContainsTieBreak(bool containsTieBreak);

    QString migratedTo() const;
    void setMigratedTo(const QString &migratedTo);

    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

    static bool isJingleMessageInitiationElement(const QDomElement &);

private:
    QSharedDataPointer<QXmppJingleMessageInitiationElementPrivate> d;
};

class QXMPP_EXPORT QXmppCallInviteElement
{
public:
    enum class Type {
        None,
        Invite,
        Retract,
        Accept,
        Reject,
        Left
    };

    /*!
        \inmodule QXmpp
        \brief References a Jingle session for a call invite.
    */
    struct Jingle {
        QString sid;
        std::optional<QString> jid;

        bool operator==(const Jingle &other) const { return other.sid == sid && other.jid == jid; }

        void parse(const QDomElement &element);
        void toXml(QXmlStreamWriter *writer) const;
    };

    /*!
        \inmodule QXmpp
        \brief References an external URI for a call invite.
    */
    struct External {
        QString uri;

        bool operator==(const External &other) const { return other.uri == uri; }

        static constexpr std::tuple XmlTag = { u"external", QXmpp::Private::ns_call_invites };
        void parse(const QDomElement &el);
        void toXml(QXmlStreamWriter *writer) const;
    };

    QXmppCallInviteElement();
    QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppCallInviteElement)

    Type type() const;
    void setType(Type type);

    QString id() const;
    void setId(const QString &id);

    bool audio() const;
    void setAudio(bool audio);

    bool video() const;
    void setVideo(bool video);

    std::optional<Jingle> jingle() const;
    void setJingle(std::optional<Jingle> jingle);

    std::optional<QList<External>> external() const;
    void setExternal(std::optional<QList<External>> external);

    void parse(const QDomElement &element);
    void toXml(QXmlStreamWriter *writer) const;

    static bool isCallInviteElement(const QDomElement &);

private:
    QSharedDataPointer<QXmppCallInviteElementPrivate> d;
};

Q_DECLARE_METATYPE(QXmppJingleReason::RtpErrorCondition)

#endif
