/**

 Copyright (c) 2010-2014  hkrn

 All rights reserved.

 Redistribution and use 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 MMDAI project team nor the names of
   its contributors may be used to endorse or promote products
   derived from this software without specific prior written
   permission.

 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 "vpvl2/vpvl2.h"
#include "vpvl2/asset/Model.h"
#include "vpvl2/internal/ModelHelper.h"

#if defined(VPVL2_LINK_ASSIMP3)
#include <assimp/ProgressHandler.hpp>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#elif defined(VPVL2_LINK_ASSIMP)
#include <assimp/assimp.hpp>
#include <assimp/aiPostProcess.h>
#include <assimp/aiScene.h>
#endif

namespace {

#if defined(VPVL2_LINK_ASSIMP) || defined(VPVL2_LINK_ASSIMP3)
using namespace vpvl2::VPVL2_VERSION_NS;

class RootBone : public IBone {
public:
    RootBone(asset::Model *modelRef, const IEncoding *encodingRef)
        : m_encodingRef(encodingRef),
          m_modelRef(modelRef),
          m_worldTransform(Transform::getIdentity())
    {
        m_worldTransform.setOrigin(modelRef->worldTranslation());
        m_worldTransform.setRotation(modelRef->worldOrientation());
    }
    ~RootBone() {
        m_encodingRef = 0;
        m_modelRef = 0;
        m_worldTransform.setIdentity();
    }

    const IString *name(IEncoding::LanguageType /* type */) const {
        return m_encodingRef->stringConstant(IEncoding::kRootBone);
    }
    void setName(const IString * /* value */, IEncoding::LanguageType /* type */) {}
    int index() const { return 0; }
    IModel *parentModelRef() const { return m_modelRef; }
    IBone *parentBoneRef() const { return 0; }
    Transform worldTransform() const {
        return m_worldTransform;
    }
    Transform localTransform() const {
        return m_worldTransform;
    }
    void getLocalTransform(Transform &world2LocalTransform) const {
        world2LocalTransform = m_worldTransform;
    }
    void setLocalTransform(const Transform & /* value */) {}
    Vector3 origin() const { return kZeroV3; }
    Vector3 destinationOrigin() const { return kZeroV3; }
    Vector3 localTranslation() const { return m_modelRef->worldTranslation(); }
    Quaternion localOrientation() const { return m_modelRef->worldOrientation(); }
    void getEffectorBones(Array<IBone *> & /* value */) const {}
    void setLocalTranslation(const Vector3 &value) {
        m_modelRef->setWorldPositionInternal(value);
        m_worldTransform.setOrigin(value);
    }
    void setLocalOrientation(const Quaternion &value) {
        m_modelRef->setWorldRotationInternal(value);
        m_worldTransform.setRotation(value);
    }
    bool isMovable() const { return true; }
    bool isRotateable() const { return true; }
    bool isVisible() const { return false; }
    bool isInteractive() const { return true; }
    bool hasInverseKinematics() const { return false; }
    bool hasFixedAxes() const { return false; }
    bool hasLocalAxes() const { return false; }
    Vector3 fixedAxis() const { return kZeroV3; }
    void getLocalAxes(Matrix3x3 & /* value */) const {}
    void setInverseKinematicsEnable(bool /* value */) {}
    bool isInverseKinematicsEnabled() const { return false; }
    bool isInherentTranslationEnabled() const { return false; }
    bool isInherentOrientationEnabled() const { return false; }
    float32 inherentCoefficient() const { return 1.0f; }
    void setInherentCoefficient(float32 /* value */) {}
    IBone *destinationOriginBoneRef() const { return 0; }
    IBone *parentInherentBoneRef() const { return 0; }

    void setParentBoneRef(IBone * /* value */) {}
    void setParentInherentBoneRef(IBone * /* value */) {}
    void setDestinationOriginBoneRef(IBone * /* value */) {}
    void setOrigin(const Vector3 & /* value */) {}
    void setDestinationOrigin(const Vector3 & /* value */) {}
    void setFixedAxis(const Vector3 & /* value */) {}
    void setAxisX(const Vector3 & /* value */) {}
    void setAxisZ(const Vector3 & /* value */) {}
    void setIndex(int /* value */) {}
    void setLayerIndex(int /* value */) {}
    void setExternalIndex(int /* value */) {}
    void setRotateable(bool /* value */) {}
    void setMovable(bool /* value */) {}
    void setVisible(bool /* value */) {}
    void setInteractive(bool /* value */) {}
    void setInherentOrientationEnable(bool /* value */) {}
    void setInherentTranslationEnable(bool /* value */) {}
    void setFixedAxisEnable(bool /* value */) {}
    void setLocalAxesEnable(bool /* value */) {}
    void setTransformAfterPhysicsEnable(bool /* value */) {}
    void setTransformedByExternalParentEnable(bool /* value */) {}

