/****************************************************************************
**
** Copyright (C) 2017 Ford Motor Company
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtRemoteObjects module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qremoteobjectpacket_p.h"

#include <QtCore/qabstractitemmodel.h>

#include "qremoteobjectpendingcall.h"
#include "qremoteobjectsource.h"
#include "qremoteobjectsource_p.h"
#include <cstring>

//#define QTRO_VERBOSE_PROTOCOL
QT_BEGIN_NAMESPACE


// Add methods so we can use QMetaEnum in a set
// Note for both functions we are skipping string comparisons/hashes.  Since the
// metaObjects are the same, we can just use the address of the string.
inline bool operator==(const QMetaEnum e1, const QMetaEnum e2)
{
    return e1.enclosingMetaObject() == e2.enclosingMetaObject()
           && e1.name() == e2.name()
           && e1.enumName() == e2.enumName()
           && e1.scope() == e2.scope();
}

inline uint qHash(const QMetaEnum &key, uint seed=0) Q_DECL_NOTHROW
{
    return qHash(key.enclosingMetaObject(), seed) ^ qHash(static_cast<const void *>(key.name()), seed)
           ^ qHash(static_cast<const void *>(key.enumName()), seed) ^ qHash(static_cast<const void *>(key.scope()), seed);
}

using namespace QtRemoteObjects;

namespace QRemoteObjectPackets {

// QDataStream sends QVariants of custom types by sending their typename, allowing decode
// on the receiving side.  For QtRO and enums, this won't work, as the enums have different
// scopes.  E.g., the examples have ParentClassSource::MyEnum and ParentClassReplica::MyEnum.
// Dynamic types will be created as ParentClass::MyEnum.  So instead, we change the variants
// to integers (encodeVariant) when sending them.  On the receive side, the we know the
// types of properties and the signatures for methods, so we can use that information to
// decode the integer variant into an enum variant (via decodeVariant).
const QVariant encodeVariant(const QVariant &value)
{
    if (QMetaType::typeFlags(value.userType()).testFlag(QMetaType::IsEnumeration)) {
        auto converted = QVariant(value);
        const auto size = QMetaType(value.userType()).sizeOf();
        switch (size) {
        case 1: converted.convert(QMetaType::Char); break;
        case 2: converted.convert(QMetaType::Short); break;
        case 4: converted.convert(QMetaType::Int); break;
        // Qt currently only supports enum values of 4 or less bytes (QMetaEnum value(index) returns int)
//        case 8: converted.convert(QMetaType::Long); break; // typeId for long from qmetatype.h
        default:
            qWarning() << "Invalid enum detected" << QMetaType::typeName(value.userType()) << "with size" << size;
            converted.convert(QMetaType::Int);
        }
#ifdef QTRO_VERBOSE_PROTOCOL
        qDebug() << "Converting from enum to integer type" << size << converted << value;
#endif
        return converted;
    }
    return value;
}

QVariant &decodeVariant(QVariant &value, int type)
{
    if (QMetaType::typeFlags(type).testFlag(QMetaType::IsEnumeration)) {
#ifdef QTRO_VERBOSE_PROTOCOL
        QVariant encoded(value);
#endif
        value.convert(type);
#ifdef QTRO_VERBOSE_PROTOCOL
        qDebug() << "Converting to enum from integer type" << value << encoded;
#endif
    }
    return value;
}

void serializeProperty(QDataStream &ds, const QRemoteObjectSourceBase *source, int internalIndex)
{
    const int propertyIndex = source->m_api->sourcePropertyIndex(internalIndex);
    Q_ASSERT (propertyIndex >= 0);
    const auto target = source->m_api->isAdapterProperty(internalIndex) ? source->m_adapter : source->m_object;
    const auto property = target->metaObject()->property(propertyIndex);
    const QVariant value = property.read(target);
    if (QMetaType::typeFlags(property.userType()).testFlag(QMetaType::PointerToQObject)) {
        auto const childSource = source->m_children.value(internalIndex);
        auto valueAsPointerToQObject = qvariant_cast<QObject *>(value);
        if (childSource->m_object != valueAsPointerToQObject)
            childSource->resetObject(valueAsPointerToQObject);
        QRO_ qro(childSource);
        if (source->d->isDynamic && qro.type == ObjectType::CLASS && childSource->m_object && !source->d->sentTypes.contains(qro.typeName)) {
            QDataStream classDef(&qro.classDefinition, QIODevice::WriteOnly);
            serializeDefinition(classDef, childSource);
            source->d->sentTypes.insert(qro.typeName);
        }
        ds << QVariant::fromValue<QRO_>(qro);
        if (qro.isNull)
            return;
        const int propertyCount = childSource->m_api->propertyCount();
        // Put the properties in a buffer, the receiver may not know how to
        // interpret the types until it registers new ones.
        QDataStream params(&qro.parameters, QIODevice::WriteOnly);
        params << propertyCount;
        for (int internalIndex = 0; internalIndex < propertyCount; ++internalIndex)
            serializeProperty(params, childSource, internalIndex);
        ds << qro.parameters;
        return;
    }
    if (source->d->isDynamic && property.userType() == QMetaType::QVariant &&
        QMetaType::typeFlags(value.userType()).testFlag(QMetaType::IsGadget)) {
        const auto typeName = QString::fromLatin1(QMetaType::typeName(value.userType()));
        if (!source->d->sentTypes.contains(typeName)) {
            QRO_ qro(value);
            ds << QVariant::fromValue<QRO_>(qro);
            ds << qro.parameters;
            source->d->sentTypes.insert(typeName);
            return;
        }
    }
    ds << encodeVariant(value);
}

void serializeHandshakePacket(DataStreamPacket &ds)
{
    ds.setId(Handshake);
    ds << QString(protocolVersion);
    ds.finishPacket();
}

void serializeInitPacket(DataStreamPacket &ds, const QRemoteObjectRootSource *source)
{
    ds.setId(InitPacket);
    ds << source->name();
    serializeProperties(ds, source);
    ds.finishPacket();
}

void serializeProperties(DataStreamPacket &ds, const QRemoteObjectSourceBase *source)
{
    const SourceApiMap *api = source->m_api;

    //Now copy the property data
    const int numProperties = api->propertyCount();
    ds << quint32(numProperties);  //Number of properties

    for (int internalIndex = 0; internalIndex < numProperties; ++internalIndex)
        serializeProperty(ds, source, internalIndex);
}

bool deserializeQVariantList(QDataStream &s, QList<QVariant> &l)
{
    // note: optimized version of: QDataStream operator>>(QDataStream& s, QList<T>& l)
    quint32 c;
    s >> c;
    const int initialListSize = l.size();
    if (static_cast<quint32>(l.size()) < c)
        l.reserve(c);
    else if (static_cast<quint32>(l.size()) > c)
        for (int i = c; i < initialListSize; ++i)
            l.removeLast();

    for (int i = 0; i < l.size(); ++i)
    {
        if (s.atEnd())
            return false;
        QVariant t;
        s >> t;
        l[i] = t;
    }
    for (quint32 i = l.size(); i < c; ++i)
    {
        if (s.atEnd())
            return false;
        QVariant t;
        s >> t;
        l.append(t);
    }
    return true;
}

void deserializeInitPacket(QDataStream &in, QVariantList &values)
{
    const bool success = deserializeQVariantList(in, values);
    Q_ASSERT(success);
    Q_UNUSED(success);
}

void serializeInitDynamicPacket(DataStreamPacket &ds, const QRemoteObjectRootSource *source)
{
    ds.setId(InitDynamicPacket);
    ds << source->name();
    serializeDefinition(ds, source);
    serializeProperties(ds, source);
    ds.finishPacket();
}

static ObjectType getObjectType(const QString &typeName)
{
    if (typeName == QLatin1String("QAbstractItemModelAdapter"))
        return ObjectType::MODEL;
    auto tid = QMetaType::type(typeName.toUtf8());
    if (tid == QMetaType::UnknownType)
        return ObjectType::CLASS;
    QMetaType type(tid);
    auto mo = type.metaObject();
    if (mo && mo->inherits(&QAbstractItemModel::staticMetaObject))
        return ObjectType::MODEL;
    return ObjectType::CLASS;
}

// Same method as in QVariant.cpp, as it isn't publicly exposed...
static QMetaEnum metaEnumFromType(int type)
{
    QMetaType t(type);
    if (t.flags() & QMetaType::IsEnumeration) {
        if (const QMetaObject *metaObject = t.metaObject()) {
            const char *enumName = QMetaType::typeName(type);
            const char *lastColon = std::strrchr(enumName, ':');
            if (lastColon)
                enumName = lastColon + 1;
            return metaObject->enumerator(metaObject->indexOfEnumerator(enumName));
        }
    }
    return QMetaEnum();
}

static bool checkEnum(int type, QSet<QMetaEnum> &enums)
{
    if (QMetaType::typeFlags(type).testFlag(QMetaType::IsEnumeration)) {
        QMetaEnum meta = metaEnumFromType(type);
        enums.insert(meta);
        return true;
    }
    return false;
}

static void recurseMetaobject(const QMetaObject *mo, QSet<const QMetaObject *> &gadgets, QSet<QMetaEnum> &enums)
{
    if (!mo || gadgets.contains(mo))
        return;
    gadgets.insert(mo);
    const int numProperties = mo->propertyCount();
    for (int i = 0; i < numProperties; ++i) {
        const auto property = mo->property(i);
        if (checkEnum(property.userType(), enums))
            continue;
        if (QMetaType::typeFlags(property.userType()).testFlag(QMetaType::IsGadget))
            recurseMetaobject(QMetaType::metaObjectForType(property.userType()), gadgets, enums);
    }
}

// A Source may only use a subset of the metaobjects properties/signals/slots, so we only search
// the ones in the API.  For nested pointer types, we will have another api to limit the search.
// For nested PODs/enums, we search the entire qobject (using the recurseMetaobject call()).
void recurseForGadgets(QSet<const QMetaObject *> &gadgets, QSet<QMetaEnum> &enums, const QRemoteObjectSourceBase *source)
{
    const SourceApiMap *api = source->m_api;

    const int numSignals = api->signalCount();
    const int numMethods = api->methodCount();
    const int numProperties = api->propertyCount();

    for (int si = 0; si < numSignals; ++si) {
        const int params = api->signalParameterCount(si);
        for (int pi = 0; pi < params; ++pi) {
            const int type = api->signalParameterType(si, pi);
            if (checkEnum(type, enums))
                continue;
            if (!QMetaType::typeFlags(type).testFlag(QMetaType::IsGadget))
                continue;
            const auto mo = QMetaType::metaObjectForType(type);
            if (source->d->sentTypes.contains(QLatin1String(mo->className())))
                continue;
            recurseMetaobject(mo, gadgets, enums);
            source->d->sentTypes.insert(QLatin1String(mo->className()));
        }
    }

    for (int mi = 0; mi < numMethods; ++mi) {
        const int params = api->methodParameterCount(mi);
        for (int pi = 0; pi < params; ++pi) {
            const int type = api->methodParameterType(mi, pi);
            if (checkEnum(type, enums))
                continue;
            if (!QMetaType::typeFlags(type).testFlag(QMetaType::IsGadget))
                continue;
            const auto mo = QMetaType::metaObjectForType(type);
            if (source->d->sentTypes.contains(QLatin1String(mo->className())))
                continue;
            recurseMetaobject(mo, gadgets, enums);
            source->d->sentTypes.insert(QLatin1String(mo->className()));
        }
    }
    for (int pi = 0; pi < numProperties; ++pi) {
        const int index = api->sourcePropertyIndex(pi);
        Q_ASSERT(index >= 0);
        const auto target = api->isAdapterProperty(pi) ? source->m_adapter : source->m_object;
        const auto metaProperty = target->metaObject()->property(index);
        const int type = metaProperty.userType();
        if (checkEnum(type, enums))
            continue;
        if (QMetaType::typeFlags(type).testFlag(QMetaType::PointerToQObject)) {
            auto const objectType = getObjectType(QString::fromLatin1(metaProperty.typeName()));
            if (objectType == ObjectType::CLASS) {
                auto const childSource = source->m_children.value(pi);
                if (childSource->m_object)
                    recurseForGadgets(gadgets, enums, childSource);
            }
        }
        if (!QMetaType::typeFlags(type).testFlag(QMetaType::IsGadget))
            continue;
        const auto mo = QMetaType::metaObjectForType(type);
        if (source->d->sentTypes.contains(QLatin1String(mo->className())))
            continue;
        recurseMetaobject(mo, gadgets, enums);
        source->d->sentTypes.insert(QLatin1String(mo->className()));
    }
}

static bool checkForEnumsInSource(const QMetaObject *meta, const QRemoteObjectSourceBase *source)
{
    if (source->m_object->inherits(meta->className()))
        return true;
    for (const auto child : source->m_children) {
        if (child->m_object && checkForEnumsInSource(meta, child))
            return true;
    }
    return false;
}

static void serializeEnum(QDataStream &ds, const QMetaEnum &enumerator)
{
    ds << QByteArray::fromRawData(enumerator.name(), qstrlen(enumerator.name()));
    ds << enumerator.isFlag();
    ds << enumerator.isScoped();
    const auto typeName = QByteArray(enumerator.scope()).append("::").append(enumerator.name());
    quint32 size = QMetaType(QMetaType::type(typeName.constData())).sizeOf();
    ds << size;
#ifdef QTRO_VERBOSE_PROTOCOL
    qDebug("  Enum (name = %s, size = %d, isFlag = %s, isScoped = %s):", enumerator.name(), size, enumerator.isFlag() ? "true" : "false", enumerator.isScoped() ? "true" : "false");
#endif
    const int keyCount = enumerator.keyCount();
    ds << keyCount;
    for (int k = 0; k < keyCount; ++k) {
        ds << QByteArray::fromRawData(enumerator.key(k), qstrlen(enumerator.key(k)));
        ds << enumerator.value(k);
#ifdef QTRO_VERBOSE_PROTOCOL
        qDebug("    Key %d (name = %s, value = %d):", k, enumerator.key(k), enumerator.value(k));
#endif
    }
}

static void serializeGadgets(QDataStream &ds, const QSet<const QMetaObject *> &gadgets, const QSet<QMetaEnum> &enums, const QRemoteObjectSourceBase *source=nullptr)
{
    // Determine how to handle the enums found
    QSet<QMetaEnum> qtEnums;
    QSet<const QMetaObject *> dynamicEnumMetaObjects;
    for (const auto metaEnum : enums) {
        auto const metaObject = metaEnum.enclosingMetaObject();
        if (gadgets.contains(metaObject)) // Part of a gadget will we serialize
            continue;
        // This checks if the enum is defined in our object heirarchy, in which case it will
        // already have been serialized.
        if (source && checkForEnumsInSource(metaObject, source->d->root))
            continue;
        // qtEnums are enumerations already known by Qt, so we only need register them.
        // We don't need to send all of the key/value data.
        if (metaObject == qt_getQtMetaObject()) // Are the other Qt metaclasses for enums?
            qtEnums.insert(metaEnum);
        else
            dynamicEnumMetaObjects.insert(metaEnum.enclosingMetaObject());
    }
    ds << quint32(qtEnums.size());
    for (const auto metaEnum : qtEnums) {
        QByteArray enumName(metaEnum.scope());
        enumName.append("::", 2).append(metaEnum.name());
        ds << enumName;
    }
    const auto allMetaObjects = gadgets + dynamicEnumMetaObjects;
    ds << quint32(allMetaObjects.size());
#ifdef QTRO_VERBOSE_PROTOCOL
    qDebug() << "  Found" << gadgets.size() << "gadget/pod and" << (allMetaObjects.size() - gadgets.size()) << "enum types";
    int i = 0;
#endif
    // There isn't an easy way to update a metaobject incrementally, so we
    // send all of the metaobject's enums, but no properties, when an external
    // enum is requested.
    for (auto const meta : allMetaObjects) {
        ds << QByteArray::fromRawData(meta->className(), qstrlen(meta->className()));
        int propertyCount = gadgets.contains(meta) ? meta->propertyCount() : 0;
        ds << quint32(propertyCount);
#ifdef QTRO_VERBOSE_PROTOCOL
        qDebug("  Gadget %d (name = %s, # properties = %d, # enums = %d):", i++, meta->className(), propertyCount, meta->enumeratorCount());
#endif
        for (int j = 0; j < propertyCount; j++) {
            auto prop = meta->property(j);
#ifdef QTRO_VERBOSE_PROTOCOL
            qDebug("    Data member %d (name = %s, type = %s):", j, prop.name(), prop.typeName());
#endif
            ds << QByteArray::fromRawData(prop.name(), qstrlen(prop.name()));
            ds << QByteArray::fromRawData(prop.typeName(), qstrlen(prop.typeName()));
        }
        int enumCount = meta->enumeratorCount();
        ds << enumCount;
        for (int j = 0; j < enumCount; j++) {
            auto const enumMeta = meta->enumerator(j);
            serializeEnum(ds, enumMeta);
        }
    }
}

void serializeDefinition(QDataStream &ds, const QRemoteObjectSourceBase *source)
{
    const SourceApiMap *api = source->m_api;
    const QByteArray desiredClassName(api->typeName().toLatin1());
    const QByteArray originalClassName = api->className();
    // The dynamic class will be called typeName on the receiving side of this definition
    // However, there are types like enums that have the QObject's class name.  Replace()
    // will convert a parameter such as "ParentClassSource::MyEnum" to "ParentClass::MyEnum"
    // so the type can be properly resolved and registered.
    auto replace = [&originalClassName, &desiredClassName](QByteArray &name) {
        name.replace(originalClassName, desiredClassName);
    };

    ds << source->m_api->typeName();
#ifdef QTRO_VERBOSE_PROTOCOL
    qDebug() << "Serializing definition for" << source->m_api->typeName();
#endif

    //Now copy the property data
    const int numEnums = api->enumCount();
    const auto metaObject = source->m_object->metaObject();
    ds << quint32(numEnums);  //Number of Enums
#ifdef QTRO_VERBOSE_PROTOCOL
    qDebug() << "  Found" << numEnums << "enumeration types";
#endif
    for (int i = 0; i < numEnums; ++i) {
        auto enumerator = metaObject->enumerator(api->sourceEnumIndex(i));
        Q_ASSERT(enumerator.isValid());
        serializeEnum(ds, enumerator);
    }

    QSet<const QMetaObject *> gadgets;
    QSet<QMetaEnum> enums;
    recurseForGadgets(gadgets, enums, source);
    serializeGadgets(ds, gadgets, enums, source);

    const int numSignals = api->signalCount();
    ds << quint32(numSignals);  //Number of signals
    for (int i = 0; i < numSignals; ++i) {
        const int index = api->sourceSignalIndex(i);
        Q_ASSERT(index >= 0);
        auto signature = api->signalSignature(i);
        replace(signature);
#ifdef QTRO_VERBOSE_PROTOCOL
        qDebug() << "  Signal" << i << "(signature =" << signature << "parameter names =" << api->signalParameterNames(i) << ")";
#endif
        ds << signature;
        ds << api->signalParameterNames(i);
    }

    const int numMethods = api->methodCount();
    ds << quint32(numMethods);  //Number of methods
    for (int i = 0; i < numMethods; ++i) {
        const int index = api->sourceMethodIndex(i);
        Q_ASSERT(index >= 0);
        auto signature = api->methodSignature(i);
        replace(signature);
        auto typeName = api->typeName(i);
        replace(typeName);
#ifdef QTRO_VERBOSE_PROTOCOL
        qDebug() << "  Slot" << i << "(signature =" << signature << "parameter names =" << api->methodParameterNames(i) << "return type =" << typeName << ")";
#endif
        ds << signature;
        ds << typeName;
        ds << api->methodParameterNames(i);
    }

    const int numProperties = api->propertyCount();
    ds << quint32(numProperties);  //Number of properties
    for (int i = 0; i < numProperties; ++i) {
        const int index = api->sourcePropertyIndex(i);
        Q_ASSERT(index >= 0);

        const auto target = api->isAdapterProperty(i) ? source->m_adapter : source->m_object;
        const auto metaProperty = target->metaObject()->property(index);
        ds << metaProperty.name();
#ifdef QTRO_VERBOSE_PROTOCOL
        qDebug() << "  Property" << i << "name =" << metaProperty.name();
#endif
        if (QMetaType::typeFlags(metaProperty.userType()).testFlag(QMetaType::PointerToQObject)) {
            auto objectType = getObjectType(QLatin1String(metaProperty.typeName()));
            ds << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*");
#ifdef QTRO_VERBOSE_PROTOCOL
            qDebug() << "    Type:" << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*");
#endif
        } else {
            ds << metaProperty.typeName();
#ifdef QTRO_VERBOSE_PROTOCOL
            qDebug() << "    Type:" << metaProperty.typeName();
#endif
        }
        if (metaProperty.notifySignalIndex() == -1) {
            ds << QByteArray();
#ifdef QTRO_VERBOSE_PROTOCOL
            qDebug() << "    Notification signal: None";
#endif
        } else {
            auto signature = metaProperty.notifySignal().methodSignature();
            replace(signature);
            ds << signature;
#ifdef QTRO_VERBOSE_PROTOCOL
            qDebug() << "    Notification signal:" << signature;
#endif
        }
    }
}

void serializeAddObjectPacket(DataStreamPacket &ds, const QString &name, bool isDynamic)
{
    ds.setId(AddObject);
    ds << name;
    ds << isDynamic;
    ds.finishPacket();
}

void deserializeAddObjectPacket(QDataStream &ds, bool &isDynamic)
{
    ds >> isDynamic;
}

void serializeRemoveObjectPacket(DataStreamPacket &ds, const QString &name)
{
    ds.setId(RemoveObject);
    ds << name;
    ds.finishPacket();
}
//There is no deserializeRemoveObjectPacket - no parameters other than id and name

void serializeInvokePacket(DataStreamPacket &ds, const QString &name, int call, int index, const QVariantList &args, int serialId, int propertyIndex)
{
    ds.setId(InvokePacket);
    ds << name;
    ds << call;
    ds << index;

    ds << (quint32)args.size();
    for (const auto &arg : args)
        ds << encodeVariant(arg);

    ds << serialId;
    ds << propertyIndex;
    ds.finishPacket();
}

void deserializeInvokePacket(QDataStream& in, int &call, int &index, QVariantList &args, int &serialId, int &propertyIndex)
{
    in >> call;
    in >> index;
    const bool success = deserializeQVariantList(in, args);
    Q_ASSERT(success);
    Q_UNUSED(success);
    in >> serialId;
    in >> propertyIndex;
}

void serializeInvokeReplyPacket(DataStreamPacket &ds, const QString &name, int ackedSerialId, const QVariant &value)
{
    ds.setId(InvokeReplyPacket);
    ds << name;
    ds << ackedSerialId;
    ds << value;
    ds.finishPacket();
}

void deserializeInvokeReplyPacket(QDataStream& in, int &ackedSerialId, QVariant &value){
    in >> ackedSerialId;
    in >> value;
}

void serializePropertyChangePacket(QRemoteObjectSourceBase *source, int signalIndex)
{
    int internalIndex = source->m_api->propertyRawIndexFromSignal(signalIndex);
    auto &ds = source->d->m_packet;
    ds.setId(PropertyChangePacket);
    ds << source->name();
    ds << internalIndex;
    serializeProperty(ds, source, internalIndex);
    ds.finishPacket();
}

void deserializePropertyChangePacket(QDataStream& in, int &index, QVariant &value)
{
    in >> index;
    in >> value;
}

void serializeObjectListPacket(DataStreamPacket &ds, const ObjectInfoList &objects)
{
    ds.setId(ObjectList);
    ds << objects;
    ds.finishPacket();
}

void deserializeObjectListPacket(QDataStream &in, ObjectInfoList &objects)
{
    in >> objects;
}

void serializePingPacket(DataStreamPacket &ds, const QString &name)
{
    ds.setId(Ping);
    ds << name;
    ds.finishPacket();
}

void serializePongPacket(DataStreamPacket &ds, const QString &name)
{
    ds.setId(Pong);
    ds << name;
    ds.finishPacket();
}

QRO_::QRO_(QRemoteObjectSourceBase *source)
    : name(source->name())
    , typeName(source->m_api->typeName())
    , type(source->m_adapter ? ObjectType::MODEL : getObjectType(typeName))
    , isNull(source->m_object == nullptr)
    , classDefinition()
    , parameters()
{}

QRO_::QRO_(const QVariant &value)
    : type(ObjectType::GADGET)
    , isNull(false)
{
    auto meta = QMetaType::metaObjectForType(value.userType());
    QDataStream out(&classDefinition, QIODevice::WriteOnly);
    const int numProperties = meta->propertyCount();
    const auto typeName = QByteArray::fromRawData(QMetaType::typeName(value.userType()), qstrlen(QMetaType::typeName(value.userType())));
    out << quint32(0) << quint32(1);
    out << typeName;
    out << numProperties;
#ifdef QTRO_VERBOSE_PROTOCOL
    qDebug("Serializing POD definition to QRO_ (name = %s)", typeName.constData());
#endif
    for (int i = 0; i < numProperties; ++i) {
        const auto property = meta->property(i);
#ifdef QTRO_VERBOSE_PROTOCOL
        qDebug("  Data member %d (name = %s, type = %s):", i, property.name(), property.typeName());
#endif
        out << QByteArray::fromRawData(property.name(), qstrlen(property.name()));
        out << QByteArray::fromRawData(property.typeName(), qstrlen(property.typeName()));
    }
    QDataStream ds(&parameters, QIODevice::WriteOnly);
    ds << value;
#ifdef QTRO_VERBOSE_PROTOCOL
    qDebug() << "  Value:" << value;
#endif
}

QDataStream &operator<<(QDataStream &stream, const QRO_ &info)
{
    stream << info.name << info.typeName << (quint8)(info.type) << info.classDefinition << info.isNull;
    qCDebug(QT_REMOTEOBJECT) << "Serializing " << info;
    // info.parameters will be filled in by serializeProperty
    return stream;
}

QDataStream &operator>>(QDataStream &stream, QRO_ &info)
{
    quint8 tmpType;
    stream >> info.name >> info.typeName >> tmpType >> info.classDefinition >> info.isNull;
    info.type = static_cast<ObjectType>(tmpType);
    qCDebug(QT_REMOTEOBJECT) << "Deserializing " << info;
    if (!info.isNull)
        stream >> info.parameters;
    return stream;
}

} // namespace QRemoteObjectPackets

QT_END_NAMESPACE
