/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------

Copyright (c) 2006-2024, assimp team


All rights reserved.

Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.

* Redistributions in binary form must reproduce the above
  copyright notice, this list of conditions and the
  following disclaimer in the documentation and/or other
  materials provided with the distribution.

* Neither the name of the assimp team, nor the names of its
  contributors may be used to endorse or promote products
  derived from this software without specific prior
  written permission of the assimp team.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

----------------------------------------------------------------------
*/

#include "OgreBinarySerializer.h"
#include "OgreParsingUtils.h"
#include "OgreXmlSerializer.h"

#include <assimp/TinyFormatter.h>
#include <assimp/DefaultLogger.hpp>

#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER

// Define as 1 to get verbose logging.
#define OGRE_BINARY_SERIALIZER_DEBUG 0

namespace Assimp {
namespace Ogre {

static constexpr auto MESH_VERSION_1_8 = "[MeshSerializer_v1.8]";
static constexpr auto SKELETON_VERSION_1_8 = "[Serializer_v1.80]";
static constexpr auto SKELETON_VERSION_1_1 = "[Serializer_v1.10]";

const unsigned short HEADER_CHUNK_ID = 0x1000;

const long MSTREAM_OVERHEAD_SIZE = sizeof(uint16_t) + sizeof(uint32_t);
const long MSTREAM_BONE_SIZE_WITHOUT_SCALE = MSTREAM_OVERHEAD_SIZE + sizeof(unsigned short) + (sizeof(float) * 7);
const long MSTREAM_KEYFRAME_SIZE_WITHOUT_SCALE = MSTREAM_OVERHEAD_SIZE + (sizeof(float) * 8);

template <>
inline bool OgreBinarySerializer::Read<bool>() {
    return (m_reader->GetU1() > 0);
}

template <>
inline char OgreBinarySerializer::Read<char>() {
    return static_cast<char>(m_reader->GetU1());
}

template <>
inline uint8_t OgreBinarySerializer::Read<uint8_t>() {
    return m_reader->GetU1();
}

template <>
inline uint16_t OgreBinarySerializer::Read<uint16_t>() {
    return m_reader->GetU2();
}

template <>
inline uint32_t OgreBinarySerializer::Read<uint32_t>() {
    return m_reader->GetU4();
}

template <>
inline float OgreBinarySerializer::Read<float>() {
    return m_reader->GetF4();
}

void OgreBinarySerializer::ReadBytes(char *dest, size_t numBytes) {
    ReadBytes(static_cast<void *>(dest), numBytes);
}

void OgreBinarySerializer::ReadBytes(uint8_t *dest, size_t numBytes) {
    ReadBytes(static_cast<void *>(dest), numBytes);
}

void OgreBinarySerializer::ReadBytes(void *dest, size_t numBytes) {
    m_reader->CopyAndAdvance(dest, numBytes);
}

uint8_t *OgreBinarySerializer::ReadBytes(size_t numBytes) {
    uint8_t *bytes = new uint8_t[numBytes];
    ReadBytes(bytes, numBytes);
    return bytes;
}

void OgreBinarySerializer::ReadVector(aiVector3D &vec) {
    m_reader->CopyAndAdvance(&vec.x, sizeof(float) * 3);
}

void OgreBinarySerializer::ReadQuaternion(aiQuaternion &quat) {
    float temp[4];
    m_reader->CopyAndAdvance(temp, sizeof(float) * 4);
    quat.x = temp[0];
    quat.y = temp[1];
    quat.z = temp[2];
    quat.w = temp[3];
}

bool OgreBinarySerializer::AtEnd() const {
    return (m_reader->GetRemainingSize() == 0);
}

std::string OgreBinarySerializer::ReadString(size_t len) {
    std::string str;
    str.resize(len);
    ReadBytes(&str[0], len);
    return str;
}

std::string OgreBinarySerializer::ReadLine() {
    std::string str;
    while (!AtEnd()) {
        char c = Read<char>();
        if (c == '\n')
            break;
        str += c;
    }
    return str;
}

uint16_t OgreBinarySerializer::ReadHeader(bool readLen) {
    uint16_t id = Read<uint16_t>();
    if (readLen)
        m_currentLen = Read<uint32_t>();

#if (OGRE_BINARY_SERIALIZER_DEBUG == 1)
    if (id != HEADER_CHUNK_ID) {
        ASSIMP_LOG_DEBUG((assetMode == AM_Mesh ? MeshHeaderToString(static_cast<MeshChunkId>(id)) : SkeletonHeaderToString(static_cast<SkeletonChunkId>(id))));
    }
#endif

    return id;
}

void OgreBinarySerializer::RollbackHeader() {
    m_reader->IncPtr(-MSTREAM_OVERHEAD_SIZE);
}

void OgreBinarySerializer::SkipBytes(size_t numBytes) {
#if (OGRE_BINARY_SERIALIZER_DEBUG == 1)
    ASSIMP_LOG_VERBOSE_DEBUG("Skipping ", numBytes, " bytes");
#endif

    m_reader->IncPtr(numBytes);
}

// Mesh

Mesh *OgreBinarySerializer::ImportMesh(MemoryStreamReader *stream) {
    OgreBinarySerializer serializer(stream, OgreBinarySerializer::AM_Mesh);

    uint16_t id = serializer.ReadHeader(false);
    if (id != HEADER_CHUNK_ID) {
        throw DeadlyImportError("Invalid Ogre Mesh file header.");
    }

    /// @todo Check what we can actually support.
    std::string version = serializer.ReadLine();
    if (version != MESH_VERSION_1_8) {
        throw DeadlyImportError("Mesh version ", version, " not supported by this importer. Run OgreMeshUpgrader tool on the file and try again.",
                                                    " Supported versions: ", MESH_VERSION_1_8);
    }

    Mesh *mesh = new Mesh();
    while (!serializer.AtEnd()) {
        id = serializer.ReadHeader();
        switch (id) {
            case M_MESH: {
                serializer.ReadMesh(mesh);
                break;
            }
        }
    }
    return mesh;
}

void OgreBinarySerializer::ReadMesh(Mesh *mesh) {
    mesh->hasSkeletalAnimations = Read<bool>();

    ASSIMP_LOG_VERBOSE_DEBUG("Reading Mesh");
    ASSIMP_LOG_VERBOSE_DEBUG("  - Skeletal animations: ", mesh->hasSkeletalAnimations ? "true" : "false");

    if (!AtEnd()) {
        uint16_t id = ReadHeader();
        while (!AtEnd() &&
                (id == M_GEOMETRY ||
                        id == M_SUBMESH ||
                        id == M_MESH_SKELETON_LINK ||
                        id == M_MESH_BONE_ASSIGNMENT ||
                        id == M_MESH_LOD ||
                        id == M_MESH_BOUNDS ||
                        id == M_SUBMESH_NAME_TABLE ||
                        id == M_EDGE_LISTS ||
                        id == M_POSES ||
                        id == M_ANIMATIONS ||
                        id == M_TABLE_EXTREMES)) {
            switch (id) {
                case M_GEOMETRY: {
                    mesh->sharedVertexData = new VertexData();
                    ReadGeometry(mesh->sharedVertexData);
                    break;
                }
                case M_SUBMESH: {
                    ReadSubMesh(mesh);
                    break;
                }
                case M_MESH_SKELETON_LINK: {
                    ReadMeshSkeletonLink(mesh);
                    break;
                }
                case M_MESH_BONE_ASSIGNMENT: {
                    ReadBoneAssignment(mesh->sharedVertexData);
                    break;
                }
                case M_MESH_LOD: {
                    ReadMeshLodInfo(mesh);
                    break;
                }
                case M_MESH_BOUNDS: {
                    ReadMeshBounds(mesh);
                    break;
                }
                case M_SUBMESH_NAME_TABLE: {
                    ReadSubMeshNames(mesh);
                    break;
                }
                case M_EDGE_LISTS: {
                    ReadEdgeList(mesh);
                    break;
                }
                case M_POSES: {
                    ReadPoses(mesh);
                    break;
                }
                case M_ANIMATIONS: {
                    ReadAnimations(mesh);
                    break;
                }
                case M_TABLE_EXTREMES: {
                    ReadMeshExtremes(mesh);
                    break;
                }
            }

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }

    NormalizeBoneWeights(mesh->sharedVertexData);
}

void OgreBinarySerializer::ReadMeshLodInfo(Mesh *mesh) {
    // Assimp does not acknowledge LOD levels as far as I can see it. This info is just skipped.
    // @todo Put this stuff to scene/mesh custom properties. If manual mesh the app can use the information.
    ReadLine(); // strategy name
    uint16_t numLods = Read<uint16_t>();
    bool manual = Read<bool>();

    /// @note Main mesh is considered as LOD 0, start from index 1.
    for (size_t i = 1; i < numLods; ++i) {
        uint16_t id = ReadHeader();
        if (id != M_MESH_LOD_USAGE) {
            throw DeadlyImportError("M_MESH_LOD does not contain a M_MESH_LOD_USAGE for each LOD level");
        }

        m_reader->IncPtr(sizeof(float)); // user value

        if (manual) {
            id = ReadHeader();
            if (id != M_MESH_LOD_MANUAL) {
                throw DeadlyImportError("Manual M_MESH_LOD_USAGE does not contain M_MESH_LOD_MANUAL");
            }

            ReadLine(); // manual mesh name (ref to another mesh)
        } else {
            for (size_t si = 0, silen = mesh->NumSubMeshes(); si < silen; ++si) {
                id = ReadHeader();
                if (id != M_MESH_LOD_GENERATED) {
                    throw DeadlyImportError("Generated M_MESH_LOD_USAGE does not contain M_MESH_LOD_GENERATED");
                }

                uint32_t indexCount = Read<uint32_t>();
                bool is32bit = Read<bool>();

                if (indexCount > 0) {
                    uint32_t len = indexCount * (is32bit ? sizeof(uint32_t) : sizeof(uint16_t));
                    m_reader->IncPtr(len);
                }
            }
        }
    }
}

void OgreBinarySerializer::ReadMeshSkeletonLink(Mesh *mesh) {
    mesh->skeletonRef = ReadLine();
}

void OgreBinarySerializer::ReadMeshBounds(Mesh * /*mesh*/) {
    // Skip bounds, not compatible with Assimp.
    // 2x float vec3 + 1x float sphere radius
    SkipBytes(sizeof(float) * 7);
}

void OgreBinarySerializer::ReadMeshExtremes(Mesh * /*mesh*/) {
    // Skip extremes, not compatible with Assimp.
    size_t numBytes = m_currentLen - MSTREAM_OVERHEAD_SIZE;
    SkipBytes(numBytes);
}

void OgreBinarySerializer::ReadBoneAssignment(VertexData *dest) {
    if (!dest) {
        throw DeadlyImportError("Cannot read bone assignments, vertex data is null.");
    }

    VertexBoneAssignment ba;
    ba.vertexIndex = Read<uint32_t>();
    ba.boneIndex = Read<uint16_t>();
    ba.weight = Read<float>();

    dest->boneAssignments.push_back(ba);
}

void OgreBinarySerializer::ReadSubMesh(Mesh *mesh) {
    uint16_t id = 0;

    SubMesh *submesh = new SubMesh();
    submesh->materialRef = ReadLine();
    submesh->usesSharedVertexData = Read<bool>();

    submesh->indexData->count = Read<uint32_t>();
    submesh->indexData->faceCount = static_cast<uint32_t>(submesh->indexData->count / 3);
    submesh->indexData->is32bit = Read<bool>();

    ASSIMP_LOG_VERBOSE_DEBUG("Reading SubMesh ", mesh->subMeshes.size());
    ASSIMP_LOG_VERBOSE_DEBUG("  - Material: '", submesh->materialRef, "'");
    ASSIMP_LOG_VERBOSE_DEBUG("  - Uses shared geometry: ", submesh->usesSharedVertexData ? "true" : "false");

    // Index buffer
    if (submesh->indexData->count > 0) {
        uint32_t numBytes = submesh->indexData->count * (submesh->indexData->is32bit ? sizeof(uint32_t) : sizeof(uint16_t));
        uint8_t *indexBuffer = ReadBytes(numBytes);
        submesh->indexData->buffer = MemoryStreamPtr(new Assimp::MemoryIOStream(indexBuffer, numBytes, true));

        ASSIMP_LOG_VERBOSE_DEBUG("  - ", submesh->indexData->faceCount,
                " faces from ", submesh->indexData->count, (submesh->indexData->is32bit ? " 32bit" : " 16bit"),
                " indexes of ", numBytes, " bytes");
    }

    // Vertex buffer if not referencing the shared geometry
    if (!submesh->usesSharedVertexData) {
        id = ReadHeader();
        if (id != M_GEOMETRY) {
            throw DeadlyImportError("M_SUBMESH does not contain M_GEOMETRY, but shader geometry is set to false");
        }

        submesh->vertexData = new VertexData();
        ReadGeometry(submesh->vertexData);
    }

    // Bone assignment, submesh operation and texture aliases
    if (!AtEnd()) {
        id = ReadHeader();
        while (!AtEnd() &&
                (id == M_SUBMESH_OPERATION ||
                        id == M_SUBMESH_BONE_ASSIGNMENT ||
                        id == M_SUBMESH_TEXTURE_ALIAS)) {
            switch (id) {
                case M_SUBMESH_OPERATION: {
                    ReadSubMeshOperation(submesh);
                    break;
                }
                case M_SUBMESH_BONE_ASSIGNMENT: {
                    ReadBoneAssignment(submesh->vertexData);
                    break;
                }
                case M_SUBMESH_TEXTURE_ALIAS: {
                    ReadSubMeshTextureAlias(submesh);
                    break;
                }
            }

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }

    NormalizeBoneWeights(submesh->vertexData);

    submesh->index = static_cast<unsigned int>(mesh->subMeshes.size());
    mesh->subMeshes.push_back(submesh);
}

void OgreBinarySerializer::NormalizeBoneWeights(VertexData *vertexData) const {
    if (!vertexData || vertexData->boneAssignments.empty())
        return;

    std::set<uint32_t> influencedVertices;
    for (VertexBoneAssignmentList::const_iterator baIter = vertexData->boneAssignments.begin(), baEnd = vertexData->boneAssignments.end(); baIter != baEnd; ++baIter) {
        influencedVertices.insert(baIter->vertexIndex);
    }

    /** Normalize bone weights.
        Some exporters won't care if the sum of all bone weights
        for a single vertex equals 1 or not, so validate here. */
    const float epsilon = 0.05f;
    for (const uint32_t vertexIndex : influencedVertices) {
        float sum = 0.0f;
        for (VertexBoneAssignmentList::const_iterator baIter = vertexData->boneAssignments.begin(), baEnd = vertexData->boneAssignments.end(); baIter != baEnd; ++baIter) {
            if (baIter->vertexIndex == vertexIndex)
                sum += baIter->weight;
        }
        if ((sum < (1.0f - epsilon)) || (sum > (1.0f + epsilon))) {
            for (auto &boneAssign : vertexData->boneAssignments) {
                if (boneAssign.vertexIndex == vertexIndex)
                    boneAssign.weight /= sum;
            }
        }
    }
}

void OgreBinarySerializer::ReadSubMeshOperation(SubMesh *submesh) {
    submesh->operationType = static_cast<SubMesh::OperationType>(Read<uint16_t>());
}

void OgreBinarySerializer::ReadSubMeshTextureAlias(SubMesh *submesh) {
    submesh->textureAliasName = ReadLine();
    submesh->textureAliasRef = ReadLine();
}

void OgreBinarySerializer::ReadSubMeshNames(Mesh *mesh) {
    uint16_t id = 0;

    if (!AtEnd()) {
        id = ReadHeader();
        while (!AtEnd() && id == M_SUBMESH_NAME_TABLE_ELEMENT) {
            uint16_t submeshIndex = Read<uint16_t>();
            SubMesh *submesh = mesh->GetSubMesh(submeshIndex);
            if (!submesh) {
                throw DeadlyImportError("Ogre Mesh does not include submesh ", submeshIndex, " referenced in M_SUBMESH_NAME_TABLE_ELEMENT. Invalid mesh file.");
            }

            submesh->name = ReadLine();
            ASSIMP_LOG_VERBOSE_DEBUG("  - SubMesh ", submesh->index, " name '", submesh->name, "'");

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }
}

void OgreBinarySerializer::ReadGeometry(VertexData *dest) {
    dest->count = Read<uint32_t>();

    ASSIMP_LOG_VERBOSE_DEBUG("  - Reading geometry of ", dest->count, " vertices");

    if (!AtEnd()) {
        uint16_t id = ReadHeader();
        while (!AtEnd() &&
                (id == M_GEOMETRY_VERTEX_DECLARATION ||
                        id == M_GEOMETRY_VERTEX_BUFFER)) {
            switch (id) {
                case M_GEOMETRY_VERTEX_DECLARATION: {
                    ReadGeometryVertexDeclaration(dest);
                    break;
                }
                case M_GEOMETRY_VERTEX_BUFFER: {
                    ReadGeometryVertexBuffer(dest);
                    break;
                }
            }

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }
}

void OgreBinarySerializer::ReadGeometryVertexDeclaration(VertexData *dest) {
    if (!AtEnd()) {
        uint16_t id = ReadHeader();
        while (!AtEnd() && id == M_GEOMETRY_VERTEX_ELEMENT) {
            ReadGeometryVertexElement(dest);

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }
}

void OgreBinarySerializer::ReadGeometryVertexElement(VertexData *dest) {
    VertexElement element;
    element.source = Read<uint16_t>();
    element.type = static_cast<VertexElement::Type>(Read<uint16_t>());
    element.semantic = static_cast<VertexElement::Semantic>(Read<uint16_t>());
    element.offset = Read<uint16_t>();
    element.index = Read<uint16_t>();

    ASSIMP_LOG_VERBOSE_DEBUG("    - Vertex element ", element.SemanticToString(), " of type ",
            element.TypeToString(), " index=", element.index, " source=", element.source);

    dest->vertexElements.push_back(element);
}

void OgreBinarySerializer::ReadGeometryVertexBuffer(VertexData *dest) {
    uint16_t bindIndex = Read<uint16_t>();
    uint16_t vertexSize = Read<uint16_t>();

    uint16_t id = ReadHeader();
    if (id != M_GEOMETRY_VERTEX_BUFFER_DATA)
        throw DeadlyImportError("M_GEOMETRY_VERTEX_BUFFER_DATA not found in M_GEOMETRY_VERTEX_BUFFER");

    if (dest->VertexSize(bindIndex) != vertexSize)
        throw DeadlyImportError("Vertex buffer size does not agree with vertex declaration in M_GEOMETRY_VERTEX_BUFFER");

    size_t numBytes = dest->count * vertexSize;
    uint8_t *vertexBuffer = ReadBytes(numBytes);
    dest->vertexBindings[bindIndex] = MemoryStreamPtr(new Assimp::MemoryIOStream(vertexBuffer, numBytes, true));

    ASSIMP_LOG_VERBOSE_DEBUG("    - Read vertex buffer for source ", bindIndex, " of ", numBytes, " bytes");
}

void OgreBinarySerializer::ReadEdgeList(Mesh * /*mesh*/) {
    // Assimp does not acknowledge LOD levels as far as I can see it. This info is just skipped.

    if (!AtEnd()) {
        uint16_t id = ReadHeader();
        while (!AtEnd() && id == M_EDGE_LIST_LOD) {
            m_reader->IncPtr(sizeof(uint16_t)); // lod index
            bool manual = Read<bool>();

            if (!manual) {
                m_reader->IncPtr(sizeof(uint8_t));
                uint32_t numTriangles = Read<uint32_t>();
                uint32_t numEdgeGroups = Read<uint32_t>();

                size_t skipBytes = (sizeof(uint32_t) * 8 + sizeof(float) * 4) * numTriangles;
                m_reader->IncPtr(skipBytes);

                for (size_t i = 0; i < numEdgeGroups; ++i) {
                    uint16_t curId = ReadHeader();
                    if (curId != M_EDGE_GROUP)
                        throw DeadlyImportError("M_EDGE_GROUP not found in M_EDGE_LIST_LOD");

                    m_reader->IncPtr(sizeof(uint32_t) * 3);
                    uint32_t numEdges = Read<uint32_t>();
                    for (size_t j = 0; j < numEdges; ++j) {
                        m_reader->IncPtr(sizeof(uint32_t) * 6 + sizeof(uint8_t));
                    }
                }
            }

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }
}

void OgreBinarySerializer::ReadPoses(Mesh *mesh) {
    if (!AtEnd()) {
        uint16_t id = ReadHeader();
        while (!AtEnd() && id == M_POSE) {
            Pose *pose = new Pose();
            pose->name = ReadLine();
            pose->target = Read<uint16_t>();
            pose->hasNormals = Read<bool>();

            ReadPoseVertices(pose);

            mesh->poses.push_back(pose);

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }
}

void OgreBinarySerializer::ReadPoseVertices(Pose *pose) {
    if (!AtEnd()) {
        uint16_t id = ReadHeader();
        while (!AtEnd() && id == M_POSE_VERTEX) {
            Pose::Vertex v;
            v.index = Read<uint32_t>();
            ReadVector(v.offset);
            if (pose->hasNormals)
                ReadVector(v.normal);

            pose->vertices[v.index] = v;

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }
}

void OgreBinarySerializer::ReadAnimations(Mesh *mesh) {
    if (!AtEnd()) {
        uint16_t id = ReadHeader();
        while (!AtEnd() && id == M_ANIMATION) {
            Animation *anim = new Animation(mesh);
            anim->name = ReadLine();
            anim->length = Read<float>();

            ReadAnimation(anim);

            mesh->animations.push_back(anim);

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }
}

void OgreBinarySerializer::ReadAnimation(Animation *anim) {
    if (!AtEnd()) {
        uint16_t id = ReadHeader();
        if (id == M_ANIMATION_BASEINFO) {
            anim->baseName = ReadLine();
            anim->baseTime = Read<float>();

            // Advance to first track
            id = ReadHeader();
        }

        while (!AtEnd() && id == M_ANIMATION_TRACK) {
            VertexAnimationTrack track;
            track.type = static_cast<VertexAnimationTrack::Type>(Read<uint16_t>());
            track.target = Read<uint16_t>();

            ReadAnimationKeyFrames(anim, &track);

            anim->tracks.push_back(track);

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }
}

void OgreBinarySerializer::ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *track) {
    if (!AtEnd()) {
        uint16_t id = ReadHeader();
        while (!AtEnd() &&
                (id == M_ANIMATION_MORPH_KEYFRAME ||
                        id == M_ANIMATION_POSE_KEYFRAME)) {
            if (id == M_ANIMATION_MORPH_KEYFRAME) {
                MorphKeyFrame kf;
                kf.timePos = Read<float>();
                bool hasNormals = Read<bool>();

                size_t vertexCount = anim->AssociatedVertexData(track)->count;
                size_t vertexSize = sizeof(float) * (hasNormals ? 6 : 3);
                size_t numBytes = vertexCount * vertexSize;

                uint8_t *morphBuffer = ReadBytes(numBytes);
                kf.buffer = MemoryStreamPtr(new Assimp::MemoryIOStream(morphBuffer, numBytes, true));

                track->morphKeyFrames.push_back(kf);
            } else if (id == M_ANIMATION_POSE_KEYFRAME) {
                PoseKeyFrame kf;
                kf.timePos = Read<float>();

                if (!AtEnd()) {
                    id = ReadHeader();
                    while (!AtEnd() && id == M_ANIMATION_POSE_REF) {
                        PoseRef pr;
                        pr.index = Read<uint16_t>();
                        pr.influence = Read<float>();
                        kf.references.push_back(pr);

                        if (!AtEnd())
                            id = ReadHeader();
                    }
                    if (!AtEnd())
                        RollbackHeader();
                }

                track->poseKeyFrames.push_back(kf);
            }

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }
}

// Skeleton

bool OgreBinarySerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh) {
    if (!mesh || mesh->skeletonRef.empty())
        return false;

    // Highly unusual to see in read world cases but support
    // binary mesh referencing a XML skeleton file.
    if (EndsWith(mesh->skeletonRef, ".skeleton.xml", false)) {
        OgreXmlSerializer::ImportSkeleton(pIOHandler, mesh);
        return false;
    }

    MemoryStreamReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef);
    if (!reader)
        return false;

    Skeleton *skeleton = new Skeleton();
    OgreBinarySerializer serializer(reader.get(), OgreBinarySerializer::AM_Skeleton);
    serializer.ReadSkeleton(skeleton);
    mesh->skeleton = skeleton;
    return true;
}

bool OgreBinarySerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh) {
    if (!mesh || mesh->skeletonRef.empty())
        return false;

    MemoryStreamReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef);
    if (!reader.get())
        return false;

    Skeleton *skeleton = new Skeleton();
    OgreBinarySerializer serializer(reader.get(), OgreBinarySerializer::AM_Skeleton);
    serializer.ReadSkeleton(skeleton);
    mesh->skeleton = skeleton;
    return true;
}

MemoryStreamReaderPtr OgreBinarySerializer::OpenReader(Assimp::IOSystem *pIOHandler, const std::string &filename) {
    if (!EndsWith(filename, ".skeleton", false)) {
        ASSIMP_LOG_ERROR("Imported Mesh is referencing to unsupported '", filename, "' skeleton file.");
        return MemoryStreamReaderPtr();
    }

    if (!pIOHandler->Exists(filename)) {
        ASSIMP_LOG_ERROR("Failed to find skeleton file '", filename, "' that is referenced by imported Mesh.");
        return MemoryStreamReaderPtr();
    }

    IOStream *f = pIOHandler->Open(filename, "rb");
    if (!f) {
        throw DeadlyImportError("Failed to open skeleton file ", filename);
    }

    return MemoryStreamReaderPtr(new MemoryStreamReader(f));
}

void OgreBinarySerializer::ReadSkeleton(Skeleton *skeleton) {
    uint16_t id = ReadHeader(false);
    if (id != HEADER_CHUNK_ID) {
        throw DeadlyImportError("Invalid Ogre Skeleton file header.");
    }

    // This deserialization supports both versions of the skeleton spec
    std::string version = ReadLine();
    if (version != SKELETON_VERSION_1_8 && version != SKELETON_VERSION_1_1) {
        throw DeadlyImportError("Skeleton version ", version, " not supported by this importer.",
                                                    " Supported versions: ", SKELETON_VERSION_1_8, " and ", SKELETON_VERSION_1_1);
    }

    ASSIMP_LOG_VERBOSE_DEBUG("Reading Skeleton");

    bool firstBone = true;
    bool firstAnim = true;

    while (!AtEnd()) {
        id = ReadHeader();
        switch (id) {
            case SKELETON_BLENDMODE: {
                skeleton->blendMode = static_cast<Skeleton::BlendMode>(Read<uint16_t>());
                break;
            }
            case SKELETON_BONE: {
                if (firstBone) {
                    ASSIMP_LOG_VERBOSE_DEBUG("  - Bones");
                    firstBone = false;
                }

                ReadBone(skeleton);
                break;
            }
            case SKELETON_BONE_PARENT: {
                ReadBoneParent(skeleton);
                break;
            }
            case SKELETON_ANIMATION: {
                if (firstAnim) {
                    ASSIMP_LOG_VERBOSE_DEBUG("  - Animations");
                    firstAnim = false;
                }

                ReadSkeletonAnimation(skeleton);
                break;
            }
            case SKELETON_ANIMATION_LINK: {
                ReadSkeletonAnimationLink(skeleton);
                break;
            }
        }
    }

    // Calculate bone matrices for root bones. Recursively calculates their children.
    for (size_t i = 0, len = skeleton->bones.size(); i < len; ++i) {
        Bone *bone = skeleton->bones[i];
        if (!bone->IsParented())
            bone->CalculateWorldMatrixAndDefaultPose(skeleton);
    }
}

void OgreBinarySerializer::ReadBone(Skeleton *skeleton) {
    Bone *bone = new Bone();
    bone->name = ReadLine();
    bone->id = Read<uint16_t>();

    // Pos and rot
    ReadVector(bone->position);
    ReadQuaternion(bone->rotation);

    // Scale (optional)
    if (m_currentLen > MSTREAM_BONE_SIZE_WITHOUT_SCALE)
        ReadVector(bone->scale);

    // Bone indexes need to start from 0 and be contiguous
    if (bone->id != skeleton->bones.size()) {
        throw DeadlyImportError("Ogre Skeleton bone indexes not contiguous. Error at bone index ", bone->id);
    }

    ASSIMP_LOG_VERBOSE_DEBUG("    ", bone->id, " ", bone->name);

    skeleton->bones.push_back(bone);
}

void OgreBinarySerializer::ReadBoneParent(Skeleton *skeleton) {
    uint16_t childId = Read<uint16_t>();
    uint16_t parentId = Read<uint16_t>();

    Bone *child = skeleton->BoneById(childId);
    Bone *parent = skeleton->BoneById(parentId);

    if (child && parent)
        parent->AddChild(child);
    else
        throw DeadlyImportError("Failed to find bones for parenting: Child id ", childId, " for parent id ", parentId);
}

void OgreBinarySerializer::ReadSkeletonAnimation(Skeleton *skeleton) {
    Animation *anim = new Animation(skeleton);
    anim->name = ReadLine();
    anim->length = Read<float>();

    if (!AtEnd()) {
        uint16_t id = ReadHeader();
        if (id == SKELETON_ANIMATION_BASEINFO) {
            anim->baseName = ReadLine();
            anim->baseTime = Read<float>();

            // Advance to first track
            id = ReadHeader();
        }

        while (!AtEnd() && id == SKELETON_ANIMATION_TRACK) {
            ReadSkeletonAnimationTrack(skeleton, anim);

            if (!AtEnd())
                id = ReadHeader();
        }
        if (!AtEnd())
            RollbackHeader();
    }

    skeleton->animations.push_back(anim);

    ASSIMP_LOG_VERBOSE_DEBUG("    ", anim->name, " (", anim->length, " sec, ", anim->tracks.size(), " tracks)");
}

void OgreBinarySerializer::ReadSkeletonAnimationTrack(Skeleton * /*skeleton*/, Animation *dest) {
    uint16_t boneId = Read<uint16_t>();
    Bone *bone = dest->parentSkeleton->BoneById(boneId);
    if (!bone) {
        throw DeadlyImportError("Cannot read animation track, target bone ", boneId, " not in target Skeleton");
    }

    VertexAnimationTrack track;
    track.type = VertexAnimationTrack::VAT_TRANSFORM;
    track.boneName = bone->name;

    uint16_t id = ReadHeader();
    while (!AtEnd() && id == SKELETON_ANIMATION_TRACK_KEYFRAME) {
        ReadSkeletonAnimationKeyFrame(&track);

        if (!AtEnd())
            id = ReadHeader();
    }
    if (!AtEnd())
        RollbackHeader();

    dest->tracks.push_back(track);
}

void OgreBinarySerializer::ReadSkeletonAnimationKeyFrame(VertexAnimationTrack *dest) {
    TransformKeyFrame keyframe;
    keyframe.timePos = Read<float>();

    // Rot and pos
    ReadQuaternion(keyframe.rotation);
    ReadVector(keyframe.position);

    // Scale (optional)
    if (m_currentLen > MSTREAM_KEYFRAME_SIZE_WITHOUT_SCALE)
        ReadVector(keyframe.scale);

    dest->transformKeyFrames.push_back(keyframe);
}

void OgreBinarySerializer::ReadSkeletonAnimationLink(Skeleton * /*skeleton*/) {
    // Skip bounds, not compatible with Assimp.
    ReadLine(); // skeleton name
    SkipBytes(sizeof(float) * 3); // scale
}

} // namespace Ogre
} // namespace Assimp

#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER
