/****************************************************************************
**
** Copyright (C) 2018 Intel Corporation.
** 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$
**
****************************************************************************/

#ifndef QCBORSTREAM_H
#define QCBORSTREAM_H

#include <QtCore/qbytearray.h>
#include <QtCore/qcborcommon.h>
#include <QtCore/qfloat16.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qstring.h>
#include <QtCore/qstringview.h>

// See qcborcommon.h for why we check
#if defined(QT_X11_DEFINES_FOUND)
#  undef True
#  undef False
#endif

QT_BEGIN_NAMESPACE

class QIODevice;

enum class QCborNegativeInteger : quint64 {};

class QCborStreamWriterPrivate;
class Q_CORE_EXPORT QCborStreamWriter
{
public:
    explicit QCborStreamWriter(QIODevice *device);
    explicit QCborStreamWriter(QByteArray *data);
    ~QCborStreamWriter();
    Q_DISABLE_COPY(QCborStreamWriter)

    void setDevice(QIODevice *device);
    QIODevice *device() const;

    void append(quint64 u);
    void append(qint64 i);
    void append(QCborNegativeInteger n);
    void append(const QByteArray &ba)       { appendByteString(ba.constData(), ba.size()); }
    void append(QLatin1String str);
    void append(QStringView str);
    void append(QCborTag tag);
    void append(QCborKnownTags tag)         { append(QCborTag(tag)); }
    void append(QCborSimpleType st);
    void append(std::nullptr_t)             { append(QCborSimpleType::Null); }
    void append(qfloat16 f);
    void append(float f);
    void append(double d);

    void appendByteString(const char *data, qsizetype len);
    void appendTextString(const char *utf8, qsizetype len);

    // convenience
    void append(bool b)     { append(b ? QCborSimpleType::True : QCborSimpleType::False); }
    void appendNull()       { append(QCborSimpleType::Null); }
    void appendUndefined()  { append(QCborSimpleType::Undefined); }

#ifndef Q_QDOC
    // overloads to make normal code not complain
    void append(int i)      { append(qint64(i)); }
    void append(uint u)     { append(quint64(u)); }
#endif
#ifndef QT_NO_CAST_FROM_ASCII
    void append(const char *str, qsizetype size = -1)
    { appendTextString(str, (str && size == -1)  ? int(strlen(str)) : size); }
#endif

    void startArray();
    void startArray(quint64 count);
    bool endArray();
    void startMap();
    void startMap(quint64 count);
    bool endMap();

    // no API for encoding chunked strings

private:
    QScopedPointer<QCborStreamWriterPrivate> d;
};

class QCborStreamReaderPrivate;
class Q_CORE_EXPORT QCborStreamReader
{
    Q_GADGET
public:
    enum Type : quint8 {
        UnsignedInteger     = 0x00,
        NegativeInteger     = 0x20,
        ByteString          = 0x40,
        ByteArray           = ByteString,
        TextString          = 0x60,
        String              = TextString,
        Array               = 0x80,
        Map                 = 0xa0,
        Tag                 = 0xc0,
        SimpleType          = 0xe0,
        HalfFloat           = 0xf9,
        Float16             = HalfFloat,
        Float               = 0xfa,
        Double              = 0xfb,

        Invalid             = 0xff
    };
    Q_ENUM(Type)

    enum StringResultCode {
        EndOfString = 0,
        Ok = 1,
        Error = -1
    };
    template <typename Container> struct StringResult {
        Container data;
        StringResultCode status = Error;
    };
    Q_ENUM(StringResultCode)

    QCborStreamReader();
    QCborStreamReader(const char *data, qsizetype len);
    QCborStreamReader(const quint8 *data, qsizetype len);
    explicit QCborStreamReader(const QByteArray &data);
    explicit QCborStreamReader(QIODevice *device);
    ~QCborStreamReader();
    Q_DISABLE_COPY(QCborStreamReader)

    void setDevice(QIODevice *device);
    QIODevice *device() const;
    void addData(const QByteArray &data);
    void addData(const char *data, qsizetype len);
    void addData(const quint8 *data, qsizetype len)
    { addData(reinterpret_cast<const char *>(data), len); }
    void reparse();
    void clear();
    void reset();

    QCborError lastError();

    qint64 currentOffset() const;

    bool isValid() const        { return !isInvalid(); }

