// SPDX-FileCopyrightText: 2024 Linus Jahn <lnj@kaidan.im>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#ifndef XMPPSOCKET_H
#define XMPPSOCKET_H

#include "QXmppLogger.h"

#include "StreamError.h"

#include <QAbstractSocket>
#include <QDomDocument>
#include <QSslError>
#include <QXmlStreamReader>

class QSslSocket;
class TestStream;
class tst_QXmppStream;

namespace QXmpp::Private {

struct StreamOpen;

struct ServerAddress {
    enum ConnectionType {
        Tcp,
        Tls,
    };

    ConnectionType type;
    QString host;
    quint16 port;
};

class SendDataInterface
{
public:
    virtual bool sendData(const QByteArray &) = 0;
};

class DomReader
{
public:
    struct Unfinished { };

    enum ErrorType {
        InvalidState,
        NotWellFormed,
        UnsupportedXmlFeature,
    };
    struct Error {
        ErrorType type;
        QString text;
    };

    using Result = std::variant<QDomElement, Unfinished, Error>;

    Result process(QXmlStreamReader &);

private:
    QDomDocument doc;
    QDomElement currentElement;
    uint depth = 0;
};

class QXMPP_EXPORT XmppSocket : public QXmppLoggable, public SendDataInterface
{
    Q_OBJECT
public:
    explicit XmppSocket(QObject *parent);
    XmppSocket(QSslSocket *socket, QObject *parent);
    ~XmppSocket() override = default;

    QSslSocket *internalSocket() const { return m_socket; }
    void resetInternalSocket();

    bool isConnected() const;
    void connectToHost(const ServerAddress &);
    void disconnectFromHost();
    bool sendData(const QByteArray &) override;
    void resetStream();
    bool isStreamReceived() const { return m_streamReceived; }

    Q_SIGNAL void started();
    Q_SIGNAL void disconnected();
    Q_SIGNAL void stanzaReceived(const QDomElement &);
    Q_SIGNAL void streamReceived(const QXmpp::Private::StreamOpen &);
    Q_SIGNAL void streamClosed();
    Q_SIGNAL void errorOccurred(const QString &text, std::variant<StreamError, QAbstractSocket::SocketError> condition);
    Q_SIGNAL void sslErrorsOccurred(const QList<QSslError> &errors);
    // TODO: replace with own connection state
    Q_SIGNAL void internalSocketStateChanged();

private:
    void setSocket(QSslSocket *socket);
    void throwError(const QString &text, StreamError condition);
    void processData(const QString &data);

    friend class ::tst_QXmppStream;

    QXmlStreamReader m_reader;
    std::optional<DomReader> m_domReader;
    bool m_streamReceived = false;
    bool m_directTls = false;
    bool m_acceptInput = true;

    QSslSocket *m_socket = nullptr;
};

}  // namespace QXmpp::Private

#endif  // XMPPSOCKET_H
