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

Copyright (c) 2006-2025, 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.

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

#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER

#include "OgreStructs.h"
#include <assimp/Exceptional.h>
#include <assimp/TinyFormatter.h>
#include <assimp/scene.h>
#include <assimp/DefaultLogger.hpp>

namespace Assimp {
namespace Ogre {

// VertexElement

VertexElement::VertexElement() :
        index(0),
        source(0),
        offset(0),
        type(VET_FLOAT1),
        semantic(VES_POSITION) {
}

size_t VertexElement::Size() const {
    return TypeSize(type);
}

size_t VertexElement::ComponentCount() const {
    return ComponentCount(type);
}

size_t VertexElement::ComponentCount(Type type) {
    switch (type) {
    case VET_COLOUR:
    case VET_COLOUR_ABGR:
    case VET_COLOUR_ARGB:
    case VET_FLOAT1:
    case VET_DOUBLE1:
    case VET_SHORT1:
    case VET_USHORT1:
    case VET_INT1:
    case VET_UINT1:
        return 1;
    case VET_FLOAT2:
    case VET_DOUBLE2:
    case VET_SHORT2:
    case VET_USHORT2:
    case VET_INT2:
    case VET_UINT2:
        return 2;
    case VET_FLOAT3:
    case VET_DOUBLE3:
    case VET_SHORT3:
    case VET_USHORT3:
    case VET_INT3:
    case VET_UINT3:
        return 3;
    case VET_FLOAT4:
    case VET_DOUBLE4:
    case VET_SHORT4:
    case VET_USHORT4:
    case VET_INT4:
    case VET_UINT4:
    case VET_UBYTE4:
        return 4;
    }
    return 0;
}

size_t VertexElement::TypeSize(Type type) {
    switch (type) {
    case VET_COLOUR:
    case VET_COLOUR_ABGR:
    case VET_COLOUR_ARGB:
        return sizeof(unsigned int);
    case VET_FLOAT1:
        return sizeof(float);
    case VET_FLOAT2:
        return sizeof(float) * 2;
    case VET_FLOAT3:
        return sizeof(float) * 3;
    case VET_FLOAT4:
        return sizeof(float) * 4;
    case VET_DOUBLE1:
        return sizeof(double);
    case VET_DOUBLE2:
        return sizeof(double) * 2;
    case VET_DOUBLE3:
        return sizeof(double) * 3;
    case VET_DOUBLE4:
        return sizeof(double) * 4;
    case VET_SHORT1:
        return sizeof(short);
    case VET_SHORT2:
        return sizeof(short) * 2;
    case VET_SHORT3:
        return sizeof(short) * 3;
    case VET_SHORT4:
        return sizeof(short) * 4;
    case VET_USHORT1:
        return sizeof(unsigned short);
    case VET_USHORT2:
        return sizeof(unsigned short) * 2;
    case VET_USHORT3:
        return sizeof(unsigned short) * 3;
    case VET_USHORT4:
        return sizeof(unsigned short) * 4;
    case VET_INT1:
        return sizeof(int);
    case VET_INT2:
        return sizeof(int) * 2;
    case VET_INT3:
        return sizeof(int) * 3;
    case VET_INT4:
        return sizeof(int) * 4;
    case VET_UINT1:
        return sizeof(unsigned int);
    case VET_UINT2:
        return sizeof(unsigned int) * 2;
    case VET_UINT3:
        return sizeof(unsigned int) * 3;
    case VET_UINT4:
        return sizeof(unsigned int) * 4;
    case VET_UBYTE4:
        return sizeof(unsigned char) * 4;
    }
    return 0;
}

std::string VertexElement::TypeToString() {
    return TypeToString(type);
}

std::string VertexElement::TypeToString(Type type) {
    switch (type) {
    case VET_COLOUR: return "COLOUR";
    case VET_COLOUR_ABGR: return "COLOUR_ABGR";
    case VET_COLOUR_ARGB: return "COLOUR_ARGB";
    case VET_FLOAT1: return "FLOAT1";
    case VET_FLOAT2: return "FLOAT2";
    case VET_FLOAT3: return "FLOAT3";
    case VET_FLOAT4: return "FLOAT4";
    case VET_DOUBLE1: return "DOUBLE1";
    case VET_DOUBLE2: return "DOUBLE2";
    case VET_DOUBLE3: return "DOUBLE3";
    case VET_DOUBLE4: return "DOUBLE4";
    case VET_SHORT1: return "SHORT1";
    case VET_SHORT2: return "SHORT2";
    case VET_SHORT3: return "SHORT3";
    case VET_SHORT4: return "SHORT4";
    case VET_USHORT1: return "USHORT1";
    case VET_USHORT2: return "USHORT2";
    case VET_USHORT3: return "USHORT3";
    case VET_USHORT4: return "USHORT4";
    case VET_INT1: return "INT1";
    case VET_INT2: return "INT2";
    case VET_INT3: return "INT3";
    case VET_INT4: return "INT4";
    case VET_UINT1: return "UINT1";
    case VET_UINT2: return "UINT2";
    case VET_UINT3: return "UINT3";
    case VET_UINT4: return "UINT4";
    case VET_UBYTE4: return "UBYTE4";
    }
    return "Uknown_VertexElement::Type";
}

std::string VertexElement::SemanticToString() {
    return SemanticToString(semantic);
}

std::string VertexElement::SemanticToString(Semantic semantic) {
    switch (semantic) {
    case VES_POSITION: return "POSITION";
    case VES_BLEND_WEIGHTS: return "BLEND_WEIGHTS";
    case VES_BLEND_INDICES: return "BLEND_INDICES";
    case VES_NORMAL: return "NORMAL";
    case VES_DIFFUSE: return "DIFFUSE";
    case VES_SPECULAR: return "SPECULAR";
    case VES_TEXTURE_COORDINATES: return "TEXTURE_COORDINATES";
    case VES_BINORMAL: return "BINORMAL";
    case VES_TANGENT: return "TANGENT";
    }
    return "Uknown_VertexElement::Semantic";
}

// IVertexData

IVertexData::IVertexData() :
        count(0) {
}

bool IVertexData::HasBoneAssignments() const {
    return !boneAssignments.empty();
}

void IVertexData::AddVertexMapping(uint32_t oldIndex, uint32_t newIndex) {
    BoneAssignmentsForVertex(oldIndex, newIndex, boneAssignmentsMap[newIndex]);
    vertexIndexMapping[oldIndex].push_back(newIndex);
}

void IVertexData::BoneAssignmentsForVertex(uint32_t currentIndex, uint32_t newIndex, VertexBoneAssignmentList &dest) const {
    for (const auto &boneAssign : boneAssignments) {
        if (boneAssign.vertexIndex == currentIndex) {
            VertexBoneAssignment a = boneAssign;
            a.vertexIndex = newIndex;
            dest.push_back(a);
        }
    }
}

AssimpVertexBoneWeightList IVertexData::AssimpBoneWeights(size_t vertices) {
    AssimpVertexBoneWeightList weights;
    for (size_t vi = 0; vi < vertices; ++vi) {
        VertexBoneAssignmentList &vertexWeights = boneAssignmentsMap[static_cast<unsigned int>(vi)];
        for (VertexBoneAssignmentList::const_iterator iter = vertexWeights.begin(), end = vertexWeights.end();
                iter != end; ++iter) {
            std::vector<aiVertexWeight> &boneWeights = weights[iter->boneIndex];
            boneWeights.emplace_back(static_cast<unsigned int>(vi), iter->weight);
        }
    }
    return weights;
}

std::set<uint16_t> IVertexData::ReferencedBonesByWeights() const {
    std::set<uint16_t> referenced;
    for (const auto &boneAssign : boneAssignments) {
        referenced.insert(boneAssign.boneIndex);
    }
    return referenced;
}

// VertexData

VertexData::VertexData() = default;

VertexData::~VertexData() {
    Reset();
}

void VertexData::Reset() {
    // Releases shared ptr memory streams.
    vertexBindings.clear();
    vertexElements.clear();
}

uint32_t VertexData::VertexSize(uint16_t source) const {
    uint32_t size = 0;
    for (const auto &element : vertexElements) {
        if (element.source == source)
            size += static_cast<uint32_t>(element.Size());
    }
    return size;
}

MemoryStream *VertexData::VertexBuffer(uint16_t source) {
    if (vertexBindings.find(source) != vertexBindings.end())
        return vertexBindings[source].get();
    return nullptr;
}

VertexElement *VertexData::GetVertexElement(VertexElement::Semantic semantic, uint16_t index) {
    for (auto &element : vertexElements) {
        if (element.semantic == semantic && element.index == index)
            return &element;
    }
    return nullptr;
}

// VertexDataXml

VertexDataXml::VertexDataXml() = default;

bool VertexDataXml::HasPositions() const {
    return !positions.empty();
}

bool VertexDataXml::HasNormals() const {
    return !normals.empty();
}

bool VertexDataXml::HasTangents() const {
    return !tangents.empty();
}

bool VertexDataXml::HasUvs() const {
    return !uvs.empty();
}

size_t VertexDataXml::NumUvs() const {
    return uvs.size();
}

// IndexData

IndexData::IndexData() :
        count(0),
        faceCount(0),
        is32bit(false) {
}

IndexData::~IndexData() {
    Reset();
}

void IndexData::Reset() {
    // Release shared ptr memory stream.
    buffer.reset();
}

size_t IndexData::IndexSize() const {
    return (is32bit ? sizeof(uint32_t) : sizeof(uint16_t));
}

size_t IndexData::FaceSize() const {
    return IndexSize() * 3;
}

// Mesh

Mesh::Mesh() :
        hasSkeletalAnimations(false),
        skeleton(nullptr),
        sharedVertexData(nullptr),
        subMeshes(),
        animations(),
        poses() {
}

Mesh::~Mesh() {
    Reset();
}

void Mesh::Reset() {
    OGRE_SAFE_DELETE(skeleton)
    OGRE_SAFE_DELETE(sharedVertexData)

    for (auto &mesh : subMeshes) {
        OGRE_SAFE_DELETE(mesh)
    }
    subMeshes.clear();
    for (auto &anim : animations) {
        OGRE_SAFE_DELETE(anim)
    }
    animations.clear();
    for (auto &pose : poses) {
        OGRE_SAFE_DELETE(pose)
    }
    poses.clear();
}

size_t Mesh::NumSubMeshes() const {
    return subMeshes.size();
}

SubMesh *Mesh::GetSubMesh(size_t index) const {
    for (size_t i = 0; i < subMeshes.size(); ++i) {
        if (subMeshes[i]->index == index) {
            return subMeshes[i];
        }
    }
    return nullptr;
}

void Mesh::ConvertToAssimpScene(aiScene *dest) {
    if (nullptr == dest) {
        return;
    }

    // Setup
    dest->mNumMeshes = static_cast<unsigned int>(NumSubMeshes());
    dest->mMeshes = new aiMesh *[dest->mNumMeshes];

    // Create root node
    dest->mRootNode = new aiNode();
    dest->mRootNode->mNumMeshes = dest->mNumMeshes;
    dest->mRootNode->mMeshes = new unsigned int[dest->mRootNode->mNumMeshes];

    // Export meshes
    for (size_t i = 0; i < dest->mNumMeshes; ++i) {
        dest->mMeshes[i] = subMeshes[i]->ConvertToAssimpMesh(this);
        dest->mRootNode->mMeshes[i] = static_cast<unsigned int>(i);
    }

    // Export skeleton
    if (skeleton) {
        // Bones
        if (!skeleton->bones.empty()) {
            BoneList rootBones = skeleton->RootBones();
            dest->mRootNode->mNumChildren = static_cast<unsigned int>(rootBones.size());
            dest->mRootNode->mChildren = new aiNode *[dest->mRootNode->mNumChildren];

            for (size_t i = 0, len = rootBones.size(); i < len; ++i) {
                dest->mRootNode->mChildren[i] = rootBones[i]->ConvertToAssimpNode(skeleton, dest->mRootNode);
            }
        }

        // Animations
        if (!skeleton->animations.empty()) {
            dest->mNumAnimations = static_cast<unsigned int>(skeleton->animations.size());
            dest->mAnimations = new aiAnimation *[dest->mNumAnimations];

            for (size_t i = 0, len = skeleton->animations.size(); i < len; ++i) {
                dest->mAnimations[i] = skeleton->animations[i]->ConvertToAssimpAnimation();
            }
        }
    }
}

// ISubMesh

ISubMesh::ISubMesh() :
        index(0),
        materialIndex(-1),
        usesSharedVertexData(false),
        operationType(OT_POINT_LIST) {
}

// SubMesh

SubMesh::SubMesh() :
        vertexData(nullptr),
        indexData(new IndexData()) {
}

SubMesh::~SubMesh() {
    Reset();
}

void SubMesh::Reset(){
    OGRE_SAFE_DELETE(vertexData)
            OGRE_SAFE_DELETE(indexData)
}

aiMesh *SubMesh::ConvertToAssimpMesh(Mesh *parent) {
    if (operationType != OT_TRIANGLE_LIST) {
        throw DeadlyImportError("Only mesh operation type OT_TRIANGLE_LIST is supported. Found ", operationType);
    }

    aiMesh *dest = new aiMesh();
    dest->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;

    if (!name.empty())
        dest->mName = name;

    // Material index
    if (materialIndex != -1)
        dest->mMaterialIndex = materialIndex;

    // Pick source vertex data from shader geometry or from internal geometry.
    VertexData *src = (!usesSharedVertexData ? vertexData : parent->sharedVertexData);

    VertexElement *positionsElement = src->GetVertexElement(VertexElement::VES_POSITION);
    VertexElement *normalsElement = src->GetVertexElement(VertexElement::VES_NORMAL);
    VertexElement *uv1Element = src->GetVertexElement(VertexElement::VES_TEXTURE_COORDINATES, 0);
    VertexElement *uv2Element = src->GetVertexElement(VertexElement::VES_TEXTURE_COORDINATES, 1);

    // Sanity checks
    if (!positionsElement) {
        throw DeadlyImportError("Failed to import Ogre VertexElement::VES_POSITION. Mesh does not have vertex positions!");
    } else if (positionsElement->type != VertexElement::VET_FLOAT3) {
        throw DeadlyImportError("Ogre Mesh position vertex element type != VertexElement::VET_FLOAT3. This is not supported.");
    } else if (normalsElement && normalsElement->type != VertexElement::VET_FLOAT3) {
        throw DeadlyImportError("Ogre Mesh normal vertex element type != VertexElement::VET_FLOAT3. This is not supported.");
    }

    // Faces
    dest->mNumFaces = indexData->faceCount;
    dest->mFaces = new aiFace[dest->mNumFaces];

    // Assimp required unique vertices, we need to convert from Ogres shared indexing.
    size_t uniqueVertexCount = dest->mNumFaces * 3;
    dest->mNumVertices = static_cast<unsigned int>(uniqueVertexCount);
    dest->mVertices = new aiVector3D[dest->mNumVertices];

    // Source streams
    MemoryStream *positions = src->VertexBuffer(positionsElement->source);
    MemoryStream *normals = (normalsElement ? src->VertexBuffer(normalsElement->source) : nullptr);
    MemoryStream *uv1 = (uv1Element ? src->VertexBuffer(uv1Element->source) : nullptr);
    MemoryStream *uv2 = (uv2Element ? src->VertexBuffer(uv2Element->source) : nullptr);

    // Element size
    const size_t sizePosition = positionsElement->Size();
    const size_t sizeNormal = (normalsElement ? normalsElement->Size() : 0);
    const size_t sizeUv1 = (uv1Element ? uv1Element->Size() : 0);
    const size_t sizeUv2 = (uv2Element ? uv2Element->Size() : 0);

    // Vertex width
    const size_t vWidthPosition = src->VertexSize(positionsElement->source);
    const size_t vWidthNormal = (normalsElement ? src->VertexSize(normalsElement->source) : 0);
    const size_t vWidthUv1 = (uv1Element ? src->VertexSize(uv1Element->source) : 0);
    const size_t vWidthUv2 = (uv2Element ? src->VertexSize(uv2Element->source) : 0);

    bool boneAssignments = src->HasBoneAssignments();

    // Prepare normals
    if (normals)
        dest->mNormals = new aiVector3D[dest->mNumVertices];

    // Prepare UVs, ignoring incompatible UVs.
    if (uv1) {
        if (uv1Element->type == VertexElement::VET_FLOAT2 || uv1Element->type == VertexElement::VET_FLOAT3) {
            dest->mNumUVComponents[0] = static_cast<unsigned int>(uv1Element->ComponentCount());
            dest->mTextureCoords[0] = new aiVector3D[dest->mNumVertices];
        } else {
            ASSIMP_LOG_WARN("Ogre imported UV0 type ", uv1Element->TypeToString(), " is not compatible with Assimp. Ignoring UV.");
            uv1 = nullptr;
        }
    }
    if (uv2) {
        if (uv2Element->type == VertexElement::VET_FLOAT2 || uv2Element->type == VertexElement::VET_FLOAT3) {
            dest->mNumUVComponents[1] = static_cast<unsigned int>(uv2Element->ComponentCount());
            dest->mTextureCoords[1] = new aiVector3D[dest->mNumVertices];
        } else {
            ASSIMP_LOG_WARN("Ogre imported UV0 type ", uv2Element->TypeToString(), " is not compatible with Assimp. Ignoring UV.");
            uv2 = nullptr;
        }
    }

    aiVector3D *uv1Dest = (uv1 ? dest->mTextureCoords[0] : nullptr);
    aiVector3D *uv2Dest = (uv2 ? dest->mTextureCoords[1] : nullptr);

    MemoryStream *faces = indexData->buffer.get();
    for (size_t fi = 0, isize = indexData->IndexSize(), fsize = indexData->FaceSize();
            fi < dest->mNumFaces; ++fi) {
        // Source Ogre face
        aiFace ogreFace;
        ogreFace.mNumIndices = 3;
        ogreFace.mIndices = new unsigned int[3];

        faces->Seek(fi * fsize, aiOrigin_SET);
        if (indexData->is32bit) {
            faces->Read(&ogreFace.mIndices[0], isize, 3);
        } else {
            uint16_t iout = 0;
            for (size_t ii = 0; ii < 3; ++ii) {
                faces->Read(&iout, isize, 1);
                ogreFace.mIndices[ii] = static_cast<unsigned int>(iout);
            }
        }

        // Destination Assimp face
        aiFace &face = dest->mFaces[fi];
        face.mNumIndices = 3;
        face.mIndices = new unsigned int[3];

        const size_t pos = fi * 3;
        for (size_t v = 0; v < 3; ++v) {
            const size_t newIndex = pos + v;

            // Write face index
            face.mIndices[v] = static_cast<unsigned int>(newIndex);

            // Ogres vertex index to ref into the source buffers.
            const size_t ogreVertexIndex = ogreFace.mIndices[v];
            src->AddVertexMapping(static_cast<uint32_t>(ogreVertexIndex), static_cast<uint32_t>(newIndex));

            // Position
            positions->Seek((vWidthPosition * ogreVertexIndex) + positionsElement->offset, aiOrigin_SET);
            positions->Read(&dest->mVertices[newIndex], sizePosition, 1);

            // Normal
            if (normals) {
                normals->Seek((vWidthNormal * ogreVertexIndex) + normalsElement->offset, aiOrigin_SET);
                normals->Read(&dest->mNormals[newIndex], sizeNormal, 1);
            }
            // UV0
            if (uv1 && uv1Dest) {
                uv1->Seek((vWidthUv1 * ogreVertexIndex) + uv1Element->offset, aiOrigin_SET);
                uv1->Read(&uv1Dest[newIndex], sizeUv1, 1);
                uv1Dest[newIndex].y = (uv1Dest[newIndex].y * -1) + 1; // Flip UV from Ogre to Assimp form
            }
            // UV1
            if (uv2 && uv2Dest) {
                uv2->Seek((vWidthUv2 * ogreVertexIndex) + uv2Element->offset, aiOrigin_SET);
                uv2->Read(&uv2Dest[newIndex], sizeUv2, 1);
                uv2Dest[newIndex].y = (uv2Dest[newIndex].y * -1) + 1; // Flip UV from Ogre to Assimp form
            }
        }
    }

    // Bones and bone weights
    if (parent->skeleton && boneAssignments) {
        AssimpVertexBoneWeightList weights = src->AssimpBoneWeights(dest->mNumVertices);
        std::set<uint16_t> referencedBones = src->ReferencedBonesByWeights();

        dest->mNumBones = static_cast<unsigned int>(referencedBones.size());
        dest->mBones = new aiBone *[dest->mNumBones];

        size_t assimpBoneIndex = 0;
        for (std::set<uint16_t>::const_iterator rbIter = referencedBones.begin(), rbEnd = referencedBones.end(); rbIter != rbEnd; ++rbIter, ++assimpBoneIndex) {
            Bone *bone = parent->skeleton->BoneById((*rbIter));
            dest->mBones[assimpBoneIndex] = bone->ConvertToAssimpBone(parent->skeleton, weights[bone->id]);
        }
    }

    return dest;
}

// MeshXml

MeshXml::MeshXml() :
        skeleton(nullptr),
        sharedVertexData(nullptr) {
}

MeshXml::~MeshXml() {
    Reset();
}

void MeshXml::Reset() {
    OGRE_SAFE_DELETE(skeleton)
    OGRE_SAFE_DELETE(sharedVertexData)

    for (auto &mesh : subMeshes) {
        OGRE_SAFE_DELETE(mesh)
    }
    subMeshes.clear();
}

size_t MeshXml::NumSubMeshes() const {
    return subMeshes.size();
}

SubMeshXml *MeshXml::GetSubMesh(uint16_t index) const {
    for (size_t i = 0; i < subMeshes.size(); ++i)
        if (subMeshes[i]->index == index)
            return subMeshes[i];
    return nullptr;
}

void MeshXml::ConvertToAssimpScene(aiScene *dest) {
    // Setup
    dest->mNumMeshes = static_cast<unsigned int>(NumSubMeshes());
    dest->mMeshes = new aiMesh *[dest->mNumMeshes];

    // Create root node
    dest->mRootNode = new aiNode();
    dest->mRootNode->mNumMeshes = dest->mNumMeshes;
    dest->mRootNode->mMeshes = new unsigned int[dest->mRootNode->mNumMeshes];

    // Export meshes
    for (size_t i = 0; i < dest->mNumMeshes; ++i) {
        dest->mMeshes[i] = subMeshes[i]->ConvertToAssimpMesh(this);
        dest->mRootNode->mMeshes[i] = static_cast<unsigned int>(i);
    }

    // Export skeleton
    if (skeleton) {
        // Bones
        if (!skeleton->bones.empty()) {
            BoneList rootBones = skeleton->RootBones();
            dest->mRootNode->mNumChildren = static_cast<unsigned int>(rootBones.size());
            dest->mRootNode->mChildren = new aiNode *[dest->mRootNode->mNumChildren];

            for (size_t i = 0, len = rootBones.size(); i < len; ++i) {
                dest->mRootNode->mChildren[i] = rootBones[i]->ConvertToAssimpNode(skeleton, dest->mRootNode);
            }
        }

        // Animations
        if (!skeleton->animations.empty()) {
            dest->mNumAnimations = static_cast<unsigned int>(skeleton->animations.size());
            dest->mAnimations = new aiAnimation *[dest->mNumAnimations];

            for (size_t i = 0, len = skeleton->animations.size(); i < len; ++i) {
                dest->mAnimations[i] = skeleton->animations[i]->ConvertToAssimpAnimation();
            }
        }
    }
}

// SubMeshXml

SubMeshXml::SubMeshXml() :
        indexData(new IndexDataXml()),
        vertexData(nullptr) {
}

SubMeshXml::~SubMeshXml() {
    Reset();
}

void SubMeshXml::Reset(){
    OGRE_SAFE_DELETE(indexData)
            OGRE_SAFE_DELETE(vertexData)
}

aiMesh *SubMeshXml::ConvertToAssimpMesh(MeshXml *parent) {
    aiMesh *dest = new aiMesh();
    dest->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;

    if (!name.empty())
        dest->mName = name;

    // Material index
    if (materialIndex != -1)
        dest->mMaterialIndex = materialIndex;

    // Faces
    dest->mNumFaces = indexData->faceCount;
    dest->mFaces = new aiFace[dest->mNumFaces];

    // Assimp required unique vertices, we need to convert from Ogres shared indexing.
    size_t uniqueVertexCount = dest->mNumFaces * 3;
    dest->mNumVertices = static_cast<unsigned int>(uniqueVertexCount);
    dest->mVertices = new aiVector3D[dest->mNumVertices];

    VertexDataXml *src = (!usesSharedVertexData ? vertexData : parent->sharedVertexData);
    bool boneAssignments = src->HasBoneAssignments();
    bool normals = src->HasNormals();
    size_t uvs = src->NumUvs();

    // Prepare normals
    if (normals)
        dest->mNormals = new aiVector3D[dest->mNumVertices];

    // Prepare UVs
    for (size_t uvi = 0; uvi < uvs; ++uvi) {
        dest->mNumUVComponents[uvi] = 2;
        dest->mTextureCoords[uvi] = new aiVector3D[dest->mNumVertices];
    }

    for (size_t fi = 0; fi < dest->mNumFaces; ++fi) {
        // Source Ogre face
        aiFace &ogreFace = indexData->faces[fi];

        // Destination Assimp face
        aiFace &face = dest->mFaces[fi];
        face.mNumIndices = 3;
        face.mIndices = new unsigned int[3];

        const size_t pos = fi * 3;
        for (size_t v = 0; v < 3; ++v) {
            const size_t newIndex = pos + v;

            // Write face index
            face.mIndices[v] = static_cast<unsigned int>(newIndex);

            // Ogres vertex index to ref into the source buffers.
            const size_t ogreVertexIndex = ogreFace.mIndices[v];
            src->AddVertexMapping(static_cast<uint32_t>(ogreVertexIndex), static_cast<uint32_t>(newIndex));

            // Position
            dest->mVertices[newIndex] = src->positions[ogreVertexIndex];

            // Normal
            if (normals)
                dest->mNormals[newIndex] = src->normals[ogreVertexIndex];

            // UVs
            for (size_t uvi = 0; uvi < uvs; ++uvi) {
                aiVector3D *uvDest = dest->mTextureCoords[uvi];
                std::vector<aiVector3D> &uvSrc = src->uvs[uvi];
                uvDest[newIndex] = uvSrc[ogreVertexIndex];
            }
        }
    }

    // Bones and bone weights
    if (parent->skeleton && boneAssignments) {
        AssimpVertexBoneWeightList weights = src->AssimpBoneWeights(dest->mNumVertices);
        std::set<uint16_t> referencedBones = src->ReferencedBonesByWeights();

        dest->mNumBones = static_cast<unsigned int>(referencedBones.size());
        dest->mBones = new aiBone *[dest->mNumBones];

        size_t assimpBoneIndex = 0;
        for (std::set<uint16_t>::const_iterator rbIter = referencedBones.begin(), rbEnd = referencedBones.end(); rbIter != rbEnd; ++rbIter, ++assimpBoneIndex) {
            Bone *bone = parent->skeleton->BoneById((*rbIter));
            dest->mBones[assimpBoneIndex] = bone->ConvertToAssimpBone(parent->skeleton, weights[bone->id]);
        }
    }

    return dest;
}

// Animation

Animation::Animation(Skeleton *parent) :
        parentMesh(nullptr),
        parentSkeleton(parent),
        length(0.0f),
        baseTime(-1.0f) {
    // empty
}

Animation::Animation(Mesh *parent) :
        parentMesh(parent),
        parentSkeleton(nullptr),
        length(0.0f),
        baseTime(-1.0f) {
    // empty
}

VertexData *Animation::AssociatedVertexData(VertexAnimationTrack *track) const {
    if (nullptr == parentMesh) {
        return nullptr;
    }

    bool sharedGeom = (track->target == 0);
    if (sharedGeom) {
        return parentMesh->sharedVertexData;
    }

    return parentMesh->GetSubMesh(track->target - 1)->vertexData;
}

aiAnimation *Animation::ConvertToAssimpAnimation() {
    aiAnimation *anim = new aiAnimation();
    anim->mName = name;
    anim->mDuration = static_cast<double>(length);
    anim->mTicksPerSecond = 1.0;

    // Tracks
    if (!tracks.empty()) {
        anim->mNumChannels = static_cast<unsigned int>(tracks.size());
        anim->mChannels = new aiNodeAnim *[anim->mNumChannels];

        for (size_t i = 0, len = tracks.size(); i < len; ++i) {
            anim->mChannels[i] = tracks[i].ConvertToAssimpAnimationNode(parentSkeleton);
        }
    }
    return anim;
}

// Skeleton

Skeleton::Skeleton() :
        bones(),
        animations(),
        blendMode(ANIMBLEND_AVERAGE) {
}

Skeleton::~Skeleton() {
    Reset();
}

void Skeleton::Reset() {
    for (auto &bone : bones) {
        OGRE_SAFE_DELETE(bone)
    }
    bones.clear();
    for (auto &anim : animations) {
        OGRE_SAFE_DELETE(anim)
    }
    animations.clear();
}

BoneList Skeleton::RootBones() const {
    BoneList rootBones;
    for (BoneList::const_iterator iter = bones.begin(); iter != bones.end(); ++iter) {
        if (!(*iter)->IsParented())
            rootBones.push_back((*iter));
    }
    return rootBones;
}

size_t Skeleton::NumRootBones() const {
    size_t num = 0;
    for (BoneList::const_iterator iter = bones.begin(); iter != bones.end(); ++iter) {
        if (!(*iter)->IsParented())
            num++;
    }
    return num;
}

Bone *Skeleton::BoneByName(const std::string &name) const {
    for (BoneList::const_iterator iter = bones.begin(); iter != bones.end(); ++iter) {
        if ((*iter)->name == name)
            return (*iter);
    }
    return nullptr;
}

Bone *Skeleton::BoneById(uint16_t id) const {
    for (BoneList::const_iterator iter = bones.begin(); iter != bones.end(); ++iter) {
        if ((*iter)->id == id)
            return (*iter);
    }
    return nullptr;
}

// Bone

Bone::Bone() :
        id(0),
        parent(nullptr),
        parentId(-1),
        scale(1.0f, 1.0f, 1.0f) {
}

bool Bone::IsParented() const {
    return (parentId != -1 && parent != nullptr);
}

uint16_t Bone::ParentId() const {
    return static_cast<uint16_t>(parentId);
}

void Bone::AddChild(Bone *bone) {
    if (!bone)
        return;
    if (bone->IsParented())
        throw DeadlyImportError("Attaching child Bone that is already parented: ", bone->name);

    bone->parent = this;
    bone->parentId = id;
    children.push_back(bone->id);
}

void Bone::CalculateWorldMatrixAndDefaultPose(Skeleton *skeleton) {
    if (!IsParented())
        worldMatrix = aiMatrix4x4(scale, rotation, position).Inverse();
    else
        worldMatrix = aiMatrix4x4(scale, rotation, position).Inverse() * parent->worldMatrix;

    defaultPose = aiMatrix4x4(scale, rotation, position);

    // Recursively for all children now that the parent matrix has been calculated.
    for (auto boneId : children) {
        Bone *child = skeleton->BoneById(boneId);
        if (!child) {
            throw DeadlyImportError("CalculateWorldMatrixAndDefaultPose: Failed to find child bone ", boneId, " for parent ", id, " ", name);
        }
        child->CalculateWorldMatrixAndDefaultPose(skeleton);
    }
}

aiNode *Bone::ConvertToAssimpNode(Skeleton *skeleton, aiNode *parentNode) {
    // Bone node
    aiNode *node = new aiNode(name);
    node->mParent = parentNode;
    node->mTransformation = defaultPose;

    // Children
    if (!children.empty()) {
        node->mNumChildren = static_cast<unsigned int>(children.size());
        node->mChildren = new aiNode *[node->mNumChildren];

        for (size_t i = 0, len = children.size(); i < len; ++i) {
            Bone *child = skeleton->BoneById(children[i]);
            if (!child) {
                throw DeadlyImportError("ConvertToAssimpNode: Failed to find child bone ", children[i], " for parent ", id, " ", name);
            }
            node->mChildren[i] = child->ConvertToAssimpNode(skeleton, node);
        }
    }
    return node;
}

aiBone *Bone::ConvertToAssimpBone(Skeleton * /*parent*/, const std::vector<aiVertexWeight> &boneWeights) {
    aiBone *bone = new aiBone();
    bone->mName = name;
    bone->mOffsetMatrix = worldMatrix;

    if (!boneWeights.empty()) {
        bone->mNumWeights = static_cast<unsigned int>(boneWeights.size());
        bone->mWeights = new aiVertexWeight[boneWeights.size()];
        memcpy(bone->mWeights, &boneWeights[0], boneWeights.size() * sizeof(aiVertexWeight));
    }

    return bone;
}

// VertexAnimationTrack

VertexAnimationTrack::VertexAnimationTrack() :
        type(VAT_NONE),
        target(0) {
}

aiNodeAnim *VertexAnimationTrack::ConvertToAssimpAnimationNode(Skeleton *skeleton) {
    if (boneName.empty() || type != VAT_TRANSFORM) {
        throw DeadlyImportError("VertexAnimationTrack::ConvertToAssimpAnimationNode: Cannot convert track that has no target bone name or is not type of VAT_TRANSFORM");
    }

    aiNodeAnim *nodeAnim = new aiNodeAnim();
    nodeAnim->mNodeName = boneName;

    Bone *bone = skeleton->BoneByName(boneName);
    if (!bone) {
        throw DeadlyImportError("VertexAnimationTrack::ConvertToAssimpAnimationNode: Failed to find bone ", boneName, " from parent Skeleton");
    }

    // Keyframes
    size_t numKeyframes = transformKeyFrames.size();

    nodeAnim->mPositionKeys = new aiVectorKey[numKeyframes];
    nodeAnim->mRotationKeys = new aiQuatKey[numKeyframes];
    nodeAnim->mScalingKeys = new aiVectorKey[numKeyframes];
    nodeAnim->mNumPositionKeys = static_cast<unsigned int>(numKeyframes);
    nodeAnim->mNumRotationKeys = static_cast<unsigned int>(numKeyframes);
    nodeAnim->mNumScalingKeys = static_cast<unsigned int>(numKeyframes);

    for (size_t kfi = 0; kfi < numKeyframes; ++kfi) {
        TransformKeyFrame &kfSource = transformKeyFrames[kfi];

        // Calculate the complete transformation from world space to bone space
        aiVector3D pos;
        aiQuaternion rot;
        aiVector3D scale;

        aiMatrix4x4 finalTransform = bone->defaultPose * kfSource.Transform();
        finalTransform.Decompose(scale, rot, pos);

        double t = static_cast<double>(kfSource.timePos);
        nodeAnim->mPositionKeys[kfi].mTime = t;
        nodeAnim->mRotationKeys[kfi].mTime = t;
        nodeAnim->mScalingKeys[kfi].mTime = t;

        nodeAnim->mPositionKeys[kfi].mValue = pos;
        nodeAnim->mRotationKeys[kfi].mValue = rot;
        nodeAnim->mScalingKeys[kfi].mValue = scale;
    }

    return nodeAnim;
}

// TransformKeyFrame

TransformKeyFrame::TransformKeyFrame() :
        timePos(0.0f),
        scale(1.0f, 1.0f, 1.0f) {
}

aiMatrix4x4 TransformKeyFrame::Transform() {
    return aiMatrix4x4(scale, rotation, position);
}

} // namespace Ogre
} // namespace Assimp

#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER
