// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include <QtProtobuf/qprotobufjsonserializer.h>

#include <QtProtobuf/private/protobuffieldpresencechecker_p.h>
#include <QtProtobuf/private/protobufscalarjsonserializers_p.h>
#include <QtProtobuf/private/qprotobufdeserializerbase_p.h>
#include <QtProtobuf/private/qprotobufregistration_p.h>
#include <QtProtobuf/private/qprotobufserializerbase_p.h>
#include <QtProtobuf/private/qtprotobufdefs_p.h>
#include <QtProtobuf/private/qtprotobufserializerhelpers_p.h>

#include <QtCore/qcoreapplication.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qhash.h>
#include <QtCore/qjsonarray.h>
#include <QtCore/qjsondocument.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/qtimezone.h>
#include <QtCore/qvariant.h>

#include <cmath>

QT_BEGIN_NAMESPACE

/*!
    \class QProtobufJsonSerializer
    \inmodule QtProtobuf
    \since 6.7
    \brief The QProtobufJsonSerializer class is an interface that represents
           basic functions for serialization/deserialization of QProtobufMessage
           objects to JSON.

    The QProtobufJsonSerializer class registers serializers/deserializers for
    classes implementing a protobuf message, inheriting \l QProtobufMessage. These
    classes are generated automatically, based on a \c{.proto} file, using the CMake
    function \l qt_add_protobuf or by running
    \l {The qtprotobufgen Tool} {qtprotobufgen} directly.
*/

using namespace Qt::StringLiterals;
using namespace QtProtobufPrivate;
using namespace ProtobufScalarJsonSerializers;

namespace {

inline QString convertJsonKeyToJsonName(QStringView name)
{
    QString result;
    result.reserve(name.size());
    bool nextUpperCase = false;
    for (const auto &c : name) {
        if (c == QChar::fromLatin1('_')) {
            nextUpperCase = true;
            continue;
        }
        result.append(nextUpperCase ? c.toUpper() : c);
        nextUpperCase = false;
    }
    return result;
}

}

class QProtobufJsonSerializerImpl final : public QProtobufSerializerBase
{
public:
    QProtobufJsonSerializerImpl() = default;
    ~QProtobufJsonSerializerImpl();

    const QJsonObject &result() const { return m_result; }

    void reset();

protected:
    void serializeMessageField(const QProtobufMessage *message,
                               const QtProtobufPrivate::QProtobufFieldInfo &fieldInfo) override;

private:
    bool serializeEnum(QVariant &value, const QProtobufFieldInfo &fieldInfo) override;
    bool serializeScalarField(const QVariant &value, const QProtobufFieldInfo &fieldInfo) override;
    void serializeMessageFieldBegin() override;
    void serializeMessageFieldEnd(const QProtobufMessage *message,
                                  const QProtobufFieldInfo &fieldInfo) override;

    void serializeTimestamp(const QProtobufMessage *message,
                            const QtProtobufPrivate::QProtobufFieldInfo &fieldInfo);

    QJsonObject m_result;
    QList<QJsonObject> m_state;

    Q_DISABLE_COPY_MOVE(QProtobufJsonSerializerImpl)
};

class QProtobufJsonSerializerPrivate;
class QProtobufJsonDeserializerImpl final : public QProtobufDeserializerBase
{
public:
    explicit QProtobufJsonDeserializerImpl(QProtobufJsonSerializerPrivate *parent);
    ~QProtobufJsonDeserializerImpl();

    void reset(QJsonObject obj);

    void setError(QAbstractProtobufSerializer::Error error, QAnyStringView errorString) override;
    void setUnexpectedEndOfStreamError();
    void setInvalidFormatError();

protected:
    bool deserializeMessageField(QProtobufMessage *message) override;

private:
    bool deserializeEnum(QVariant &value,
                         const QtProtobufPrivate::QProtobufFieldInfo &fieldInfo) override;
    int nextFieldIndex(QProtobufMessage *message) override;
    bool deserializeScalarField(QVariant &, const QtProtobufPrivate::QProtobufFieldInfo &) override;

    [[nodiscard]] bool deserializeTimestamp(QProtobufMessage *message);

