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

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#ifndef QQSBCOLLECTION_H
#define QQSBCOLLECTION_H

#include <QtQuick3DUtils/private/qtquick3dutilsglobal_p.h>

#include <QtCore/qfile.h>
#include <QtCore/qset.h>
#include <QtCore/qmap.h>

#include <QtGui/private/qshader_p.h>

QT_BEGIN_NAMESPACE

class QRhiShaderStage;

class Q_QUICK3DUTILS_EXPORT QQsbCollection
{
public:
    virtual ~QQsbCollection();

    struct Q_QUICK3DUTILS_EXPORT Entry
    {
        // 'value' is optional. hashing and comparison are based solely on 'key'.
        Entry() = default;
        explicit Entry(const QByteArray &key) : key(key)
        {
            hashKey = qHash(key);
        }
        Entry(const QByteArray &key, qint64 value) : key(key), value(value)
        {
            hashKey = qHash(key);
        }
        bool isValid() const { return !key.isEmpty(); }
        QByteArray key;
        qint64 value = -1;
        size_t hashKey;
    };

    using FeatureSet = QMap<QByteArray, bool>; // QMap so it is ordered by key

    template<typename T>
    static FeatureSet toFeatureSet(const T &ssgFeatureSet)
    {
        FeatureSet ret;
        for (quint32 i = 0, end = T::Count; i != end; ++i) {
            auto def = T::fromIndex(i);
            if (ssgFeatureSet.isSet(def))
                ret.insert(T::asDefineString(def), true);
        }
        return ret;
    }

    struct Q_QUICK3DUTILS_EXPORT EntryDesc {
        QByteArray materialKey;
        FeatureSet featureSet;
        QShader vertShader;
        QShader fragShader;
        QByteArray generateSha() const;
        static QByteArray generateSha(const QByteArray &materialKey, const FeatureSet &featureSet);
    };

    using EntryMap = QSet<Entry>;
    virtual EntryMap availableEntries() const = 0;
    virtual Entry addEntry(const QByteArray &key, const EntryDesc &entryDesc) = 0;
    virtual bool extractEntry(Entry entry, EntryDesc &entryDesc) = 0;

protected:
    enum Version : quint8
    {
        Unknown,
        One = 0x10,
        Two = 0x20
    };
    bool readEndHeader(QDataStream &ds, qint64 *startPos, quint8 *version);
    void writeEndHeader(QDataStream &ds, qint64 startPos, quint8 version, quint64 magic);
    bool readEndHeader(QIODevice *device, EntryMap *entries, quint8 *version);
    void writeEndHeader(QIODevice *device, const EntryMap &entries);
};

Q_DECLARE_TYPEINFO(QQsbCollection::Entry, Q_PRIMITIVE_TYPE);

Q_QUICK3DUTILS_EXPORT QDataStream &operator<<(QDataStream &stream, const QQsbCollection::Entry &entry);
Q_QUICK3DUTILS_EXPORT QDataStream &operator>>(QDataStream &stream, QQsbCollection::Entry &entry);
Q_QUICK3DUTILS_EXPORT QDataStream &operator<<(QDataStream &stream, const QQsbCollection::EntryDesc &entryDesc);
Q_QUICK3DUTILS_EXPORT QDataStream &operator>>(QDataStream &stream, QQsbCollection::EntryDesc &entryDesc);

Q_QUICK3DUTILS_EXPORT size_t qHash(const QQsbCollection::Entry &entry, size_t);
Q_QUICK3DUTILS_EXPORT bool operator==(const QQsbCollection::Entry &l, const QQsbCollection::Entry &r);

// Simple implementation backed by a hash table. Save and load are explicit and
// all data is read and written. The file format is compatible with other
// implementations.
class Q_QUICK3DUTILS_EXPORT QQsbInMemoryCollection : public QQsbCollection
{
public:
    QQsbInMemoryCollection() = default;

    EntryMap availableEntries() const override;
    Entry addEntry(const QByteArray &key, const EntryDesc &entryDesc) override;
    bool extractEntry(Entry entry, EntryDesc &entryDesc) override;

    void clear();

    bool load(const QString &filename);
    bool save(const QString &filename);

private:
    Q_DISABLE_COPY(QQsbInMemoryCollection);

    QHash<Entry, EntryDesc> entries;
};

// Serial, direct-to/from-QIODevice implementation.
class Q_QUICK3DUTILS_EXPORT QQsbIODeviceCollection : public QQsbCollection
{
public:
    enum MapMode
    {
        Read = QIODevice::ReadOnly,
        Write = (QIODevice::WriteOnly | QIODevice::Truncate)
    };

    explicit QQsbIODeviceCollection(const QString &filePath);
    explicit QQsbIODeviceCollection(QIODevice &dev);
    ~QQsbIODeviceCollection();

    bool map(MapMode mode);
    void unmap();

    EntryMap availableEntries() const override;
    Entry addEntry(const QByteArray &key, const EntryDesc &entryDesc) override;
    bool extractEntry(Entry entry, EntryDesc &entryDesc) override;

    void dumpInfo();
    static void dumpInfo(const QString &device);
    static void dumpInfo(QIODevice &device);

private:
    Q_DISABLE_COPY(QQsbIODeviceCollection);

    enum class DeviceOwner : quint8
    {
        Self,
        Extern
    };
    QFile file;
    QIODevice &device;
    DeviceOwner devOwner = DeviceOwner::Self;
    quint8 version = Version::Unknown;
    EntryMap entries;
};

QT_END_NAMESPACE

#endif // QQSBCOLLECTION_H
