// SPDX-FileCopyrightText: 2009 Manjeet Dahiya <manjeetdahiya@gmail.com>
// SPDX-FileCopyrightText: 2010 Jeremy Lainé <jeremy.laine@m4x.org>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "QXmppUtils.h"

#include "QXmppNonza.h"
#include "QXmppUtils_p.h"

#include "Algorithms.h"
#include "StringLiterals.h"
#include "XmlWriter.h"

#include <QBuffer>
#include <QByteArray>
#include <QCryptographicHash>
#include <QDateTime>
#include <QDebug>
#include <QDomElement>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QStringList>
#include <QUrl>
#include <QUuid>
#include <QXmlStreamWriter>

using namespace QXmpp::Private;

// adapted from public domain source by Ross Williams and Eric Durbin
// FIXME : is this valid for big-endian machines?
static quint32 crctable[256] = {
    0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL,
    0x076DC419L, 0x706AF48FL, 0xE963A535L, 0x9E6495A3L,
    0x0EDB8832L, 0x79DCB8A4L, 0xE0D5E91EL, 0x97D2D988L,
    0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, 0x90BF1D91L,
    0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL,
    0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L,
    0x136C9856L, 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL,
    0x14015C4FL, 0x63066CD9L, 0xFA0F3D63L, 0x8D080DF5L,
    0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, 0xA2677172L,
    0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL,
    0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L,
    0x32D86CE3L, 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L,
    0x26D930ACL, 0x51DE003AL, 0xC8D75180L, 0xBFD06116L,
    0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, 0xB8BDA50FL,
    0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L,
    0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL,
    0x76DC4190L, 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL,
    0x71B18589L, 0x06B6B51FL, 0x9FBFE4A5L, 0xE8B8D433L,
    0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, 0xE10E9818L,
    0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L,
    0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL,
    0x6C0695EDL, 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L,
    0x65B0D9C6L, 0x12B7E950L, 0x8BBEB8EAL, 0xFCB9887CL,
    0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, 0xFBD44C65L,
    0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L,
    0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL,
    0x4369E96AL, 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L,
    0x44042D73L, 0x33031DE5L, 0xAA0A4C5FL, 0xDD0D7CC9L,
    0x5005713CL, 0x270241AAL, 0xBE0B1010L, 0xC90C2086L,
    0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL,
    0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L,
    0x59B33D17L, 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL,
    0xEDB88320L, 0x9ABFB3B6L, 0x03B6E20CL, 0x74B1D29AL,
    0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, 0x73DC1683L,
    0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L,
    0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L,
    0xF00F9344L, 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL,
    0xF762575DL, 0x806567CBL, 0x196C3671L, 0x6E6B06E7L,
    0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, 0x67DD4ACCL,
    0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L,
    0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L,
    0xD1BB67F1L, 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL,
    0xD80D2BDAL, 0xAF0A1B4CL, 0x36034AF6L, 0x41047A60L,
    0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, 0x4669BE79L,
    0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L,
    0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL,
    0xC5BA3BBEL, 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L,
    0xC2D7FFA7L, 0xB5D0CF31L, 0x2CD99E8BL, 0x5BDEAE1DL,
    0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, 0x026D930AL,
    0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L,
    0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L,
    0x92D28E9BL, 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L,
    0x86D3D2D4L, 0xF1D4E242L, 0x68DDB3F8L, 0x1FDA836EL,
    0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, 0x18B74777L,
    0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL,
    0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L,
    0xA00AE278L, 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L,
    0xA7672661L, 0xD06016F7L, 0x4969474DL, 0x3E6E77DBL,
    0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, 0x37D83BF0L,
    0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L,
    0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L,
    0xBAD03605L, 0xCDD70693L, 0x54DE5729L, 0x23D967BFL,
    0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L,
    0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL
};

QAtomicInt QXmpp::Private::globalStanzaIdCounter = 0;

/*! Parses a date-time from the string \a str according to \xep{0082}{XMPP Date and Time Profiles}.

    Takes QStringView since QXmpp 1.8. Returns true on success. */
QDateTime QXmppUtils::datetimeFromString(QStringView str)
{
    // Qt::ISODate parses milliseconds, but doesn't output them
    return QDateTime::fromString(toString60(str), Qt::ISODate).toUTC();
}

/*!
    Serializes a date-time to a string according to \xep{0082}{XMPP Date and Time Profiles}.

    \a dt.
*/
QString QXmppUtils::datetimeToString(const QDateTime &dt)
{
    if (!dt.isValid()) {
        return {};
    }
    if (dt.time().msec()) {
        return dt.toUTC().toString(Qt::ISODateWithMs);
    }
    return dt.toUTC().toString(Qt::ISODate);
}