private:
    const IEncoding *m_encodingRef;
    asset::Model *m_modelRef;
    Transform m_worldTransform;
};

class ScaleBone : public IBone {
public:
    ScaleBone(asset::Model *modelRef, const IEncoding *encodingRef)
        : m_encodingRef(encodingRef),
          m_modelRef(modelRef),
          m_position(kZeroV3)
    {
        const Scalar &scaleFactor = modelRef->scaleFactor();
        m_position.setValue(scaleFactor, scaleFactor, scaleFactor);
    }
    ~ScaleBone() {
        m_encodingRef = 0;
        m_modelRef = 0;
        m_position.setZero();
    }

    const IString *name(IEncoding::LanguageType /* type */) const {
        return m_encodingRef->stringConstant(IEncoding::kScaleBoneAsset);
    }
    void setName(const IString * /* value */, IEncoding::LanguageType /* type */) {}
    int index() const { return -1; }
    IModel *parentModelRef() const { return m_modelRef; }
    IBone *parentBoneRef() const { return 0; }
    Transform worldTransform() const {
        return Transform::getIdentity();
    }
    Transform localTransform() const {
        return Transform::getIdentity();
    }
    void getLocalTransform(Transform &world2LocalTransform) const {
        world2LocalTransform = Transform::getIdentity();
    }
    void setLocalTransform(const Transform & /* value */) {}
    Vector3 origin() const { return kZeroV3; }
    Vector3 destinationOrigin() const { return kZeroV3; }
    Vector3 localTranslation() const { return m_position; }
    Quaternion localOrientation() const { return Quaternion::getIdentity(); }
    void getEffectorBones(Array<IBone *> & /* value */) const {}
    void setLocalTranslation(const Vector3 &value) {
        m_position = value;
        m_position.setMax(kMaxValue);
        const Scalar &scaleFactor = (m_position.x() + m_position.y() + m_position.z()) / 3.0f;
        m_modelRef->setScaleFactorInternal(scaleFactor);
    }
    void setLocalOrientation(const Quaternion & /* value */) {}
    bool isMovable() const { return true; }
    bool isRotateable() const { return false; }
    bool isVisible() const { return false; }
    bool isInteractive() const { return true; }
    bool hasInverseKinematics() const { return false; }
    bool hasFixedAxes() const { return false; }
    bool hasLocalAxes() const { return false; }
    Vector3 fixedAxis() const { return kZeroV3; }
    void getLocalAxes(Matrix3x3 & /* value */) const {}
    void setInverseKinematicsEnable(bool /* value */) {}
    bool isInverseKinematicsEnabled() const { return false; }
    bool isInherentTranslationEnabled() const { return false; }
    bool isInherentOrientationEnabled() const { return false; }
    float32 inherentCoefficient() const { return 1.0f; }
    void setInherentCoefficient(float32 /* value */) {}
    IBone *destinationOriginBoneRef() const { return 0; }
    IBone *parentInherentBoneRef() const { return 0; }

    void setParentBoneRef(IBone * /* value */) {}
    void setParentInherentBoneRef(IBone * /* value */) {}
    void setDestinationOriginBoneRef(IBone * /* value */) {}
    void setOrigin(const Vector3 & /* value */) {}
    void setDestinationOrigin(const Vector3 & /* value */) {}
    void setFixedAxis(const Vector3 & /* value */) {}
    void setAxisX(const Vector3 & /* value */) {}
    void setAxisZ(const Vector3 & /* value */) {}
    void setIndex(int /* value */) {}
    void setLayerIndex(int /* value */) {}
    void setExternalIndex(int /* value */) {}
    void setRotateable(bool /* value */) {}
    void setMovable(bool /* value */) {}
    void setVisible(bool /* value */) {}
    void setInteractive(bool /* value */) {}
    void setInherentOrientationEnable(bool /* value */) {}
    void setInherentTranslationEnable(bool /* value */) {}
    void setFixedAxisEnable(bool /* value */) {}
    void setLocalAxesEnable(bool /* value */) {}
    void setTransformAfterPhysicsEnable(bool /* value */) {}
    void setTransformedByExternalParentEnable(bool /* value */) {}

private:
    static const Vector3 kMaxValue;
    const IEncoding *m_encodingRef;
    asset::Model *m_modelRef;
    Vector3 m_position;
};
const Vector3 ScaleBone::kMaxValue = Vector3(0.01f, 0.01f, 0.01f);