    struct JsonDeserializerState
    {
        JsonDeserializerState(const QJsonObject &obj) : obj(obj) { }

        QJsonObject obj = {};
        int index = 0;
        QJsonValue scalarValue = {};
    };

    QList<JsonDeserializerState> m_state;
    QProtobufJsonSerializerPrivate *m_parent = nullptr;
};

class QProtobufJsonSerializerPrivate final
{
    Q_DISABLE_COPY_MOVE(QProtobufJsonSerializerPrivate)
public:
    using Serializer = std::function<QJsonValue(const QVariant &)>;
    using Deserializer = std::function<QVariant(const QJsonValue&, bool &ok)>;

    struct SerializationHandlers {
        // serializer assigned to class
        Serializer serializer;
        // deserializer assigned to class
        Deserializer deserializer;
        ProtobufFieldPresenceChecker::Function isPresent;
    };

    template <typename T>
    static SerializationHandlers createCommonHandler()
    {
        return { serializeCommon<T>, deserializeCommon<T>,
                 ProtobufFieldPresenceChecker::isPresent<T> };
    }

    template <typename L, typename T>
    static SerializationHandlers createCommonListHandler()
    {
        return { serializeList<L>, deserializeList<L, T>,
                 ProtobufFieldPresenceChecker::isPresent<L> };
    }

    template<typename T, if_json_floating_point<T> = true>
    static bool isPresent(const QVariant &value)
    {
        const T val = value.value<T>();
        return val != 0 || std::signbit(val);
    }

    using SerializerRegistry = QHash<int/*metatypeid*/, SerializationHandlers>;

    QProtobufJsonSerializerPrivate();
    ~QProtobufJsonSerializerPrivate() = default;

    void clearError();

    QAbstractProtobufSerializer::Error lastError = QAbstractProtobufSerializer::Error::None;
    QString lastErrorString;

    static SerializerRegistry handlers;

    QProtobufJsonSerializerImpl serializer;
    QProtobufJsonDeserializerImpl deserializer;
};

QProtobufJsonSerializerImpl::~QProtobufJsonSerializerImpl() = default;

void QProtobufJsonSerializerImpl::reset()
{
    m_result = {};
    m_state.clear();
}

void QProtobufJsonSerializerImpl::serializeMessageField(const QProtobufMessage *message,
                                                        const QtProtobufPrivate::QProtobufFieldInfo
                                                            &fieldInfo)
{
    if (message->propertyOrdering()
            ->messageFullName()
            .compare(QString::fromUtf8("google.protobuf.Timestamp"))
        == 0) {
        serializeTimestamp(message, fieldInfo);
    } else {
        QProtobufSerializerBase::serializeMessageField(message, fieldInfo);
    }
}

void QProtobufJsonSerializerImpl::serializeTimestamp(const QProtobufMessage *message,
                                                     const QtProtobufPrivate::QProtobufFieldInfo
                                                         &fieldInfo)
{
    qint64 secs = 0;
    qint32 nanos = 0;

    if (const auto secondsValue = message->property("seconds"); secondsValue.canConvert<qint64>()) {
        secs = secondsValue.value<qint64>();
    } else {
        qWarning() << "QProtobufJsonSerializerImpl::serializeTimestamp() failed to convert seconds";
    }

    if (const auto nanosValue = message->property("nanos"); nanosValue.canConvert<qint32>()) {
        nanos = nanosValue.value<qint32>();
    } else {
        qWarning() << "QProtobufJsonSerializerImpl::serializeTimestamp() failed to convert nanos";
    }

    const auto datetime = QDateTime::fromMSecsSinceEpoch(secs * 1000 + nanos / 1000000,
                                                         QTimeZone::UTC);
    const auto datetimeISO = datetime.toString(Qt::ISODateWithMs);

    const auto jsonName = fieldInfo.jsonName();
    m_result.insert(jsonName.toString(), serializeCommon<QString>(datetimeISO));
}

bool QProtobufJsonSerializerImpl::serializeEnum(QVariant &value,
                                                const QProtobufFieldInfo &fieldInfo)

