/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $QT_BEGIN_LICENSE:COMM$
**
** 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.
**
** $QT_END_LICENSE$
**
**
**
**
**
**
**
**
**
****************************************************************************/

#ifndef QSSGMESHUTILITIES_P_H
#define QSSGMESHUTILITIES_P_H

//
//  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.
//

#include <QtQuick3DAssetImport/private/qtquick3dassetimportglobal_p.h>

#include <QtQuick3DUtils/private/qssgbounds3_p.h>

#include <QtQuick3DRender/private/qssgrenderbasetypes_p.h>

#include <QtCore/QString>
#include <QtCore/QByteArray>
#include <QtCore/QIODevice>
#include <QtCore/QFile>

QT_BEGIN_NAMESPACE

namespace QSSGMeshUtilities {

struct MeshData
{
    // All enums must match the ones defined by QSSGRenderGeometry class in quick3d module
    enum PrimitiveType { // Must match also internal QSSGRenderDrawMode
        UnknownType = 0,
        Points,
        LineStrip,
        LineLoop,
        Lines,
        TriangleStrip,
        TriangleFan,
        Triangles, // Default primitive type
        Patches
    };

    struct Attribute {
        enum Semantic {
            UnknownSemantic = 0,
            IndexSemantic,
            PositionSemantic, // attr_pos
            NormalSemantic,   // attr_norm
            TexCoordSemantic, // attr_uv0
            TangentSemantic,  // attr_textan
            BinormalSemantic  // attr_binormal
        };
        enum ComponentType { // Must match also internal QSSGRenderComponentType
            DefaultType = 0,
            U8Type,
            I8Type,
            U16Type,
            I16Type,
            U32Type, // Default for IndexSemantic
            I32Type,
            U64Type,
            I64Type,
            F16Type,
            F32Type, // Default for other semantics
            F64Type
        };

        int typeSize() const
        {
            switch (componentType) {
            case U8Type:  return 1;
            case I8Type:  return 1;
            case U16Type: return 2;
            case I16Type: return 2;
            case U32Type: return 4;
            case I32Type: return 4;
            case U64Type: return 8;
            case I64Type: return 8;
            case F16Type: return 2;
            case F32Type: return 4;
            case F64Type: return 8;
            default:
                Q_ASSERT(false);
                return 0;
            }
        }

        int componentCount() const
        {
            switch (semantic) {
            case IndexSemantic:    return 1;
            case PositionSemantic: return 3;
            case NormalSemantic:   return 3;
            case TexCoordSemantic: return 2;
            case TangentSemantic:  return 3;
            case BinormalSemantic: return 3;
            default:
                Q_ASSERT(false);
                return 0;
            }
        }

        Semantic semantic = PositionSemantic;
        ComponentType componentType = F32Type;
        int offset = 0;
    };

    static const int MAX_ATTRIBUTES = 6;

    void clear()
    {
        m_vertexBuffer.clear();
        m_indexBuffer.clear();
        m_attributeCount = 0;
        m_primitiveType = Triangles;
    }

    QByteArray m_vertexBuffer;
    QByteArray m_indexBuffer;

