// SPDX-FileCopyrightText: 2015 Jeremy Lainé <jeremy.laine@m4x.org>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#ifndef QXMPPSTUN_P_H
#define QXMPPSTUN_P_H

#include "QXmppStun.h"

class QUdpSocket;
class QTimer;

//
//  W A R N I N G
//  -------------
//
// This file is not part of the QXmpp API.
//
// This header file may change from version to version without notice,
// or even be removed.
//
// We mean it.
//

//
// The QXmppStunTransaction class represents a STUN transaction.
//
class QXMPP_EXPORT QXmppStunTransaction : public QXmppLoggable
{
    Q_OBJECT

public:
    QXmppStunTransaction(const QXmppStunMessage &request, std::function<void(const QXmppStunMessage &)> sendStunMessage, std::function<void(QXmppStunTransaction *)> onFinished, QObject *parent);
    const QXmppStunMessage &request() const { return m_request; }
    const QXmppStunMessage &response() const { return m_response; }

    Q_SIGNAL void finished();
    Q_SIGNAL void writeStun(const QXmppStunMessage &request);

    Q_SLOT void readStun(const QXmppStunMessage &response);

private:
    Q_SLOT void retry();

    QXmppStunMessage m_request;
    QXmppStunMessage m_response;
    QTimer *m_retryTimer;
    int m_tries = 0;
};

class QXMPP_EXPORT QXmppIceTransport : public QXmppLoggable
{
    Q_OBJECT

public:
    QXmppIceTransport(QObject *parent = nullptr);
    ~QXmppIceTransport() override;

    virtual QXmppJingleCandidate localCandidate(int component) const = 0;
    virtual qint64 writeDatagram(const QByteArray &data, const QHostAddress &host, quint16 port) = 0;

    Q_SLOT virtual void disconnectFromHost() = 0;

    /// \brief This signal is emitted when a data packet is received.
    Q_SIGNAL void datagramReceived(const QByteArray &data, const QHostAddress &host, quint16 port);
};

//
// The QXmppTurnAllocation class represents a TURN allocation as defined
// by RFC 5766 Traversal Using Relays around NAT (TURN).
//
class QXMPP_EXPORT QXmppTurnAllocation : public QXmppIceTransport
{
    Q_OBJECT

public:
    enum AllocationState {
        UnconnectedState,
        ConnectingState,
        ConnectedState,
        ClosingState
    };

    QXmppTurnAllocation(QObject *parent = nullptr);
    ~QXmppTurnAllocation() override;

    QHostAddress relayedHost() const;
    quint16 relayedPort() const;
    AllocationState state() const;

    void setServer(const QHostAddress &host, quint16 port = 3478);
    void setUser(const QString &user);
    void setPassword(const QString &password);

    QXmppJingleCandidate localCandidate(int component) const override;
    qint64 writeDatagram(const QByteArray &data, const QHostAddress &host, quint16 port) override;

    /// \brief This signal is emitted once TURN allocation succeeds.
    Q_SIGNAL void connected();

    /// \brief This signal is emitted when TURN allocation fails.
    Q_SIGNAL void disconnected();

    Q_SLOT void connectToHost();
    Q_SLOT void disconnectFromHost() override;

private:
    void sendStunMessage(QXmppStunMessage &&message);
    Q_SLOT void readyRead();
    Q_SLOT void refresh();
    Q_SLOT void refreshChannels();
    void handleTransactionFinished(QXmppStunTransaction *);
    void writeStun(const QXmppStunMessage &message);

    void handleDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port);
    void setState(AllocationState state);

    QUdpSocket *socket = nullptr;
    QTimer *m_timer = nullptr;
    QTimer *m_channelTimer = nullptr;
    QString m_password;
    QString m_username;
    QHostAddress m_relayedHost;
    quint16 m_relayedPort = 0;
    QHostAddress m_turnHost;
    quint16 m_turnPort = 0;

    // channels
    typedef QPair<QHostAddress, quint16> Address;
    quint16 m_channelNumber = 0x4000;
    QMap<quint16, Address> m_channels;

    // state
    quint32 m_lifetime = 600;
    QByteArray m_key;
    QString m_realm;
    QByteArray m_nonce;
    AllocationState m_state = UnconnectedState;
    QList<QXmppStunTransaction *> m_transactions;
};

//
// The QXmppUdpTransport class represents a UDP transport.
//
class QXMPP_EXPORT QXmppUdpTransport : public QXmppIceTransport
{
    Q_OBJECT

public:
    QXmppUdpTransport(QUdpSocket *socket, QObject *parent = nullptr);
    ~QXmppUdpTransport() override;

    QXmppJingleCandidate localCandidate(int component) const override;
    qint64 writeDatagram(const QByteArray &data, const QHostAddress &host, quint16 port) override;

    Q_SLOT void disconnectFromHost() override;

private:
    Q_SLOT void readyRead();

    QUdpSocket *m_socket;
};

#endif