{
    const auto jsonName = fieldInfo.jsonName();
    if (fieldInfo.fieldFlags().testFlag(QtProtobufPrivate::FieldFlag::Repeated)) {
        if (!value.convert(QMetaType::fromType<QStringList>()))
            return false;
        if (!ProtobufFieldPresenceChecker::isPresent<QStringList>(value))
            return true;
        m_result.insert(jsonName.toString(), serializeList<QStringList>(value));
    } else {
        if (!value.convert(QMetaType::fromType<QString>()))
            return false;
        if (!ProtobufFieldPresenceChecker::isPresent<QString>(value)
            && !isOneofOrOptionalField(fieldInfo.fieldFlags())) {
            return true;
        }
        m_result.insert(jsonName.toString(), serializeCommon<QString>(value));
    }
    return true;
}

bool QProtobufJsonSerializerImpl::serializeScalarField(const QVariant &value,
                                                       const QProtobufFieldInfo &fieldInfo)
{
    const auto it = QProtobufJsonSerializerPrivate::handlers.constFind(value.userType());
    // Is not a protobuf scalar value type
    if (it == QProtobufJsonSerializerPrivate::handlers.cend())
        return false;

    // Field is empty
    if (!it->isPresent(value) && !isOneofOrOptionalField(fieldInfo.fieldFlags()))
        return true;

    // If serializer is not defined we should use the standard QJsonValue cast from variant
    m_result.insert(fieldInfo.jsonName().toString(),
                    it->serializer ? it->serializer(value) : QJsonValue::fromVariant(value));
    return true;
}

void QProtobufJsonSerializerImpl::serializeMessageFieldBegin()
{
    m_state.emplaceBack(std::move(m_result));
    m_result = {};
}

void QProtobufJsonSerializerImpl::serializeMessageFieldEnd(const QProtobufMessage *message,
                                                           const QProtobufFieldInfo &field)
{
    QJsonObject store = m_state.takeLast();
    QString fieldName = field.jsonName().toString();
    if (field.fieldFlags().testFlag(QtProtobufPrivate::FieldFlag::Repeated)) {
        // Repeated fields are stored in array property
        // "repeatedField": [{...}, {...}, ...]
        QJsonArray array = store.value(fieldName).toArray();
        array.append(m_result);
        store.insert(fieldName, array);
    } else if (field.fieldFlags().testFlag(QtProtobufPrivate::FieldFlag::Map)) {
        // Maps are stored as fields in inner JSON object
        // "mapField": { "key1": "value1", ... }
        QJsonObject mapObject = store.value(fieldName).toObject();
        // We don't need to serialize key in general, since all JSON object fields should be
        // represented as strings, and types that can be map keys have valid string converters.
        mapObject.insert(message->property("key").toString(), m_result.value("value"_L1));
        store.insert(fieldName, mapObject);
    } else {
        store.insert(fieldName, m_result);
    }
    m_result = store;
}

QProtobufJsonDeserializerImpl::QProtobufJsonDeserializerImpl(QProtobufJsonSerializerPrivate *parent)
    : m_parent(parent)
{
}

QProtobufJsonDeserializerImpl::~QProtobufJsonDeserializerImpl()
    = default;

void QProtobufJsonDeserializerImpl::reset(QJsonObject obj)
{
    clearCachedValue();
    m_state.clear();
    if (!obj.isEmpty())
        m_state.push_back({ obj });
}

void QProtobufJsonDeserializerImpl::setError(QAbstractProtobufSerializer::Error error,
                                             QAnyStringView errorString)
{
    m_parent->lastError = error;
    m_parent->lastErrorString = errorString.toString();
}

bool QProtobufJsonDeserializerImpl::deserializeMessageField(QProtobufMessage *message)
{
    if (!m_state.last().scalarValue.isNull()) {
        if (message->propertyOrdering()
                    ->messageFullName()
                    .compare(QString::fromUtf8("google.protobuf.Timestamp"))
                == 0
            && m_state.last().scalarValue.isString()) {
            return deserializeTimestamp(message);
        }
        setInvalidFormatError();
        return false;
    }
    return QProtobufDeserializerBase::deserializeMessageField(message);
}