class Label : public ILabel {
public:
    Label(asset::Model *modelRef, const Array<IBone *> &bones, const IEncoding *encodingRef)
        : m_modelRef(modelRef),
          m_name(0)
    {
        static const uint8 name[] = "Root";
        m_name = encodingRef->toString(name, sizeof(name) - 1, IString::kUTF8);
        m_bones.copy(bones);
    }
    ~Label() {
        internal::deleteObject(m_name);
        m_modelRef = 0;
    }

    const IString *name(IEncoding::LanguageType /* type */) const {
        return m_name;
    }
    const IString *englishName() const { return m_name; }
    IModel *parentModelRef() const { return m_modelRef; }
    bool isSpecial() const { return true; }
    int count() const { return m_bones.count(); }
    IBone *boneRef(int index) const { return m_bones[index]; }
    IMorph *morphRef(int /*index*/) const { return 0; }
    int index() const { return -1; }

    void setName(const IString *value, IEncoding::LanguageType /* type */) {
        internal::setString(value, m_name);
    }
    void setSpecial(bool /* value */) {}
    void addBoneRef(IBone * /* value */) {}
    void addMorphRef(IMorph * /* value */) {}
    void removeBoneRef(IBone * /* value */) {}
    void removeMorphRef(IMorph * /* value */) {}

private:
    asset::Model *m_modelRef;
    IString *m_name;
    Array<IBone *> m_bones;
};

class Material : public IMaterial {
public:
    Material(asset::Model *modelRef, const aiMaterial *materialRef, IEncoding *encodingRef, int nindices, int index)
        : m_materialRef(materialRef),
          m_modelRef(modelRef),
          m_encodingRef(encodingRef),
          m_mainTexture(0),
          m_sphereTexture(0),
          m_sphereTextureRenderMode(kNone),
          m_nindices(nindices),
          m_index(index),
          m_visible(true)
    {
        aiColor4D color;
        aiGetMaterialColor(m_materialRef, AI_MATKEY_COLOR_AMBIENT, &color);
        m_ambient.setValue(color.r, color.g, color.b, 1);
        aiGetMaterialColor(m_materialRef, AI_MATKEY_COLOR_DIFFUSE, &color);
        m_diffuse.setValue(color.r, color.g, color.b, color.a);
        aiGetMaterialColor(m_materialRef, AI_MATKEY_COLOR_SPECULAR, &color);
        m_specular.setValue(color.r, color.g, color.b, 1);
        float shininess;
        aiGetMaterialFloat(m_materialRef, AI_MATKEY_SHININESS, &shininess);
        m_shininess = shininess;
        setMaterialTextures();
    }
    ~Material() {
        internal::deleteObject(m_mainTexture);
        internal::deleteObject(m_sphereTexture);
        m_modelRef = 0;
        m_materialRef = 0;
        m_encodingRef = 0;
        m_ambient.setZero();
        m_diffuse.setZero();
        m_specular.setZero();
        m_shininess = 0;
        m_nindices = 0;
        m_index = 0;
        m_visible = false;
    }

    IModel *parentModelRef() const { return m_modelRef; }
    const IString *name(IEncoding::LanguageType /* type */) const { return 0; }
    const IString *englishName() const { return 0; }
    const IString *userDataArea() const { return 0; }
    const IString *mainTexture() const { return m_mainTexture; }
    const IString *sphereTexture() const { return m_sphereTexture; }
    const IString *toonTexture() const { return 0; }
    SphereTextureRenderMode sphereTextureRenderMode() const { return m_sphereTextureRenderMode; }
    Color ambient() const { return m_ambient; }
    Color diffuse() const { return m_diffuse; }
    Color specular() const { return m_specular; }
    Color edgeColor() const { return kZeroC; }
    Color mainTextureBlend() const { return kWhiteColor; }
    Color sphereTextureBlend() const { return kWhiteColor; }
    Color toonTextureBlend() const { return kWhiteColor; }
    IndexRange indexRange() const { return IndexRange(); }
    float shininess() const { return m_shininess; }
    IVertex::EdgeSizePrecision edgeSize() const { return 1; }
    int index() const { return m_index; }
    int mainTextureIndex() const { return -1; }
    int sphereTextureIndex() const { return -1; }
    int toonTextureIndex() const { return -1; }
    int sizeofIndices() const { return m_nindices; }
    bool isSharedToonTextureUsed() const { return false; }
    bool isCullingDisabled() const { return !btFuzzyZero(m_diffuse.w() - 1); }
    bool isCastingShadowEnabled() const { return false; }
    bool isCastingShadowMapEnabled() const { return !btFuzzyZero(m_diffuse.x() - 0.98f); }
    bool isShadowMapEnabled() const { return isCastingShadowMapEnabled(); }
    bool isEdgeEnabled() const { return false; }
    bool isVertexColorEnabled() const { return false; }
    bool isVisible() const { return m_visible; }