    Attribute m_attributes[MAX_ATTRIBUTES];
    int m_attributeCount = 0;
    PrimitiveType m_primitiveType = Triangles;
    int m_stride = 0;
};

template<typename DataType>
struct OffsetDataRef
{
    quint32 m_offset;
    quint32 m_size;
    OffsetDataRef() : m_offset(0), m_size(0) {}
    DataType *begin(quint8 *inBase)
    {
        DataType *value = reinterpret_cast<DataType *>(inBase + m_offset);
        return value;
    }
    DataType *end(quint8 *inBase) { return begin(inBase) + m_size; }
    const DataType *begin(const quint8 *inBase) const { return reinterpret_cast<const DataType *>(inBase + m_offset); }
    const DataType *end(const quint8 *inBase) const { return begin(inBase) + m_size; }
    quint32 size() const { return m_size; }
    bool empty() const { return m_size == 0; }
    DataType &index(quint8 *inBase, quint32 idx)
    {
        Q_ASSERT(idx < m_size);
        return begin(inBase)[idx];
    }
    const DataType &index(const quint8 *inBase, quint32 idx) const
    {
        Q_ASSERT(idx < m_size);
        return begin(inBase)[idx];
    }
};

struct MeshVertexBufferEntry
{
    quint32 m_nameOffset;
    /** Datatype of the this entry points to in the buffer */
    QSSGRenderComponentType m_componentType;
    /** Number of components of each data member. 1,2,3, or 4.  Don't be stupid.*/
    quint32 m_numComponents;
    /** Offset from the beginning of the buffer of the first item */
    quint32 m_firstItemOffset;
    MeshVertexBufferEntry()
        : m_nameOffset(0), m_componentType(QSSGRenderComponentType::Float32), m_numComponents(3), m_firstItemOffset(0)
    {
    }
    QSSGRenderVertexBufferEntry toVertexBufferEntry(quint8 *inBaseAddress) const
    {
        const char *nameBuffer = "";
        if (m_nameOffset)
            nameBuffer = reinterpret_cast<const char *>(inBaseAddress + m_nameOffset);
        return QSSGRenderVertexBufferEntry(nameBuffer, m_componentType, m_numComponents, m_firstItemOffset);
    }
};

struct VertexBuffer
{
    OffsetDataRef<MeshVertexBufferEntry> m_entries;
    quint32 m_stride;
    OffsetDataRef<quint8> m_data;
    VertexBuffer(OffsetDataRef<MeshVertexBufferEntry> entries, quint32 stride, OffsetDataRef<quint8> data)
        : m_entries(entries), m_stride(stride), m_data(data)
    {
    }
    VertexBuffer() : m_stride(0) {}
};

struct IndexBuffer
{
    // Component types must be either UnsignedInt16 or UnsignedInt8 in order for the
    // graphics hardware to deal with the buffer correctly.
    QSSGRenderComponentType m_componentType;
    OffsetDataRef<quint8> m_data;
    // Either quint8 or quint16 component types are allowed by the underlying rendering
    // system, so you would be wise to stick with those.
    IndexBuffer(QSSGRenderComponentType compType, OffsetDataRef<quint8> data)
        : m_componentType(compType), m_data(data)
    {
    }
    IndexBuffer() : m_componentType(QSSGRenderComponentType::Unknown) {}
};

template<quint32 TNumBytes>
struct MeshPadding
{
    quint8 m_padding[TNumBytes];
    MeshPadding() { memZero(m_padding, TNumBytes); }
};

struct Vec3
{
    float x;
    float y;
    float z;
};

struct MeshSubset
{
    // std::numeric_limits<quint32>::max() means use all available items
    quint32 m_count;
    // Offset is in item size, not bytes.
    quint32 m_offset;
    // Bounds of this subset.  This is filled in by the builder
    // see AddMeshSubset
    QSSGBounds3 m_bounds;

    // Subsets have to be named else artists will be unable to use
    // a mesh with multiple subsets as they won't have any idea
    // while part of the model a given mesh actually maps to.
    OffsetDataRef<char16_t> m_name;

    MeshSubset(quint32 count, quint32 off, const QSSGBounds3 &bounds, OffsetDataRef<char16_t> inName)
        : m_count(count), m_offset(off), m_bounds(bounds), m_name(inName)
    {
    }
    MeshSubset() : m_count(quint32(-1)), m_offset(0), m_bounds() {}
    bool hasCount() const { return m_count != 4294967295U; } // AKA U_MAX 0xffffffff
};

struct Joint
{
    qint32 m_jointID;
    qint32 m_parentID;
    float m_invBindPose[16];
    float m_localToGlobalBoneSpace[16];

    Joint(qint32 jointID, qint32 parentID, const float *invBindPose, const float *localToGlobalBoneSpace)
        : m_jointID(jointID), m_parentID(parentID)
    {
        ::memcpy(m_invBindPose, invBindPose, sizeof(float) * 16);
        ::memcpy(m_localToGlobalBoneSpace, localToGlobalBoneSpace, sizeof(float) * 16);
    }
    Joint() : m_jointID(-1), m_parentID(-1)
    {
        ::memset(m_invBindPose, 0, sizeof(float) * 16);
        ::memset(m_localToGlobalBoneSpace, 0, sizeof(float) * 16);
    }
};

// Tells us what offset a mesh with this ID starts.
struct MeshMultiEntry
{
    quint64 m_meshOffset;
    quint32 m_meshId;
    quint32 m_padding;
    MeshMultiEntry() : m_meshOffset(0), m_meshId(0), m_padding(0) {}
    MeshMultiEntry(quint64 mo, quint32 meshId) : m_meshOffset(mo), m_meshId(meshId), m_padding(0) {}
};

// The multi headers are actually saved at the end of the file.
// Thus when you append to the file we overwrite the last header
// then write out a new header structure.
// The last 8 bytes of the file contain the multi header.
// The previous N*8 bytes contain the mesh entries.
struct MeshMultiHeader
{
    quint32 m_fileId;
    quint32 m_version;
    OffsetDataRef<MeshMultiEntry> m_entries;
    static quint32 getMultiStaticFileId() { return 555777497U; }
    static quint32 getMultiStaticVersion() { return 1; }