bool QProtobufJsonDeserializerImpl::deserializeTimestamp(QProtobufMessage *message)
{
    const auto tsString = m_state.last().scalarValue.toString();
    // Protobuf requires upper-case letters in timestamp string be case sensitive.
    if (tsString.toUpper() != tsString)
        return false;

    if (tsString.contains(u' '))
        return false;

    //Ensure the field either ends with Z or a valid offset
    static const QRegularExpression TimeStampEnding(".+([\\+\\-]\\d{2}:\\d{2}|Z)$"_L1);
    if (!TimeStampEnding.match(tsString).hasMatch())
        return false;

    const auto datetime = QDateTime::fromString(tsString, Qt::ISODateWithMs);

    if (!datetime.isValid()) {
        qWarning() << "QProtobufJsonDeserializerImpl::deserializeTimestamp() datetime is invalid";
        return false;
    }
    const auto msecs = datetime.toMSecsSinceEpoch();
    const qint64 seconds = msecs / 1000;
    const qint32 nanos = (msecs % 1000) * 1000000;

    message->setProperty("seconds", QVariant::fromValue(seconds));
    message->setProperty("nanos", QVariant::fromValue(nanos));

    return true;
}

bool QProtobufJsonDeserializerImpl::deserializeEnum(QVariant &value,
                                                    const QtProtobufPrivate::QProtobufFieldInfo
                                                        &fieldInfo)
{
    bool ok = false;
    auto &state = m_state.last();
    if (fieldInfo.fieldFlags().testFlag(QtProtobufPrivate::FieldFlag::Repeated)) {
        value = deserializeList<QStringList, QString>(state.scalarValue, ok);
    } else {
        // It's allowed to pass single enum value as numeric value.
        // Make the backward value conversion and deserialize enum as QtProtobuf::int64.
        if (state.scalarValue.isString()) {
            value = deserializeCommon<QString>(state.scalarValue, ok);
        } else {
            value = deserializeCommon<QtProtobuf::int64>(state.scalarValue, ok);
        }
    }

    return ok;
}

int QProtobufJsonDeserializerImpl::nextFieldIndex(QProtobufMessage *message)
{
    const auto *ordering = message->propertyOrdering();
    const int fieldCount = ordering->fieldCount();
    if (fieldCount == 0)
        return -1;

    JsonDeserializerState &state = m_state.last();
    state.scalarValue = {};
    while (state.index < fieldCount) {
        const auto jsonName = ordering->jsonName(state.index);
        const auto keys = state.obj.keys();
        const auto it = std::find_if(keys.constBegin(), keys.constEnd(),
                                     [&jsonName](const auto &val) {
                                         return jsonName == val
                                             || jsonName == convertJsonKeyToJsonName(val);
                                     });

        if (it == keys.constEnd()) {
            ++state.index;
            continue;
        }

        QtProtobufPrivate::FieldFlags flags = ordering->fieldFlags(state.index);
        QJsonValue val = state.obj.value(*it);
        if (val.isNull()) {
            ++state.index;
            continue;
        }

        int index = state.index;
        if (flags.testFlags({ QtProtobufPrivate::FieldFlag::Message,
                              QtProtobufPrivate::FieldFlag::Repeated })) {
            if (!val.isArray()) {
                setInvalidFormatError();
                return -1;
            }

            auto array = val.toArray();
            if (array.isEmpty()) {
                ++state.index;
                continue;
            }

            if (!array.at(0).isObject()) {
                setInvalidFormatError();
                return -1;
            }

            auto nextObject = array.takeAt(0).toObject();
            state.obj.insert(*it, array);
            m_state.push_back({ nextObject });
        } else if (flags.testFlag(QtProtobufPrivate::FieldFlag::Map)) {
            if (!val.isObject()) {
                setInvalidFormatError();
                return -1;
            }

            auto mapObject = val.toObject();
            if (mapObject.isEmpty()) {
                ++state.index;
                continue;
            }

            QString key = mapObject.begin().key();
            QJsonObject nextObject;
            nextObject.insert("key"_L1, key);
            nextObject.insert("value"_L1, mapObject.take(key));
            state.obj.insert(*it, mapObject);
            m_state.push_back({ nextObject });
        } else if (flags.testFlag(QtProtobufPrivate::FieldFlag::Message)) {
            if (val.isArray() || val.isUndefined() || val.isNull()) {
                setInvalidFormatError();
                return -1;
            }

            if (val.isObject()) {
                ++state.index;
                m_state.push_back({ val.toObject() });
            } else {
                state.scalarValue = val;
                ++state.index;
            }
        } else {
            state.scalarValue = val;
            ++state.index;
        }
        return index;
    }

    m_state.pop_back();
    return -1;
}

bool QProtobufJsonDeserializerImpl::deserializeScalarField(QVariant &value,
                                                           const QProtobufFieldInfo &)
{
    auto handler = QProtobufJsonSerializerPrivate::handlers.constFind(value.userType());
    if (handler == QProtobufJsonSerializerPrivate::handlers.constEnd()
        || !handler.value().deserializer) {
        return false;
    }

    bool ok = false;
    value = handler.value().deserializer(m_state.last().scalarValue, ok);
    if (!ok)
        setInvalidFormatError();
    return true;
}

void QProtobufJsonDeserializerImpl::setUnexpectedEndOfStreamError()
{
    setError(QAbstractProtobufSerializer::Error::UnexpectedEndOfStream,
             QCoreApplication::translate("QtProtobuf", "JSON: Unexpected end of stream"));
}

void QProtobufJsonDeserializerImpl::setInvalidFormatError()
{
    setError(QAbstractProtobufSerializer::Error::InvalidFormat,
             QCoreApplication::translate("QtProtobuf",
                                         "JSON: One or more fields have invalid format"));
}

QProtobufJsonSerializerPrivate::SerializerRegistry QProtobufJsonSerializerPrivate::handlers = {};

QProtobufJsonSerializerPrivate::QProtobufJsonSerializerPrivate() : deserializer(this)
{
    [[maybe_unused]] static bool initialized = []() -> bool {
        handlers[qMetaTypeId<QtProtobuf::int32>()] = createCommonHandler<QtProtobuf::int32>();
        handlers[qMetaTypeId<QtProtobuf::sfixed32>()] = createCommonHandler<QtProtobuf::sfixed32>();
        handlers[qMetaTypeId<QtProtobuf::sint32>()] = createCommonHandler<QtProtobuf::sint32>();
        handlers[qMetaTypeId<QtProtobuf::uint32>()] = createCommonHandler<QtProtobuf::uint32>();
        handlers[qMetaTypeId<QtProtobuf::fixed32>()] = createCommonHandler<QtProtobuf::fixed32>();
        handlers[qMetaTypeId<QtProtobuf::sint64>()] = createCommonHandler<QtProtobuf::sint64>();
        handlers[qMetaTypeId<QtProtobuf::int64>()] = createCommonHandler<QtProtobuf::int64>();
        handlers[qMetaTypeId<QtProtobuf::sfixed64>()] = createCommonHandler<QtProtobuf::sfixed64>();
        handlers[qMetaTypeId<QtProtobuf::uint64>()] = createCommonHandler<QtProtobuf::uint64>();
        handlers[qMetaTypeId<QtProtobuf::fixed64>()] = createCommonHandler<QtProtobuf::fixed64>();
        handlers[qMetaTypeId<bool>()] = createCommonHandler<bool>();
        handlers[QMetaType::QString] = createCommonHandler<QString>();
        handlers[QMetaType::QByteArray] = createCommonHandler<QByteArray>();
        handlers[QMetaType::Float] = { serializeCommon<float>, deserializeCommon<float>,
                                       QProtobufJsonSerializerPrivate::isPresent<float> };
        handlers[QMetaType::Double] = { serializeCommon<double>, deserializeCommon<double>,
                                        isPresent<double> };

        handlers[qMetaTypeId<QtProtobuf::boolList>()] = createCommonListHandler<
            QtProtobuf::boolList, bool>();
        handlers[qMetaTypeId<QtProtobuf::int32List>()] = createCommonListHandler<
            QtProtobuf::int32List, QtProtobuf::int32>();
        handlers[qMetaTypeId<QtProtobuf::int64List>()] = createCommonListHandler<
            QtProtobuf::int64List, QtProtobuf::int64>();
        handlers[qMetaTypeId<QtProtobuf::sint32List>()] = createCommonListHandler<
            QtProtobuf::sint32List, QtProtobuf::sint32>();
        handlers[qMetaTypeId<QtProtobuf::sint64List>()] = createCommonListHandler<
            QtProtobuf::sint64List, QtProtobuf::sint64>();
        handlers[qMetaTypeId<QtProtobuf::uint32List>()] = createCommonListHandler<
            QtProtobuf::uint32List, QtProtobuf::uint32>();
        handlers[qMetaTypeId<QtProtobuf::uint64List>()] = createCommonListHandler<
            QtProtobuf::uint64List, QtProtobuf::uint64>();
        handlers[qMetaTypeId<QtProtobuf::fixed32List>()] = createCommonListHandler<
            QtProtobuf::fixed32List, QtProtobuf::fixed32>();
        handlers[qMetaTypeId<QtProtobuf::fixed64List>()] = createCommonListHandler<
            QtProtobuf::fixed64List, QtProtobuf::fixed64>();
        handlers[qMetaTypeId<QtProtobuf::sfixed32List>()] = createCommonListHandler<
            QtProtobuf::sfixed32List, QtProtobuf::sfixed32>();
        handlers[qMetaTypeId<QtProtobuf::sfixed64List>()] = createCommonListHandler<
            QtProtobuf::sfixed64List, QtProtobuf::sfixed64>();
        handlers[qMetaTypeId<QtProtobuf::floatList>()] = createCommonListHandler<
            QtProtobuf::floatList, float>();
        handlers[qMetaTypeId<QtProtobuf::doubleList>()] = createCommonListHandler<
            QtProtobuf::doubleList, double>();
        handlers[qMetaTypeId<QStringList>()] = createCommonListHandler<QStringList, QString>();
        handlers[qMetaTypeId<QByteArrayList>()] = createCommonListHandler<QByteArrayList,
                                                                          QByteArray>();
        return true;
    }();
}