    void setName(const IString * /* value */, IEncoding::LanguageType /* type */) {}
    void setUserDataArea(const IString * /* value */) {}
    void setMainTexture(const IString * /* value */) {}
    void setSphereTexture(const IString * /* value */) {}
    void setToonTexture(const IString * /* value */) {}
    void setSphereTextureRenderMode(SphereTextureRenderMode /* value */) {}
    void setAmbient(const Color &value) { m_ambient = value; }
    void setDiffuse(const Color &value) { m_diffuse = value; }
    void setSpecular(const Color &value) { m_specular = value; }
    void setEdgeColor(const Color & /* value */) {}
    void setIndexRange(const IndexRange & /* value */) {}
    void setShininess(float value) { m_shininess = value; }
    void setEdgeSize(const IVertex::EdgeSizePrecision & /* value */) {}
    void setMainTextureIndex(int /* value */) {}
    void setSphereTextureIndex(int /* value */) {}
    void setToonTextureIndex(int /* value */) {}
    void setIndices(int /* value */) {}
    void setFlags(int /* value */) {}
    void setSharedToonTextureUsed(bool /* value */) {}
    void setCullingDisabled(bool /* value */) {}
    void setCastingShadowEnabled(bool /* value */) {}
    void setCastingShadowMapEnabled(bool /* value */) {}
    void setShadowMapEnabled(bool /* value */) {}
    void setEdgeEnabled(bool /* value */) {}
    void setVertexColorEnabled(bool /* value */) {}
    void setVisible(bool value) { m_visible = value; }

private:
    void setMaterialTextures() {
        aiString texturePath;
        if (m_materialRef->GetTexture(aiTextureType_DIFFUSE, 0, &texturePath) == aiReturn_SUCCESS) {
            const uint8 *path = reinterpret_cast<const uint8 *>(texturePath.data);
            const IString *separator = m_encodingRef->stringConstant(IEncoding::kAsterisk);
            const IString *sph = m_encodingRef->stringConstant(IEncoding::kSPHExtension);
            const IString *spa = m_encodingRef->stringConstant(IEncoding::kSPAExtension);
            IString *texture = m_encodingRef->toString(path, texturePath.length, IString::kShiftJIS);
            if (texture->contains(separator)) {
                Array<IString *> tokens;
                texture->split(separator, 2, tokens);
                internal::deleteObject(texture);
                IString *mainTexture = tokens[0];
                if (mainTexture->endsWith(sph)) {
                    m_sphereTexture = mainTexture;
                    m_sphereTextureRenderMode = kMultTexture;
                }
                else {
                    m_mainTexture = mainTexture;
                }
                if (tokens.count() == 2) {
                    IString *subTexture = tokens[1];
                    if (subTexture->endsWith(sph)) {
                        m_sphereTexture = subTexture;
                        m_sphereTextureRenderMode = kMultTexture;
                    }
                    else if (subTexture->endsWith(spa)) {
                        m_sphereTexture = subTexture;
                        m_sphereTextureRenderMode = kAddTexture;
                    }
                }
            }
            else if (texture->endsWith(sph)) {
                m_sphereTexture = texture;
                m_sphereTextureRenderMode = kMultTexture;
            }
        }
    }

    static const Color kWhiteColor;
    const aiMaterial *m_materialRef;
    asset::Model *m_modelRef;
    IEncoding *m_encodingRef;
    IString *m_mainTexture;
    IString *m_sphereTexture;
    SphereTextureRenderMode m_sphereTextureRenderMode;
    Color m_ambient;
    Color m_diffuse;
    Color m_specular;
    float m_shininess;
    int m_nindices;
    int m_index;
    bool m_visible;
};
const Color Material::kWhiteColor = Color(1, 1, 1, 1);

class OpacityMorph : public IMorph {
public:
    OpacityMorph(asset::Model *modelRef, const IEncoding *encodingRef)
        : m_encodingRef(encodingRef),
          m_modelRef(modelRef),
          m_opacity(modelRef->opacity())
    {
    }
    ~OpacityMorph() {
        m_encodingRef = 0;
        m_modelRef = 0;
        m_opacity = 0;
    }

    const IString *name(IEncoding::LanguageType /* type */) const {
        return m_encodingRef->stringConstant(IEncoding::kOpacityMorphAsset);
    }
    void setName(const IString * /* value */, IEncoding::LanguageType /* type */) {}
    int index() const { return 0; }
    IModel *parentModelRef() const { return m_modelRef; }
    Category category() const { return IMorph::kOther; }
    void setCategory(Category /* value */) {}
    Type type() const { return IMorph::kMaterialMorph; }
    bool hasParent() const { return false; }
    WeightPrecision weight() const { return m_opacity; }
    void setWeight(const WeightPrecision &value) {
        m_modelRef->setOpacity(Scalar(value));
        m_opacity = value;
    }
    void markDirty() {}