    MeshMultiHeader() : m_fileId(getMultiStaticFileId()), m_version(getMultiStaticVersion()) {}
};

struct Mesh;

// Result of a multi-load operation.  This returns both the mesh
// and the id of the mesh that was loaded.
struct MultiLoadResult
{
    Mesh *m_mesh;
    quint32 m_id;
    MultiLoadResult(Mesh *inMesh, quint32 inId) : m_mesh(inMesh), m_id(inId) {}
    MultiLoadResult() : m_mesh(nullptr), m_id(0) {}
    operator Mesh *() { return m_mesh; }
};

struct Q_QUICK3DASSETIMPORT_EXPORT Mesh
{
    static const char16_t *m_defaultName;

    VertexBuffer m_vertexBuffer;
    IndexBuffer m_indexBuffer;
    OffsetDataRef<MeshSubset> m_subsets;
    OffsetDataRef<Joint> m_joints;
    QSSGRenderDrawMode m_drawMode;
    QSSGRenderWinding m_winding;

    Mesh() : m_drawMode(QSSGRenderDrawMode::Triangles), m_winding(QSSGRenderWinding::CounterClockwise) {}
    Mesh(VertexBuffer vbuf,
         IndexBuffer ibuf,
         const OffsetDataRef<MeshSubset> &insts,
         const OffsetDataRef<Joint> &joints,
         QSSGRenderDrawMode drawMode = QSSGRenderDrawMode::Triangles,
         QSSGRenderWinding winding = QSSGRenderWinding::CounterClockwise)
        : m_vertexBuffer(vbuf), m_indexBuffer(ibuf), m_subsets(insts), m_joints(joints), m_drawMode(drawMode), m_winding(winding)
    {
    }

    quint8 *getBaseAddress() { return reinterpret_cast<quint8 *>(this); }
    const quint8 *getBaseAddress() const { return reinterpret_cast<const quint8 *>(this); }

    static const char *getPositionAttrName() { return "attr_pos"; }
    static const char *getNormalAttrName() { return "attr_norm"; }
    static const char *getUVAttrName() { return "attr_uv0"; }
    static const char *getUV2AttrName() { return "attr_uv1"; }
    static const char *getTexTanAttrName() { return "attr_textan"; }
    static const char *getTexBinormalAttrName() { return "attr_binormal"; }
    static const char *getWeightAttrName() { return "attr_weight"; }
    static const char *getBoneIndexAttrName() { return "attr_boneid"; }
    static const char *getColorAttrName() { return "attr_color"; }

    // Run through the vertex buffer items indicated by subset
    // Assume vbuf entry[posEntryIndex] is the position entry
    // This entry has to be QT3DSF32 and 3 components.
    // Using this entry and the (possibly empty) index buffer
    // along with the (possibly emtpy) logical vbuf data
    // return a bounds of the given vertex buffer.
    static QSSGBounds3 calculateSubsetBounds(const QSSGRenderVertexBufferEntry &inEntry,
                                               const QByteArray &inVertxData,
                                               quint32 inStride,
                                               const QByteArray &inIndexData,
                                               QSSGRenderComponentType inIndexCompType,
                                               quint32 inSubsetCount,
                                               quint32 inSubsetOffset);

    // Format is:
    // MeshDataHeader
    // mesh data.

    void save(QIODevice &outStream) const;

    // Save a mesh using fopen and fwrite
    bool save(const char *inFilePath) const;

    // read the header, then read the object.
    // Load a mesh using fopen and fread
    // Mesh needs to be freed by the caller using free
    static Mesh *load(QIODevice &inStream);
    static Mesh *load(const char *inFilePath);

    // Create a mesh given this header, and that data.  data.size() must match
    // header.SizeInBytes.  The mesh returned starts a data[0], so however data
    // was allocated is how the mesh should be deallocated.
    static Mesh *initialize(quint16 meshVersion, quint16 meshFlags, QSSGByteView data);

    // You can save multiple meshes in a file.  Each mesh returns an incrementing
    // integer for the multi file.  The original meshes aren't changed, and the file
    // is appended to.
    quint32 saveMulti(QIODevice &inStream, quint32 inId = 0) const;
    quint32 saveMulti(const char *inFilePath) const;

    // Load a single mesh using c file API and malloc/free.
    static MultiLoadResult loadMulti(QIODevice &inStream, quint32 inId);
    static MultiLoadResult loadMulti(const char *inFilePath, quint32 inId);

    // Returns true if this is a multimesh (several meshes in one file).
    static bool isMulti(QIODevice &inStream);

    // Load a multi header from a file using malloc.  Header needs to be freed using free.
    static MeshMultiHeader *loadMultiHeader(QIODevice &inStream);
    static MeshMultiHeader *loadMultiHeader(const char *inFilePath);

