/****************************************************************************
 **
 ** Copyright (C) 2013 BlackBerry Limited. All rights reserved.
 ** Contact: https://www.qt.io/licensing/
 **
 ** This file is part of the QtCore 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 "qppsobject_p.h"

#include "qppsobjectprivate_p.h"
#include "qppsattribute_p.h"
#include "qppsattributeprivate_p.h"
#include "qcore_unix_p.h"

#include <QObject>
#include <QSocketNotifier>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <confname.h>

#include <sys/pps.h>

QT_BEGIN_NAMESPACE

///////////////////////////////////////////////////////////////////////////////
static inline void safeAssign(bool *pointer, bool value)
{
    if (pointer)
        *pointer = value;
}

class QPpsMaxSize
{
public:
    QPpsMaxSize()
    {
        int fd = qt_safe_open("/pps/.all", O_RDONLY);
        if (fd == -1) {
            qWarning("qppsobject.cpp: qt_safe_open failed");
            value = -1;
        }

        // This tells us the maximum transfer size across PPS
        value = ::fpathconf(fd, _PC_REC_MAX_XFER_SIZE);

        qt_safe_close(fd);
    }

    int value;
};

Q_GLOBAL_STATIC(QPpsMaxSize, ppsMaxSize)


///////////////////////////////////////////////////////////////////////////////
//
// QPpsObjectPrivate
//
///////////////////////////////////////////////////////////////////////////////

QPpsObjectPrivate::QPpsObjectPrivate(const QString &path)
    : notifier(0),
      path(path),
      error(EOK),
      fd(-1),
      readyReadEnabled(true)
{
}

QPpsAttributeMap QPpsObjectPrivate::decode(const QByteArray &rawData, bool *ok)
{
    QPpsAttributeMap attributeMap;
    pps_decoder_t decoder;

    QByteArray mutableData(rawData);
    pps_decoder_error_t error = pps_decoder_initialize(&decoder, mutableData.data());
    if (error == PPS_DECODER_OK) {
        // no need to check ok in this case
        attributeMap = decodeObject(&decoder, ok);
    } else {
        qWarning("QPpsObjectPrivate::decode: pps_decoder_initialize failed");
        *ok = false;
    }

    pps_decoder_cleanup(&decoder);
    return attributeMap;
}

QVariantMap QPpsObjectPrivate::variantMapFromPpsAttributeMap(const QPpsAttributeMap &data)
{
    QVariantMap variantMap;

    for (QPpsAttributeMap::const_iterator it = data.constBegin(); it != data.constEnd(); ++it) {
        QVariant variant = variantFromPpsAttribute(it.value());
        if (!variant.isValid())
            return QVariantMap();
        variantMap[it.key()] = variant;
    }

    return variantMap;
}

QPpsAttribute::Flags QPpsObjectPrivate::readFlags(pps_decoder_t *decoder)
{
    int rawFlags = pps_decoder_flags(decoder, 0);

    QPpsAttribute::Flags attributeFlags;

    if (rawFlags & PPS_INCOMPLETE)
        attributeFlags |= QPpsAttribute::Incomplete;
    if (rawFlags & PPS_DELETED)
        attributeFlags |= QPpsAttribute::Deleted;
    if (rawFlags & PPS_CREATED)
        attributeFlags |= QPpsAttribute::Created;
    if (rawFlags & PPS_TRUNCATED)
        attributeFlags |= QPpsAttribute::Truncated;
    if (rawFlags & PPS_PURGED)
        attributeFlags |= QPpsAttribute::Purged;

    return attributeFlags;
}

QPpsAttribute QPpsObjectPrivate::decodeString(pps_decoder_t *decoder)
{
    const char *value = 0;
    pps_decoder_error_t error = pps_decoder_get_string(decoder, 0, &value);

    if (error != PPS_DECODER_OK) {
        qWarning("QPpsObjectPrivate::decodeString: PPS_DECODER_GET_STRING failed");
        return QPpsAttribute();
    }

    QPpsAttribute::Flags flags = readFlags(decoder);
    return QPpsAttributePrivate::createPpsAttribute(QString::fromUtf8(value), flags);
}

QPpsAttribute QPpsObjectPrivate::decodeNumber(pps_decoder_t *decoder)
{
    // In order to support more number types, we have to do something stupid because the PPS
    // library won't let us work any other way. Basically, we have to probe the encoded type in
    // order to try to get exactly what we want.
    int64_t llValue;
    double dValue;
    int iValue;
    QPpsAttribute::Flags flags;

    if (pps_decoder_is_integer(decoder, 0)) {
        pps_decoder_error_t error = pps_decoder_get_int(decoder, 0, &iValue);
        switch (error) {
        case PPS_DECODER_OK:
            flags = readFlags(decoder);
            return QPpsAttributePrivate::createPpsAttribute(iValue, flags);
        case PPS_DECODER_CONVERSION_FAILED:
            error = pps_decoder_get_int64(decoder, 0, &llValue);
            if (error != PPS_DECODER_OK) {
                qWarning("QPpsObjectPrivate::decodeNumber: failed to decode integer");
                return QPpsAttribute();
            }
            flags = readFlags(decoder);
            return QPpsAttributePrivate::createPpsAttribute(static_cast<long long>(llValue), flags);
        default:
            qWarning("QPpsObjectPrivate::decodeNumber: pps_decoder_get_int failed");
            return QPpsAttribute();
        }
    } else {
        pps_decoder_error_t error = pps_decoder_get_double(decoder, 0, &dValue);
        if (error != PPS_DECODER_OK) {
            qWarning("QPpsObjectPrivate::decodeNumber: pps_decoder_get_double failed");
            return QPpsAttribute();
        }
        flags = readFlags(decoder);
        return QPpsAttributePrivate::createPpsAttribute(dValue, flags);
    }
}

QPpsAttribute QPpsObjectPrivate::decodeBool(pps_decoder_t *decoder)
{
    bool value;
    pps_decoder_error_t error = pps_decoder_get_bool(decoder, 0, &value);

    if (error != PPS_DECODER_OK) {
        qWarning("QPpsObjectPrivate::decodeBool: pps_decoder_get_bool failed");
        return QPpsAttribute();
    }

    QPpsAttribute::Flags flags = readFlags(decoder);
    return QPpsAttributePrivate::createPpsAttribute(value, flags);
}

template<typename T>
QPpsAttribute QPpsObjectPrivate::decodeNestedData(T (*decodeFunction)(pps_decoder_t *, bool *),
                                                  pps_decoder_t *decoder)
{
    // We must read the flags before we push into the object,
    // otherwise we'll get the flags for the first element in the object.
    QPpsAttribute::Flags flags = readFlags(decoder);

    if (!decoderPush(decoder))
        return QPpsAttribute();

    bool ok = false;

    T attributeContainer = decodeFunction(decoder, &ok);

    if (!ok)
        return QPpsAttribute();

    QPpsAttribute returnVal = QPpsAttributePrivate::createPpsAttribute(attributeContainer, flags);

    if (!decoderPop(decoder))
        return QPpsAttribute();

    return returnVal;
}

QPpsAttribute QPpsObjectPrivate::decodeData(pps_decoder_t *decoder)
{
    pps_node_type_t nodeType = pps_decoder_type(decoder, 0);
    switch (nodeType) {
    case PPS_TYPE_BOOL:
        return decodeBool(decoder);
    case PPS_TYPE_NUMBER:
        return decodeNumber(decoder);
    case PPS_TYPE_STRING:
        return decodeString(decoder);
    case PPS_TYPE_ARRAY:
        return decodeNestedData(&QPpsObjectPrivate::decodeArray, decoder);
    case PPS_TYPE_OBJECT:
        return decodeNestedData(&QPpsObjectPrivate::decodeObject, decoder);
    case PPS_TYPE_DELETED: {
        // This should create an attribute with the flags set to PpsAttribute::Deleted.
        // However, we need to create a valid QPpsAttribute while doing so. To do this,
        // I'll create an empty map as a sentinel. Note that the readFlags() call with produce
        // the correct set of flags. While I suspect that there will never be any other flags
        // set in conjunction with this one, I'd rather not be surprised later.
        QPpsAttributeMap emptyMap;
        QPpsAttribute::Flags flags = readFlags(decoder);
        QPpsAttribute returnVal = QPpsAttributePrivate::createPpsAttribute(emptyMap, flags);
        return returnVal;
    }
    case PPS_TYPE_NULL:
    case PPS_TYPE_NONE:
    case PPS_TYPE_UNKNOWN:
    default:
        qWarning("QPpsObjectPrivate::decodeData: invalid pps_node_type");
        return QPpsAttribute();
    }
}

QPpsAttributeList QPpsObjectPrivate::decodeArray(pps_decoder_t *decoder, bool *ok)
{
    QPpsAttributeList list;

    int length = pps_decoder_length(decoder);
    for (int i = 0; i < length; ++i) {
        // Force movement to a specific index.
        pps_decoder_error_t error = pps_decoder_goto_index(decoder, i);
        if (error != PPS_DECODER_OK) {
            qWarning("QPpsObjectPrivate::decodeArray: pps_decoder_goto_index failed");
            *ok = false;
            return QPpsAttributeList();
        }

        QPpsAttribute ppsAttribute = decodeData(decoder);
        if (!ppsAttribute.isValid()) {
            *ok = false;
            return QPpsAttributeList();
        }

        list << ppsAttribute;
    }

    *ok = true;
    return list;
}

QPpsAttributeMap QPpsObjectPrivate::decodeObject(pps_decoder_t *decoder, bool *ok)
{
    QPpsAttributeMap map;

    int length = pps_decoder_length(decoder);
    for (int i = 0; i < length; ++i) {
        // Force movement to a specific index.
        pps_decoder_error_t error = pps_decoder_goto_index(decoder, i);
        if (error != PPS_DECODER_OK) {
            qWarning("QPpsObjectPrivate::decodeObject: pps_decoder_goto_index failed");
            *ok = false;
            return QPpsAttributeMap();
        }
        QString name = QString::fromUtf8(pps_decoder_name(decoder));
        QPpsAttribute ppsAttribute = decodeData(decoder);
        if (!ppsAttribute.isValid()) {
            *ok = false;
            return QPpsAttributeMap();
        }
        map[name] = ppsAttribute;
    }

    *ok = true;
    return map;
}

QVariant QPpsObjectPrivate::variantFromPpsAttribute(const QPpsAttribute &attribute)
{
    switch (attribute.type()) {
    case QPpsAttribute::Number:
        switch (attribute.toVariant().type()) {
        case QVariant::Int:
            return attribute.toInt();
        case QVariant::LongLong:
            return attribute.toLongLong();
        default:
            return attribute.toDouble();
        }
        break;
    case QPpsAttribute::Bool:
        return attribute.toBool();
    case QPpsAttribute::String:
        return attribute.toString();
    case QPpsAttribute::Array: {
        QVariantList variantList;
        const auto attrs = attribute.toList();
        for (const QPpsAttribute &attr : attrs) {
            QVariant variant = variantFromPpsAttribute(attr);
            if (!variant.isValid())
                return QVariantList();
            variantList << variant;
        }
        return variantList;
    }
    case QPpsAttribute::Object:
        return variantMapFromPpsAttributeMap(attribute.toMap());
    case QPpsAttribute::None:
    default:
        qWarning("QPpsObjectPrivate::variantFromPpsAttribute: invalid attribute parameter");
        return QVariant();
    }
}

QByteArray QPpsObjectPrivate::encode(const QVariantMap &ppsData, bool *ok)
{
    pps_encoder_t encoder;
    pps_encoder_initialize(&encoder, false);

    encodeObject(&encoder, ppsData, ok);
    const char *rawData = 0;
    if (*ok) {
        // rawData points to a memory owned by encoder.
        // The memory will be freed when pps_encoder_cleanup is called.
        rawData = pps_encoder_buffer(&encoder);
        if (!rawData) {
            qWarning("QPpsObjectPrivate::encode: pps_encoder_buffer failed");
            *ok = false;
        }
    }

    pps_encoder_cleanup(&encoder);
    return QByteArray(rawData);
}

void QPpsObjectPrivate::encodeData(pps_encoder_t *encoder, const char *name, const QVariant &data,
                                   bool *ok)
{
    const char *errorFunction;
    pps_encoder_error_t error = PPS_ENCODER_OK;
    switch (data.type()) {
    case QVariant::Bool:
        error = pps_encoder_add_bool(encoder, name, data.toBool());
        errorFunction = "pps_encoder_add_bool";
        break;
    // We want to support encoding uint even though libpps doesn't support it directly.
    // We can't encode uint as an int since that will lose precision (e.g. 2^31+1 can't be
    // encoded that way). However, we can convert uint to double without losing precision.
    // QVariant.toDouble() conveniently takes care of the conversion for us.
    case QVariant::UInt:
    case QVariant::Double:
        error = pps_encoder_add_double(encoder, name, data.toDouble());
        errorFunction = "pps_encoder_add_double";
        break;
    case QVariant::Int:
        error = pps_encoder_add_int(encoder, name, data.toInt());
        errorFunction = "pps_encoder_add_int";
        break;
    case QVariant::LongLong:
        error = pps_encoder_add_int64(encoder, name, data.toLongLong());
        errorFunction = "pps_encoder_add_int64";
        break;
    case QVariant::String:
        error = pps_encoder_add_string(encoder, name, data.toString().toUtf8().constData());
        errorFunction = "pps_encoder_add_string";
        break;
    case QVariant::List:
        error = pps_encoder_start_array(encoder, name);
        errorFunction = "pps_encoder_start_array";
        if (error == PPS_ENCODER_OK) {
            encodeArray(encoder, data.toList(), ok);
            error = pps_encoder_end_array(encoder);
            errorFunction = "pps_encoder_end_array";
        }
        break;
    case QVariant::Map:
        error = pps_encoder_start_object(encoder, name);
        errorFunction = "pps_encoder_start_object";
        if (error == PPS_ENCODER_OK) {
            encodeObject(encoder, data.toMap(), ok);
            error = pps_encoder_end_object(encoder);
            errorFunction = "pps_encoder_end_object";
        }
        break;
    case QVariant::Invalid:
        error = pps_encoder_add_null(encoder, name);
        errorFunction = "pps_encoder_add_null";
        break;
    default:
        qWarning("QPpsObjectPrivate::encodeData: the type of the parameter data is invalid");
        *ok = false;
        return;
    }

    if (error != PPS_ENCODER_OK) {
        qWarning("QPpsObjectPrivate::encodeData: %s failed", errorFunction);
        *ok = false;
    } else {
        *ok = true;
    }
}

void QPpsObjectPrivate::encodeArray(pps_encoder_t *encoder, const QVariantList &data, bool *ok)
{
    for (QVariantList::const_iterator it = data.constBegin(); it != data.constEnd(); ++it) {
        encodeData(encoder, 0, *it, ok);
        if (!(*ok))
            return;
    }
    // if the passed data is empty, nothing went wrong and ok is set to true
    *ok = true;
}

void QPpsObjectPrivate::encodeObject(pps_encoder_t *encoder, const QVariantMap &data, bool *ok)
{
    for (QVariantMap::const_iterator it = data.constBegin(); it != data.constEnd(); ++it) {
        encodeData(encoder, it.key().toUtf8().constData(), it.value(), ok);
        if (!(*ok))
            return;
    }
    // if the passed data is empty, nothing went wrong and ok is set to true
    *ok = true;
}



///////////////////////////////////////////////////////////////////////////////
//
// QPpsObjectPrivate
//
///////////////////////////////////////////////////////////////////////////////

QPpsObject::QPpsObject(const QString &path, QObject *parent)
    : QObject(*new QPpsObjectPrivate(path), parent)
{
}

QPpsObject::~QPpsObject()
{
    // RAII - ensure file gets closed
    if (isOpen())
        close();
}

int QPpsObject::error() const
{
    Q_D(const QPpsObject);
    return d->error;
}

QString QPpsObject::errorString() const
{
    Q_D(const QPpsObject);
    return qt_error_string(d->error);
}

bool QPpsObject::isReadyReadEnabled() const
{
    Q_D(const QPpsObject);

    // query state of read ready signal
    return d->readyReadEnabled;
}

void QPpsObject::setReadyReadEnabled(bool enable)
{
    Q_D(QPpsObject);

    // toggle whether socket notifier will emit a signal on read ready
    d->readyReadEnabled = enable;
    if (isOpen())
        d->notifier->setEnabled(enable);
}

bool QPpsObject::isBlocking() const
{
    Q_D(const QPpsObject);

    // reset last error
    d->error = EOK;

    // abort if file not open
    if (!isOpen()) {
        d->error = EBADF;
        return false;
    }

    // query file status flags
    int flags = fcntl(d->fd, F_GETFL);
    if (flags == -1) {
        d->error = errno;
        return false;
    }
    // check if non-blocking flag is unset
    return ((flags & O_NONBLOCK) != O_NONBLOCK);
}

bool QPpsObject::setBlocking(bool enable)
{
    Q_D(QPpsObject);

    // reset last error
    d->error = EOK;

    // abort if file not open
    if (!isOpen()) {
        d->error = EBADF;
        return false;
    }

    // query file status flags
    int flags = fcntl(d->fd, F_GETFL);
    if (flags == -1) {
        d->error = errno;
        return false;
    }

    // configure non-blocking flag
    if (enable)
        flags &= ~O_NONBLOCK;
    else
        flags |= O_NONBLOCK;

    // update file status flags
    flags = fcntl(d->fd, F_SETFL, flags);
    if (flags == -1) {
        d->error = errno;
        return false;
    }

    return true;
}

bool QPpsObject::isOpen() const
{
    Q_D(const QPpsObject);
    return (d->fd != -1);
}

bool QPpsObject::open(QPpsObject::OpenModes mode)
{
    Q_D(QPpsObject);

    // reset last error
    d->error = EOK;

    // abort if file already open
    if (isOpen()) {
        d->error = EBUSY;
        return false;
    }

    // convert pps flags to open flags
    int oflags = 0;
    if ((mode & QPpsObject::Publish) && (mode & QPpsObject::Subscribe))
        oflags |= O_RDWR;
    else if (mode & QPpsObject::Publish)
        oflags |= O_WRONLY;
    else if (mode & QPpsObject::Subscribe)
        oflags |= O_RDONLY;

    if (mode & QPpsObject::Create)
        oflags |= O_CREAT | O_EXCL;

    if (mode & QPpsObject::DeleteContents)
        oflags |= O_TRUNC;

    // open pps file
    d->fd = qt_safe_open(d->path.toUtf8().data(), oflags, 0666);
    if (d->fd == -1) {
        d->error = errno;
        return false;
    }
    // wire up socket notifier to know when reads are ready
    d->notifier = new QSocketNotifier(d->fd, QSocketNotifier::Read, this);
    d->notifier->setEnabled(d->readyReadEnabled);
    QObject::connect(d->notifier, &QSocketNotifier::activated, this, &QPpsObject::readyRead);
    return true;
}

bool QPpsObject::close()
{
    Q_D(QPpsObject);

    // reset last error
    d->error = EOK;

    // abort if file not open
    if (!isOpen()) {
        d->error = EBADF;
        return false;
    }

    // shutdown socket notifier
    delete d->notifier;
    d->notifier = 0;

    // close pps file
    const int result = qt_safe_close(d->fd);
    d->fd = -1;

    // check success of operation
    if (result != 0) {
        d->error = errno;
        return false;
    }
    return true;
}

QByteArray QPpsObject::read(bool *ok)
{
    Q_D(QPpsObject);

    // reset last error
    d->error = EOK;

    // abort if file not open
    if (!isOpen()) {
        d->error = EBADF;
        safeAssign(ok, false);
        return QByteArray();
    }

    const int maxSize = ppsMaxSize->value;
    if (maxSize == -1) {
        qWarning("QPpsObject::read: maxSize is equal to -1");
        safeAssign(ok, false);
        return QByteArray();
    }

    QByteArray byteArray;
    byteArray.resize(maxSize); // resize doesn't initialize the data
    const int result = qt_safe_read(d->fd, byteArray.data(), byteArray.size());

    if (result == -1) {
        d->error = errno;
        qWarning() << "QPpsObject::read failed to read pps data, error " << errorString();
        safeAssign(ok, false);
        return QByteArray(); // Specifically return a default-constructed QByteArray.
    }
    if (result == 0) {
        // normalize the behavior of read() when no data is ready so a pps object
        // put in non-blocking mode via opening w/o wait (read returns 0) looks
        // the same as a pps object put in non-blocking mode by setting O_NONBLOCK
        // (read returns EAGAIN)
        d->error = EAGAIN;
        safeAssign(ok, false);
        return QByteArray(); // Specifically return a default-constructed QByteArray.
    }
    // resize byte array to amount actually read
    byteArray.resize(result);
    safeAssign(ok, true);
    return byteArray;
}

bool QPpsObject::write(const QByteArray &byteArray)
{
    Q_D(QPpsObject);

    // reset last error
    d->error = EOK;

    // abort if file not open
    if (!isOpen()) {
        d->error = EBADF;
        return false;
    }

    // write entire byte array to pps file
    const int result = qt_safe_write(d->fd, byteArray.data(), byteArray.size());
    if (result == -1)
        d->error = errno;

    return (result == byteArray.size());
}

int QPpsObject::writeMessage(const QString &msg, const QVariantMap &dat)
{
    // Treat empty msg as an encoding error
    if (msg.isEmpty())
        return -1;

    bool ok;
    QByteArray byteArray = encodeMessage(msg, dat, &ok);

    if (!ok)
        return -1;

    ok = write(byteArray);
    if (!ok)
        return error();

    return EOK;
}

int QPpsObject::writeMessage(const QString &msg, const QString &id, const QVariantMap &dat)
{
    // Treat empty msg or id as an encoding error
    if (msg.isEmpty() || id.isEmpty())
        return -1;

    bool ok;
    QByteArray byteArray = encodeMessage(msg, id, dat, &ok);

    if (!ok)
        return -1;

    ok = write(byteArray);
    if (!ok)
        return error();

    return EOK;
}

bool QPpsObject::remove()
{
    Q_D(QPpsObject);

    // reset last error
    d->error = EOK;

    // delete pps file
    const int result = unlink(d->path.toUtf8().data());

    // check success of operation
    if (result != 0) {
        d->error = errno;
        return false;
    }
    return true;
}

// static
QVariantMap QPpsObject::decode(const QByteArray &rawData, bool *ok)
{
    QPpsAttributeMap mapData = decodeWithFlags(rawData, 0, ok);

    // If *ok is false, then mapData is empty, so the resulting QVariantMap
    // will also be empty, as desired.
    return QPpsObjectPrivate::variantMapFromPpsAttributeMap(mapData);
}

// static
QPpsAttributeMap QPpsObject::decodeWithFlags(const QByteArray &rawData, bool *ok)
{
    return QPpsObject::decodeWithFlags(rawData, 0, ok);
}

// static
QPpsAttributeMap QPpsObject::decodeWithFlags(const QByteArray &rawData,
    QPpsAttribute *objectAttribute, bool *ok)
{
    safeAssign(ok, true);

    bool success = false;
    QPpsAttributeMap mapData =  QPpsObjectPrivate::decode(rawData, &success);
    if (!success) {
        safeAssign(ok, false);
        return QPpsAttributeMap();
    }

    // The object name is the key of the first element, and the flags of that attribute
    // give the status for the object as a whole.
    if (!mapData.isEmpty() && objectAttribute) {
        QString extractedName = mapData.begin().key();
        QPpsAttribute topmostAttribute = mapData.begin().value();
        QPpsAttribute::Flags topmostFlags = topmostAttribute.flags();
        QPpsAttribute toplevelAttribute =
            QPpsAttributePrivate::createPpsAttribute(extractedName, topmostFlags);
        *objectAttribute = toplevelAttribute;
    }

    return mapData;
}


// static
QByteArray QPpsObject::encode(const QVariantMap &ppsData, bool *ok)
{
    safeAssign(ok, true);

    bool success = false;
    QByteArray byteArray = QPpsObjectPrivate::encode(ppsData, &success);
    if (!success) {
        safeAssign(ok, false);
        return QByteArray();
    }
    return byteArray;
}

// static
QByteArray QPpsObject::encodeMessage(const QString &msg, const QVariantMap &dat, bool *ok)
{
    safeAssign(ok, true);

    // Treat empty msg as an encoding error
    if (msg.isEmpty()) {
        safeAssign(ok, false);
        return QByteArray();
    }

    QVariantMap ppsData;
    ppsData[QStringLiteral("msg")] = msg;
    ppsData[QStringLiteral("dat")] = dat;

    return QPpsObject::encode(ppsData, ok);
}

// static
QByteArray QPpsObject::encodeMessage(const QString &msg, const QString &id, const QVariantMap &dat,
                                     bool *ok)
{
    safeAssign(ok, true);

    // Treat empty msg or id as an encoding error
    if (msg.isEmpty() || id.isEmpty()) {
        safeAssign(ok, false);
        return QByteArray();
    }

    QVariantMap ppsData;
    ppsData[QStringLiteral("msg")] = msg;
    ppsData[QStringLiteral("id")] = id;
    ppsData[QStringLiteral("dat")] = dat;

    return QPpsObject::encode(ppsData, ok);
}

// static
int QPpsObject::sendMessage(const QString &path, const QString &message)
{
    QPpsObject pps(path);

    bool ok = pps.open(QPpsObject::Publish);
    if (!ok)
        return pps.error();

    ok = pps.write(message.toLocal8Bit());
    if (!ok)
        return pps.error();

    return EOK;
}

// static
int QPpsObject::sendMessage(const QString &path, const QVariantMap &message)
{
    QPpsObject pps(path);

    bool ok = pps.open(QPpsObject::Publish);
    if (!ok)
        return pps.error();

    QByteArray payload = QPpsObject::encode(message, &ok);
    if (!ok)
        return -1;

    ok = pps.write(payload);
    if (!ok)
        return pps.error();

    return EOK;
}

// static
int QPpsObject::sendMessage(const QString &path, const QString &msg, const QVariantMap &dat)
{
    // Treat empty msg as an encoding error
    if (msg.isEmpty())
        return -1;

    QPpsObject pps(path);

    bool ok = pps.open(QPpsObject::Publish);
    if (!ok)
        return pps.error();

    QByteArray payload = QPpsObject::encodeMessage(msg, dat, &ok);
    if (!ok)
        return -1;

    ok = pps.write(payload);
    if (!ok)
        return pps.error();

    return EOK;
}

// static
int QPpsObject::sendMessage(const QString &path, const QByteArray &ppsData)
{
    QPpsObject pps(path);

    bool ok = pps.open(QPpsObject::Publish);
    if (!ok)
        return pps.error();

    ok = pps.write(ppsData);
    if (!ok)
        return pps.error();

    return EOK;
}

QT_END_NAMESPACE