void QProtobufJsonSerializerPrivate::clearError()
{
    lastError = QAbstractProtobufSerializer::Error::None;
    lastErrorString.clear();
}

QProtobufJsonSerializer::QProtobufJsonSerializer() : d_ptr(new QProtobufJsonSerializerPrivate)
{
}

QProtobufJsonSerializer::~QProtobufJsonSerializer() = default;

/*!
   Returns the last deserialization error for the serializer instance.
   \sa lastErrorString()
*/
QAbstractProtobufSerializer::Error QProtobufJsonSerializer::lastError() const
{
    return d_ptr->lastError;
}

/*!
   Returns the last deserialization error string for the serializer instance.
   \sa lastError()
*/
QString QProtobufJsonSerializer::lastErrorString() const
{
    return d_ptr->lastErrorString;
}

QByteArray QProtobufJsonSerializer::serializeMessage(const QProtobufMessage *message) const
{
    d_ptr->clearError();
    d_ptr->serializer.reset();
    d_ptr->serializer.serializeMessage(message);
    auto result = QJsonDocument(d_ptr->serializer.result()).toJson(QJsonDocument::Compact);
    d_ptr->serializer.reset();
    return result;
}

bool QProtobufJsonSerializer::deserializeMessage(QProtobufMessage *message,
                                                 QByteArrayView data) const
{
    d_ptr->clearError();
    QJsonParseError err;
    auto document = QJsonDocument::fromJson(data.toByteArray(), &err);
    if (err.error != QJsonParseError::NoError) {
        d_ptr->deserializer.setUnexpectedEndOfStreamError();
        return false;
    }

    if (!document.isObject()) {
        d_ptr->deserializer.setInvalidFormatError();
        return false;
    }

    if (auto obj = document.object(); !obj.isEmpty()) {
        d_ptr->deserializer.reset(obj);
        bool result = d_ptr->deserializer.deserializeMessage(message);
        d_ptr->deserializer.reset({});
        return result;
    }
    return true;
}

QT_END_NAMESPACE