    int containerDepth() const;
    QCborStreamReader::Type parentContainerType() const;
    bool hasNext() const noexcept Q_DECL_PURE_FUNCTION;
    bool next(int maxRecursion = 10000);

    Type type() const               { return QCborStreamReader::Type(type_); }
    bool isUnsignedInteger() const  { return type() == UnsignedInteger; }
    bool isNegativeInteger() const  { return type() == NegativeInteger; }
    bool isInteger() const          { return quint8(type()) <= quint8(NegativeInteger); }
    bool isByteArray() const        { return type() == ByteArray; }
    bool isString() const           { return type() == String; }
    bool isArray() const            { return type() == Array; }
    bool isMap() const              { return type() == Map; }
    bool isTag() const              { return type() == Tag; }
    bool isSimpleType() const       { return type() == SimpleType; }
    bool isFloat16() const          { return type() == Float16; }
    bool isFloat() const            { return type() == Float; }
    bool isDouble() const           { return type() == Double; }
    bool isInvalid() const          { return type() == Invalid; }

    bool isSimpleType(QCborSimpleType st) const { return isSimpleType() && toSimpleType() == st; }
    bool isFalse() const            { return isSimpleType(QCborSimpleType::False); }
    bool isTrue() const             { return isSimpleType(QCborSimpleType::True); }
    bool isBool() const             { return isFalse() || isTrue(); }
    bool isNull() const             { return isSimpleType(QCborSimpleType::Null); }
    bool isUndefined() const        { return isSimpleType(QCborSimpleType::Undefined); }

    bool isLengthKnown() const noexcept Q_DECL_PURE_FUNCTION;
    quint64 length() const;

    bool isContainer() const            { return isMap() || isArray(); }
    bool enterContainer()               { Q_ASSERT(isContainer()); return _enterContainer_helper(); }
    bool leaveContainer();

    StringResult<QString> readString()      { Q_ASSERT(isString()); return _readString_helper(); }
    StringResult<QByteArray> readByteArray(){ Q_ASSERT(isByteArray()); return _readByteArray_helper(); }
    qsizetype currentStringChunkSize() const{ Q_ASSERT(isString() || isByteArray()); return _currentStringChunkSize(); }
    StringResult<qsizetype> readStringChunk(char *ptr, qsizetype maxlen);

    bool toBool() const                 { Q_ASSERT(isBool()); return value64 - int(QCborSimpleType::False); }
    QCborTag toTag() const              { Q_ASSERT(isTag()); return QCborTag(value64); }
    quint64 toUnsignedInteger() const   { Q_ASSERT(isUnsignedInteger()); return value64; }
    QCborNegativeInteger toNegativeInteger() const { Q_ASSERT(isNegativeInteger()); return QCborNegativeInteger(value64 + 1); }
    QCborSimpleType toSimpleType() const{ Q_ASSERT(isSimpleType()); return QCborSimpleType(value64); }
    qfloat16 toFloat16() const          { Q_ASSERT(isFloat16()); return _toFloatingPoint<qfloat16>(); }
    float toFloat() const               { Q_ASSERT(isFloat()); return _toFloatingPoint<float>(); }
    double toDouble() const             { Q_ASSERT(isDouble()); return _toFloatingPoint<double>(); }

    qint64 toInteger() const
    {
        Q_ASSERT(isInteger());
        qint64 v = qint64(value64);
        if (isNegativeInteger())
            return -v - 1;
        return v;
    }

private:
    void preparse();
    bool _enterContainer_helper();
    StringResult<QString> _readString_helper();
    StringResult<QByteArray> _readByteArray_helper();
    qsizetype _currentStringChunkSize() const;

    template <typename FP> FP _toFloatingPoint() const noexcept
    {
        using UInt = typename QIntegerForSizeof<FP>::Unsigned;
        UInt u = UInt(value64);
        FP f;
        memcpy(static_cast<void *>(&f), &u, sizeof(f));
        return f;
    }

    friend QCborStreamReaderPrivate;
    friend class QCborContainerPrivate;
    quint64 value64;
    QScopedPointer<QCborStreamReaderPrivate> d;
    quint8 type_;
    quint8 reserved[3] = {};
};

QT_END_NAMESPACE

#if defined(QT_X11_DEFINES_FOUND)
#  define True  1
#  define False 0
#endif

#endif // QCBORSTREAM_H