    void addBoneMorph(Bone * /* value */) {}
    void removeBoneMorph(Bone * /* value */) {}
    void addGroupMorph(Group * /* value */) {}
    void removeGroupMorph(Group * /* value */) {}
    void addMaterialMorph(Material * /* value */) {}
    void removeMaterialMorph(Material * /* value */) {}
    void addUVMorph(UV * /* value */) {}
    void removeUVMorph(UV * /* value */) {}
    void addVertexMorph(Vertex * /* value */) {}
    void removeVertexMorph(Vertex * /* value */) {}
    void addFlipMorph(Flip * /* value */) {}
    void removeFlipMorph(Flip * /* value */) {}
    void addImpulseMorph(Impulse * /* value */) {}
    void removeImpulseMorph(Impulse * /* value */) {}
    void setType(Type /* value */) {}
    void getBoneMorphs(Array<Bone *> &morphs) const { morphs.clear(); }
    void getGroupMorphs(Array<Group *> &morphs) const { morphs.clear(); }
    void getMaterialMorphs(Array<Material *> &morphs) const { morphs.clear(); }
    void getUVMorphs(Array<UV *> &morphs) const { morphs.clear(); }
    void getVertexMorphs(Array<Vertex *> &morphs) const { morphs.clear(); }
    void getFlipMorphs(Array<Flip *> &morphs) const { morphs.clear(); }
    void getImpulseMorphs(Array<Impulse *> &morphs) const { morphs.clear(); }

private:
    const IEncoding *m_encodingRef;
    asset::Model *m_modelRef;
    WeightPrecision m_opacity;
};

class Vertex : public IVertex {
public:
    Vertex(asset::Model *modelRef, const Vector3 &origin, const Vector3 &normal, const Vector3 &texcoord, int index)
        : m_modelRef(modelRef),
          m_origin(origin),
          m_normal(normal),
          m_texcoord(texcoord),
          m_index(index)
    {
    }
    ~Vertex() {
        m_origin.setZero();
        m_normal.setZero();
        m_texcoord.setZero();
        m_modelRef = 0;
        m_index = 0;
    }

    IModel *parentModelRef() const { return m_modelRef; }
    void performSkinning(Vector3 &/*position*/, Vector3 &/*normal*/) const {}
    void reset() {}
    Vector3 origin() const { return m_origin; }
    Vector3 normal() const { return m_normal; }
    Vector3 textureCoord() const { return m_texcoord; }
    Vector3 sdefC() const { return kZeroV3; }
    Vector3 sdefR0() const { return kZeroV3; }
    Vector3 sdefR1() const { return kZeroV3; }
    Vector4 uv(int /* index */) const { return kZeroV4; }
    Vector4 originUV(int /* index */) const { return kZeroV4; }
    Vector4 morphUV(int /* index */) const { return kZeroV4; }
    Vector3 delta() const { return kZeroV3; }
    Type type() const { return IVertex::kBdef1; }
    EdgeSizePrecision edgeSize() const { return 0; }
    WeightPrecision weight(int /* index */) const { return 0; }
    IBone *boneRef(int /* index */) const { return 0; }
    IMaterial *materialRef() const { return 0; }
    int index() const { return m_index; }
    void setOrigin(const Vector3 & /* value */) {}
    void setNormal(const Vector3 & /* value */) {}
    void setTextureCoord(const Vector3 & /* value */) {}
    void setSdefC(const Vector3 & /* value */) {}
    void setSdefR0(const Vector3 & /* value */) {}
    void setSdefR1(const Vector3 & /* value */) {}
    void setOriginUV(int /* index */, const Vector4 & /* value */) {}
    void setMorphUV(int /* index */, const Vector4 & /* value */) {}
    void setType(Type /* value */) {}
    void setEdgeSize(const EdgeSizePrecision & /* value */) {}
    void setWeight(int /* index */, const WeightPrecision & /* weight */) {}
    void setBoneRef(int /* index */, IBone * /* value */) {}
    void setMaterialRef(IMaterial * /* material */) {}

private:
    asset::Model *m_modelRef;
    Vector3 m_origin;
    Vector3 m_normal;
    Vector3 m_texcoord;
    int m_index;
};

class ProgressReporter : public Assimp::ProgressHandler {
public:
    ProgressReporter(IProgressReporter *reporterRef)
        : m_progressReporterRef(reporterRef)
    {
    }
    ~ProgressReporter() {}