/*! Parses a timezone offset (in seconds) from the string \a str according to
    \xep{0082}{XMPP Date and Time Profiles}. Returns true on success. */
int QXmppUtils::timezoneOffsetFromString(const QString &str)
{
    static const QRegularExpression timezoneRegex(u"(Z|([+-])([0-9]{2}):([0-9]{2}))"_s);

    const auto match = timezoneRegex.match(str);
    if (!match.hasMatch()) {
        return 0;
    }

    // No offset from UTC
    if (match.captured(1) == QChar(u'Z')) {
        return 0;
    }

    // Calculate offset
    const int offset = match.captured(3).toInt() * 3600 +
        match.captured(4).toInt() * 60;

    if (match.captured(2) == QChar(u'-')) {
        return -offset;
    }
    return offset;
}

/*!
    Serializes a timezone offset (in seconds) to a string according to
    \xep{0082}{XMPP Date and Time Profiles}.

    \a secs.
*/
QString QXmppUtils::timezoneOffsetToString(int secs)
{
    if (!secs) {
        return u"Z"_s;
    }

    const QTime tzoTime = QTime(0, 0, 0).addSecs(qAbs(secs));
    return (secs < 0 ? u"-"_s : u"+"_s) + tzoTime.toString(u"hh:mm"_s);
}

/*! Returns the domain for the given \a jid. */
QString QXmppUtils::jidToDomain(const QString &jid)
{
    return jidToBareJid(jid).split(u"@"_s).last();
}

/*! Returns the resource for the given \a jid. */
QString QXmppUtils::jidToResource(const QString &jid)
{
    const int pos = jid.indexOf(u'/');
    if (pos < 0) {
        return QString();
    }
    return jid.mid(pos + 1);
}

/*! Returns the user for the given \a jid. */
QString QXmppUtils::jidToUser(const QString &jid)
{
    const int pos = jid.indexOf(u'@');
    if (pos < 0) {
        return QString();
    }
    return jid.left(pos);
}

/*! Returns the bare jid (i.e. without resource) for the given \a jid. */
QString QXmppUtils::jidToBareJid(const QString &jid)
{
    const int pos = jid.indexOf(u'/');
    if (pos < 0) {
        return jid;
    }
    return jid.left(pos);
}

/*!
    Calculates the CRC32 checksum for the given input.

    \a in.
*/
quint32 QXmppUtils::generateCrc32(const QByteArray &in)
{
    quint32 result = 0xffffffff;
    for (char n : in) {
        result = (result >> 8) ^ (crctable[(result & 0xff) ^ (quint8)n]);
    }
    return result ^= 0xffffffff;
}

static QByteArray generateHmac(QCryptographicHash::Algorithm algorithm, const QByteArray &key, const QByteArray &text)
{
    QCryptographicHash hasher(algorithm);

    const int B = 64;
    QByteArray kpad = key + QByteArray(B - key.size(), 0);

    QByteArray ba;
    for (int i = 0; i < B; ++i) {
        ba += kpad[i] ^ 0x5c;
    }

    QByteArray tmp;
    for (int i = 0; i < B; ++i) {
        tmp += kpad[i] ^ 0x36;
    }
    hasher.addData(tmp);
    hasher.addData(text);
    ba += hasher.result();

    hasher.reset();
    hasher.addData(ba);
    return hasher.result();
}

/*! Generates the MD5 HMAC for the given \a key and \a text. */

QByteArray QXmppUtils::generateHmacMd5(const QByteArray &key, const QByteArray &text)
{
    return generateHmac(QCryptographicHash::Md5, key, text);
}

/*! Generates the SHA1 HMAC for the given \a key and \a text. */

QByteArray QXmppUtils::generateHmacSha1(const QByteArray &key, const QByteArray &text)
{
    return generateHmac(QCryptographicHash::Sha1, key, text);
}

/*!
    Generates a random integer x between 0 and \a N - 1.
*/

int QXmppUtils::generateRandomInteger(int N)
{
    Q_ASSERT(N > 0 && N <= RAND_MAX);
    int val;
    while (N <= (val = QRandomGenerator::global()->generate() / (RAND_MAX / N))) {
    }
    return val;
}

/*!
    Returns a random byte array of the size specified by \a length.
*/

QByteArray QXmppUtils::generateRandomBytes(int length)
{
    QByteArray bytes(length, 'm');
    for (int i = 0; i < length; ++i) {
        bytes[i] = (char)generateRandomInteger(256);
    }
    return bytes;
}

/*!
    Creates a new stanza id in the UUID format.

    \since QXmpp 1.3
*/
QString QXmppUtils::generateStanzaUuid()
{
    return QUuid::createUuid().toString(QUuid::WithoutBraces);
}

/*!
    Returns a random alphanumerical string of the specified size.

    Since QXmpp 1.3 this will generate a UUID, if the specified \a length is 36
    which is also the new default value. The returned string is still 36
    characters long, but will contain dashes (as specified in the UUID format).

    \note It is recommended to use UUIDs for cases where IDs must be unique and
    are possibly stored permanently. This can be done using
    QXmppUtils::generateStanzaUuid(). However, since that function is only
    available since QXmpp 1.3, you may also want to continue to use this
    function because of compatibility reasons.
*/
QString QXmppUtils::generateStanzaHash(int length)
{
    if (length == 36) {
        return QXmppUtils::generateStanzaUuid();
    }

    const QString somechars = u"1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"_s;
    const int N = somechars.size();
    QString hashResult;
    for (int idx = 0; idx < length; ++idx) {
        hashResult += somechars[generateRandomInteger(N)];
    }
    return hashResult;
}

std::tuple<QString, QString> QXmpp::Private::elementXmlTag(const QDomElement &el)
{
    return { el.tagName(), el.namespaceURI() };
}

std::tuple<QString, QString> QXmpp::Private::iqPayloadXmlTag(const QDomElement &el)
{
    if (el.tagName() == u"iq") {
        return elementXmlTag(el.firstChildElement());
    }
    return { {}, {} };
}

/*!
    Generates a new sequential stanza id. The ID is locally unique, not globally unique (see
    QXmppUtils::generateStanzaUuid()).

    This is used for all QXmppStanzas automatically.

    \since QXmpp 1.11
*/
QString QXmpp::generateSequentialStanzaId()
{
    ++globalStanzaIdCounter;
    return u"qx" + QString::number(globalStanzaIdCounter);
}

std::optional<QByteArray> QXmpp::Private::parseBase64(const QString &text)
{
    if (auto result = QByteArray::fromBase64Encoding(text.toUtf8())) {
        return *result;
    }
    return {};
}

template<typename T>
concept SupportedNumber =
    std::same_as<T, int8_t> || std::same_as<T, uint8_t> ||
    std::same_as<T, int16_t> || std::same_as<T, uint16_t> ||
    std::same_as<T, int32_t> || std::same_as<T, uint32_t> ||
    std::same_as<T, int64_t> || std::same_as<T, uint64_t> ||
    std::same_as<T, float> || std::same_as<T, double>;

template<typename Int>
Int stringToInt(QStringView str, bool *ok)
{
    static_assert(SupportedNumber<Int>, "invalid integer type");

    if constexpr (std::is_same_v<Int, int8_t>) {
        auto result = str.toShort(ok);
        if (*ok && result <= std::numeric_limits<int8_t>::max() && result >= std::numeric_limits<int8_t>::min()) {
            return Int(result);
        }
        *ok = false;
        return 0;
    } else if constexpr (std::is_same_v<Int, uint8_t>) {
        auto result = str.toUShort(ok);
        if (*ok && result <= std::numeric_limits<int8_t>::max()) {
            return Int(result);
        }
        *ok = false;
        return 0;
    } else if constexpr (std::is_same_v<Int, int16_t>) {
        return str.toShort(ok);
    } else if constexpr (std::is_same_v<Int, uint16_t>) {
        return str.toUShort(ok);
    } else if constexpr (std::is_same_v<Int, int32_t>) {
        return str.toInt(ok);
    } else if constexpr (std::is_same_v<Int, uint32_t>) {
        return str.toUInt(ok);
    } else if constexpr (std::is_same_v<Int, int64_t>) {
        return str.toLongLong(ok);
    } else if constexpr (std::is_same_v<Int, uint64_t>) {
        return str.toULongLong(ok);
    } else if constexpr (std::is_same_v<Int, double>) {
        return str.toDouble(ok);
    } else if constexpr (std::is_same_v<Int, float>) {
        return str.toFloat(ok);
    }
}

template<typename Int>
std::optional<Int> QXmpp::Private::parseInt(QStringView str)
{
    bool ok = false;
    if (auto result = stringToInt<Int>(str, &ok); ok) {
        return result;
    }
    return {};
}

template std::optional<int8_t> QXmpp::Private::parseInt<int8_t>(QStringView);
template std::optional<uint8_t> QXmpp::Private::parseInt<uint8_t>(QStringView);
template std::optional<int16_t> QXmpp::Private::parseInt<int16_t>(QStringView);
template std::optional<uint16_t> QXmpp::Private::parseInt<uint16_t>(QStringView);
template std::optional<int32_t> QXmpp::Private::parseInt<int32_t>(QStringView);
template std::optional<uint32_t> QXmpp::Private::parseInt<uint32_t>(QStringView);
template std::optional<int64_t> QXmpp::Private::parseInt<int64_t>(QStringView);
template std::optional<uint64_t> QXmpp::Private::parseInt<uint64_t>(QStringView);

std::optional<double> QXmpp::Private::parseDouble(QStringView str)
{
    return parseInt<double>(str);
}

std::optional<float> QXmpp::Private::parseFloat(QStringView str)
{
    return parseInt<float>(str);
}

std::optional<bool> QXmpp::Private::parseBoolean(const QString &str)
{
    if (str == u"1" || str == u"true") {
        return true;
    }
    if (str == u"0" || str == u"false") {
        return false;
    }
    return {};
}

QString QXmpp::Private::serializeBoolean(bool value)
{
    return value ? QStringLiteral("true") : QStringLiteral("false");
}

QDomElement QXmpp::Private::firstChildElement(const QDomElement &el, QStringView tagName, QStringView xmlNs)
{
    for (auto child = el.firstChild(); !child.isNull(); child = child.nextSibling()) {
        if (child.isElement() && (xmlNs.isEmpty() || child.namespaceURI() == xmlNs)) {
            auto elt = child.toElement();
            if (tagName.isEmpty() || elt.tagName() == tagName) {
                return elt;
            }
        }
    }
    return {};
}

QDomElement QXmpp::Private::nextSiblingElement(const QDomElement &el, QStringView tagName, QStringView xmlNs)
{
    for (auto sib = el.nextSibling(); !sib.isNull(); sib = sib.nextSibling()) {
        if (sib.isElement() && (xmlNs.isEmpty() || sib.namespaceURI() == xmlNs)) {
            auto elt = sib.toElement();
            if (tagName.isEmpty() || elt.tagName() == tagName) {
                return elt;
            }
        }
    }
    return {};
}

QByteArray QXmpp::Private::serializeXmlWriter(std::function<void(XmlWriter &)> toXml)
{
    QByteArray data;
    QXmlStreamWriter xmlStream(&data);
    XmlWriter writer(&xmlStream);
    toXml(writer);
    return data;
}

QByteArray QXmpp::Private::serializeQXmlStream(std::function<void(QXmlStreamWriter *)> toXml)
{
    QByteArray data;
    QXmlStreamWriter xmlStream(&data);
    toXml(&xmlStream);
    return data;
}

//
// Generates a random count of random bytes between \a minimumByteCount and
// \a maximumByteCount.
//
// Returns the generated bytes.
//
QByteArray QXmpp::Private::generateRandomBytes(size_t minimumByteCount, size_t maximumByteCount)
{
    const auto byteCount = QRandomGenerator::system()->bounded(quint64(minimumByteCount), quint64(maximumByteCount));
    QByteArray bytes;
    bytes.resize(byteCount);
    generateRandomBytes(reinterpret_cast<uint8_t *>(bytes.data()), byteCount);

    return bytes;
}

//
// Generates \a byteCount random bytes into \a bytes.
//
void QXmpp::Private::generateRandomBytes(uint8_t *bytes, size_t byteCount)
{
    // QRandomGenerator does not provide a random generation of single bytes. Thus, this approach
    // fills an array with unsigned integers until no additional integer fits into the array. If
    // byteCount is no multiple of intSize, the remaining bytes need to be filled separately.
    constexpr auto intSize = sizeof(uint32_t);
    auto intCount = byteCount / intSize;

    auto *randomGenerator = QRandomGenerator::system();

    // Fill the space with intCount unsigned integers.
    if (intCount) {
        randomGenerator->fillRange(reinterpret_cast<uint32_t *>(bytes), intCount);
    }

    // Fill the remaining space with single bytes.
    for (size_t i = byteCount - byteCount % intSize; i < byteCount; i++) {
        bytes[i] = randomGenerator->bounded(std::numeric_limits<uint8_t>::max() + 1);
    }
}

float QXmpp::Private::calculateProgress(qint64 transferred, qint64 total)
{
    if (total > 0) {
        if (transferred > total) {
            return 1;
        }
        return float(transferred) / total;
    }

    return 0;
}

std::pair<QString, int> QXmpp::Private::parseHostAddress(const QString &address)
{
    QUrl url(u"//" + address);
    if (url.isValid() && !url.host().isEmpty()) {
        return { url.host(), url.port() };
    }
    return { {}, -1 };
}