    // Get the highest mesh version from a file.
    static quint32 getHighestMultiVersion(QIODevice &inStream);
    static quint32 getHighestMultiVersion(const char *inFilePath);
};

struct MeshDataHeader
{
    static quint32 getFileId() { return quint32(-929005747); }
    static quint16 getCurrentFileVersion() { return 3; }
    quint32 m_fileId;
    quint16 m_fileVersion;
    quint16 m_headerFlags;
    quint32 m_sizeInBytes;
    MeshDataHeader(quint32 size = 0)
        : m_fileId(getFileId()), m_fileVersion(getCurrentFileVersion()), m_sizeInBytes(size)
    {
    }
};

struct MeshBuilderVBufEntry
{
    const char *m_name;
    QByteArray m_data;
    QSSGRenderComponentType m_componentType;
    quint32 m_numComponents;
    MeshBuilderVBufEntry() : m_name(nullptr), m_componentType(QSSGRenderComponentType::Unknown), m_numComponents(0)
    {
    }
    MeshBuilderVBufEntry(const char *name, const QByteArray &data, QSSGRenderComponentType componentType, quint32 numComponents)
        : m_name(name), m_data(data), m_componentType(componentType), m_numComponents(numComponents)
    {
    }
};

// Useful class to build up a mesh.  Necessary since meshes don't include that
// sort of utility.
class Q_QUICK3DASSETIMPORT_EXPORT QSSGMeshBuilder
{
public:
    QAtomicInt ref;
    virtual ~QSSGMeshBuilder();
    virtual void release() = 0;
    virtual void reset() = 0;
    // Set the draw parameters for any subsets.  Defaults to triangles and counter clockwise
    virtual void setDrawParameters(QSSGRenderDrawMode drawMode, QSSGRenderWinding winding) = 0;
    // Set the vertex buffer and have the mesh builder interleave the data for you
    virtual bool setVertexBuffer(const QVector<MeshBuilderVBufEntry> &entries) = 0;
    // Set the vertex buffer from interleaved data.
    virtual void setVertexBuffer(const QVector<QSSGRenderVertexBufferEntry> &entries, quint32 stride, QByteArray data) = 0;
    // The builder (and the majority of the rest of the product) only supports unsigned 16 bit
    // indexes
    virtual void setIndexBuffer(const QByteArray &data, QSSGRenderComponentType comp) = 0;
    // Assets if the supplied parameters are out of range.
    virtual void addJoint(qint32 jointID, qint32 parentID, const float *invBindPose, const float *localToGlobalBoneSpace) = 0;
    /**
     *  Add a subset, which equates roughly to a draw call.
     *  A logical vertex buffer allows you to have more that 64K vertexes but still
     *  use u16 index buffers.  In any case, if the mesh has an index buffer then this subset
     *  refers to that index buffer, else it is assumed to index into the vertex buffer.
     *  count and offset do exactly what they seem to do, while boundsPositionEntryIndex,
     *  if set to something other than std::numeric_limits<quint32>::max(),
     *  drives the calculation of the aa-bounds of the subset using mesh::CalculateSubsetBounds.
     */
    virtual void addMeshSubset(const char16_t *inSubsetName = Mesh::m_defaultName,
                               quint32 count = std::numeric_limits<quint32>::max(),
                               quint32 offset = 0,
                               quint32 boundsPositionEntryIndex = std::numeric_limits<quint32>::max()) = 0;

    virtual void addMeshSubset(const char16_t *inSubsetName, quint32 count, quint32 offset, const QSSGBounds3 &inBounds) = 0;

    // Call to optimize the index and vertex buffers.  This doesn't change the subset information,
    // each triangle is rendered precisely the same.
    // It just orders the vertex data so we iterate through it as linearly as possible.
    // This *only* works if the *entire* builder is using triangles as the draw mode.  This will be
    // a disaster if that condition is not met.
    virtual void optimizeMesh() = 0;

    /**
     * @brief This functions stitches together sub-meshes with the same material.
     *		 This re-writes the index buffer
     *
     * @return no return.
     */
    virtual void connectSubMeshes() = 0;

    // Return the current mesh.  This is only good for this function call, item may change or be
    // released
    // due to any further function calls.
    virtual Mesh &getMesh() = 0;

    virtual Mesh *buildMesh(const MeshData &data, QString &error, const QSSGBounds3 &inBounds) = 0;

    // Uses new/delete.
    static QSSGRef<QSSGMeshBuilder> createMeshBuilder();
};

} // end QSSGMeshUtilities namespace

QT_END_NAMESPACE

#endif // QSSGMESHUTILITIES_P_H