    bool Update(float percentage) {
        if (m_progressReporterRef) {
            m_progressReporterRef->reportProgress(percentage);
        }
        return true;
    }

private:
    IProgressReporter *m_progressReporterRef;
};

#endif
}

namespace vpvl2
{
namespace VPVL2_VERSION_NS
{
namespace asset
{

Model::Model(IEncoding *encoding)
    :
      #if defined(VPVL2_LINK_ASSIMP) || defined(VPVL2_LINK_ASSIMP3)
      m_scene(0),
      #endif
      m_encodingRef(encoding),
      m_name(0),
      m_comment(0),
      m_parentSceneRef(0),
      m_parentModelRef(0),
      m_parentBoneRef(0),
      m_rootBoneRef(0),
      m_scaleBoneRef(0),
      m_opacityMorphRef(0),
      m_progressReporterRef(0),
      m_aabbMax(kZeroV3),
      m_aabbMin(kZeroV3),
      m_position(kZeroV3),
      m_rotation(Quaternion::getIdentity()),
      m_opacity(1),
      m_scaleFactor(10),
      m_visible(false)
{
#if defined(VPVL2_LINK_ASSIMP) || defined(VPVL2_LINK_ASSIMP3)
    m_rootBoneRef = m_bones.append(new RootBone(this, m_encodingRef));
    m_scaleBoneRef = m_bones.append(new ScaleBone(this, m_encodingRef));
    m_labels.append(new Label(this, m_bones, m_encodingRef));
    m_opacityMorphRef = m_morphs.append(new OpacityMorph(this, m_encodingRef));
#endif
}

Model::~Model()
{
    m_rootBoneRef = 0;
    m_scaleBoneRef = 0;
    m_opacityMorphRef = 0;
    m_progressReporterRef = 0;
    m_bones.releaseAll();
    m_labels.releaseAll();
    m_materials.releaseAll();
    m_morphs.releaseAll();
    m_vertices.releaseAll();
    internal::deleteObject(m_name);
    internal::deleteObject(m_comment);
    m_parentSceneRef = 0;
    m_parentBoneRef = 0;
    m_parentModelRef = 0;
    m_encodingRef = 0;
    m_position.setZero();
    m_rotation.setValue(0, 0, 0, 1);
    m_opacity = 0;
    m_scaleFactor = 0;
    m_visible = false;
#if defined(VPVL2_LINK_ASSIMP) || defined(VPVL2_LINK_ASSIMP3)
    m_scene = 0;
#endif
}

bool Model::load(const uint8 *data, vsize size)
{
#if defined(VPVL2_LINK_ASSIMP) || defined(VPVL2_LINK_ASSIMP3)
    int flags = aiProcessPreset_TargetRealtime_Fast;
    m_importer.SetProgressHandler(new ProgressReporter(m_progressReporterRef));
    m_scene = m_importer.ReadFileFromMemory(data, size, flags, ".x");
    m_importer.SetProgressHandler(0);
    const int nbones = m_bones.count();
    for (int i = 0; i < nbones; i++) {
        IBone *bone = m_bones[i];
        m_name2boneRefs.insert(bone->name(IEncoding::kDefaultLanguage)->toHashString(), bone);
    }
    const int nmorphs = m_morphs.count();
    for (int i = 0; i < nmorphs; i++) {
        IMorph *morph = m_morphs[i];
        m_name2morphRefs.insert(morph->name(IEncoding::kDefaultLanguage)->toHashString(), morph);
    }
    if (m_scene) {
        setMaterialRefsRecurse(m_scene, m_scene->mRootNode);
        setVertexRefsRecurse(m_scene, m_scene->mRootNode);
        return true;
    }
#else
    (void) data;
    (void) size;
#endif
    return false;
}

IBone *Model::findBoneRef(const IString *value) const
{
    IBone *const *bone = m_name2boneRefs.find(value->toHashString());
    return bone ? *bone : 0;
}

IMorph *Model::findMorphRef(const IString *value) const
{
    IMorph *const *morph = m_name2morphRefs.find(value->toHashString());
    return morph ? *morph : 0;
}

int Model::count(ObjectType value) const
{
    switch (value) {
    case kBone:
        return m_bones.count();
    case kIK:
        return 0;
    case kIndex:
        return 0;
    case kJoint:
        return 0;
    case kMaterial:
        return 0;
    case kMorph:
        return m_morphs.count();
    case kRigidBody:
        return 0;
    case kSoftBody:
        return 0;
    case kTexture:
        return 0;
    case kVertex:
        return m_vertices.count();
    default:
        return 0;
    }
}

void Model::getBoneRefs(Array<IBone *> &value) const
{
    value.copy(m_bones);
}

void Model::getJointRefs(Array<IJoint *> & /* value */) const
{
    /* do nothing */
}

void Model::getLabelRefs(Array<ILabel *> &value) const
{
    value.copy(m_labels);
}

void Model::getMaterialRefs(Array<IMaterial *> &value) const
{
    value.copy(m_materials);
}

void Model::getMorphRefs(Array<IMorph *> &value) const
{
    value.copy(m_morphs);
}

void Model::getRigidBodyRefs(Array<IRigidBody *> & /* value */) const
{
    /* do nothing */
}

void Model::getTextureRefs(Array<const IString *> & /* value */) const
{
    /* do nothing */
}

void Model::getVertexRefs(Array<IVertex *> &value) const
{
    value.copy(m_vertices);
}

void Model::getIndices(Array<int> & /* value */) const
{
}

void Model::getIKConstraintRefs(Array<IBone::IKConstraint *> & /* value */) const
{
}

void Model::getBoundingBox(Vector3 &min, Vector3 &max) const
{
    min.setZero();
    max.setZero();
#if defined(VPVL2_LINK_ASSIMP) || defined(VPVL2_LINK_ASSIMP3)
    getBoundingBoxRecurse(m_scene, m_scene->mRootNode, min, max);
#endif
}

void Model::setName(const IString *value, IEncoding::LanguageType /* type */)
{
    internal::setString(value, m_name);
}

void Model::setComment(const IString *value, IEncoding::LanguageType /* type */)
{
    internal::setString(value, m_comment);
}

void Model::setEncodingType(IString::Codec /* value */)
{
}

void Model::setWorldTranslation(const Vector3 &value)
{
    m_rootBoneRef->setLocalTranslation(value);
}

void Model::setWorldPositionInternal(const Vector3 &value)
{
    m_position = value;
}

void Model::setWorldOrientation(const Quaternion &value)
{
    m_rootBoneRef->setLocalOrientation(value);
}

void Model::setWorldRotationInternal(const Quaternion &value)
{
    m_rotation = value;
}

void Model::setOpacity(const Scalar &value)
{
    m_opacity = value;
}

void Model::setScaleFactor(const Scalar &value)
{
    m_scaleBoneRef->setLocalTranslation(Vector3(value, value, value));
}

void Model::setScaleFactorInternal(const Scalar &value)
{
    m_scaleFactor = value;
}

void Model::setParentSceneRef(Scene *value)
{
    m_parentSceneRef = value;
}

void Model::setParentModelRef(IModel *value)
{
    if (!internal::ModelHelper::hasModelLoopChain(value, this)) {
        m_parentModelRef = value;
    }
}

void Model::setParentBoneRef(IBone *value)
{
    if (!internal::ModelHelper::hasBoneLoopChain(value, m_parentModelRef)) {
        m_parentBoneRef = value;
    }
}

void Model::setVisible(bool value)
{
    m_visible = value;
}

void Model::setAabb(const Vector3 &min, const Vector3 &max)
{
    m_aabbMin = min;
    m_aabbMax = max;
}

void Model::getAabb(Vector3 &min, Vector3 &max) const
{
    min = m_aabbMin;
    max = m_aabbMax;
}

float32 Model::version() const
{
    return 1.0f;
}

void Model::setVersion(float32 /* value */)
{
    /* do nothing */
}

int Model::maxUVCount() const
{
    return 0;
}

void Model::setMaxUVCount(int /* value */)
{
    /* do nothing */
}

IBone *Model::createBone()
{
    return 0;
}

IJoint *Model::createJoint()
{
    return 0;
}

ILabel *Model::createLabel()
{
    return 0;
}

IMaterial *Model::createMaterial()
{
    return 0;
}

IMorph *Model::createMorph()
{
    return 0;
}

IRigidBody *Model::createRigidBody()
{
    return 0;
}

IVertex *Model::createVertex()
{
    return 0;
}

IBone *Model::findBoneRefAt(int /* value */) const
{
    return 0;
}

IJoint *Model::findJointRefAt(int /* value */) const
{
    return 0;
}

ILabel *Model::findLabelRefAt(int /* value */) const
{
    return 0;
}

IMaterial *Model::findMaterialRefAt(int /* value */) const
{
    return 0;
}

IMorph *Model::findMorphRefAt(int /* value */) const
{
    return 0;
}

IRigidBody *Model::findRigidBodyRefAt(int /* value */) const
{
    return 0;
}

IVertex *Model::findVertexRefAt(int /* value */) const
{
    return 0;
}

void Model::setIndices(const Array<int> & /* value */)
{
    /* do nothing */
}

void Model::addBone(IBone * /* value */)
{
    /* do nothing */
}

void Model::addJoint(IJoint * /* value */)
{
    /* do nothing */
}

void Model::addLabel(ILabel * /* value */)
{
    /* do nothing */
}

void Model::addMaterial(IMaterial * /* value */)
{
    /* do nothing */
}

void Model::addMorph(IMorph * /* value */)
{
    /* do nothing */
}

void Model::addRigidBody(IRigidBody * /* value */)
{
    /* do nothing */
}

void Model::addVertex(IVertex * /* value */)
{
    /* do nothing */
}

void Model::removeBone(IBone * /* value */)
{
    /* do nothing */
}

void Model::removeJoint(IJoint * /* value */)
{
    /* do nothing */
}

void Model::removeLabel(ILabel * /* value */)
{
    /* do nothing */
}

void Model::removeMaterial(IMaterial * /* value */)
{
    /* do nothing */
}

void Model::removeMorph(IMorph * /* value */)
{
    /* do nothing */
}

void Model::removeRigidBody(IRigidBody * /* value */)
{
    /* do nothing */
}

void Model::removeVertex(IVertex * /* value */)
{
    /* do nothing */
}

IProgressReporter *Model::progressReporterRef() const
{
    return m_progressReporterRef;
}

void Model::setProgressReporterRef(IProgressReporter *value)
{
    m_progressReporterRef = value;
}

#if defined(VPVL2_LINK_ASSIMP) || defined(VPVL2_LINK_ASSIMP3)
void Model::setIndicesRecurse(const aiScene *scene, const aiNode *node)
{
    const unsigned int nmeshes = node->mNumMeshes;
    for (unsigned int i = 0; i < nmeshes; i++) {
        const struct aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
        const struct aiFace *faces = mesh->mFaces;
        const unsigned int nfaces = mesh->mNumFaces;
        for (unsigned int j = 0; j < nfaces; j++) {
            const struct aiFace &face = faces[j];
            const unsigned int *indices = face.mIndices;
            const unsigned int nindices = face.mNumIndices;
            for (unsigned int k = 0; k < nindices; k++) {
                m_indices.append(indices[k]);
            }
        }
    }
}

void Model::setMaterialRefsRecurse(const aiScene *scene, const aiNode *node)
{
    const unsigned int nmeshes = node->mNumMeshes;
    for (unsigned int i = 0; i < nmeshes; i++) {
        const struct aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
        const struct aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];
        const struct aiFace *faces = mesh->mFaces;
        const unsigned int nfaces = mesh->mNumFaces;
        unsigned int nindices = 0;
        for (unsigned int j = 0; j < nfaces; j++) {
            nindices += faces[j].mNumIndices;
        }
        m_materials.append(new Material(this, material, m_encodingRef, nindices, i));
    }
}

void Model::setVertexRefsRecurse(const aiScene *scene, const aiNode *node)
{
    const unsigned int nmeshes = node->mNumMeshes;
    for (unsigned int i = 0; i < nmeshes; i++) {
        const struct aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
        const aiVector3D *meshVertices = mesh->mVertices;
        const aiVector3D *meshNormals = mesh->HasNormals() ? mesh->mNormals : 0;
        const aiVector3D *meshTextureCoords = mesh->HasTextureCoords(0) ? mesh->mTextureCoords[0] : 0;
        const unsigned int nvertices = mesh->mNumVertices;
        for (unsigned int j = 0; j < nvertices; j++) {
            const aiVector3D &v = meshVertices[j];
            const aiVector3D &n = meshNormals ? meshVertices[j] : aiVector3D();
            const aiVector3D &t = meshTextureCoords ? meshTextureCoords[j] : aiVector3D();
            m_vertices.append(new Vertex(this, Vector3(v.x, v.y, v.z), Vector3(n.x, n.y, n.z), Vector3(t.x, t.y, t.z), j));
        }
    }
}

void Model::getBoundingBoxRecurse(const aiScene *scene, const aiNode *node, Vector3 &min, Vector3 &max) const
{
    const unsigned int nmeshes = node->mNumMeshes;
    const Scalar &scale = scaleFactor();
    Vector3 position;
    for (unsigned int i = 0; i < nmeshes; i++) {
        const struct aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
        const aiVector3D *vertices = mesh->mVertices;
        const unsigned int nvertices = mesh->mNumVertices;
        for (unsigned int j = 0; j < nvertices; j++) {
            const aiVector3D &vertex = vertices[i] * scale;
            position.setValue(vertex.x, vertex.y, vertex.z);
            min.setMin(position);
            max.setMax(position);
        }
    }
}
#endif

} /* namespace asset */
} /* namespace VPVL2_VERSION_NS */
} /* namespace vpvl2 */
