/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once

#include "glmCoreAPI.h"
#include "glmRTTI.h"
#include "glmGenericSpecificConverterBone.h"
#include "glmBonePhysicConfiguration.h"
#include "glmEffectorMirrorSource.h"
#include "glmBlindData.h"
#include "glmHierarchicalSkeletonDescription.h"
#include <glmMutex.h>

#include <glmGolaemCharacterDeclarations.h> // GolaemCharacter declarations

namespace glm
{
    struct GenericSpecificConverterMappingV0;
    struct GenericSpecificConverterMappingV1;
    struct GenericSpecificConverterMappingV2;
    struct GenericSpecificConverterMappingV3;
    struct GenericSpecificConverterMappingV4;
    struct GenericSpecificConverterMappingV5;
    struct GenericSpecificConverterMappingV6;
    struct GenericSpecificConverterMappingV7;

    class SkeletonMappingContainer; //for charamaker access

    class FastBoundingBox;

    /**
    * \brief Represent the generalization of a skeleton (the conversion from the hierarchical skeleton to the generic skeleton)
    *
    * \author dev@golaem.com
    *
    */
    class GLM_CORE_API GenericSpecificConverterMappingV8
    {
        GLM_RTTI("05C7B16A-E5CF-45EB-ABF1-2C5B1A9AB1D3", glm::GenericSpecificConverterMappingV8, addAttributes)

    public: //statics
        inline static int getCurrentVersionNumber()
        {
            return _currentVersionNumber;
        }
        static int findRigidBodiesBoneCorrespondances(glm::PODArray<int>& rigidBodiesBoneCorrespondance, glm::PODArray<int>& rigidBodiesBoneCorrespondanceInverse, const GenericSpecificConverterMappingV8* converterMapping);
        static void buildBoneChain(glm::PODArray<const HierarchicalBoneV1*>& outBoneChain, const PODArray<HierarchicalBoneV1*>& inHierarchicalSkeletonBones, const GenericSpecificConverterBoneV1& inBoneConverter, int inAuxExt, bool orderFromFirstBoneInConverter);
        static Vector3 computeIkNormalPlane(glm::PODArray<const HierarchicalBoneV1*>& inBoneChain, const Vector3& inUpAxis);
        static int findFatherIndexInSpines(const GenericSpecificConverterBoneV1& source, const GenericSpecificConverterMappingV8& mapping);
        static int findFatherIndexInLimbs(const GenericSpecificConverterBoneV1& source, const GenericSpecificConverterMappingV8& mapping);

        static const GenericSpecificConverterBone* findGenericSpecificConverterBone(int genericBoneIndex, const GenericSpecificConverterMappingV8& converterMapping, int* retAuxExt=NULL);
        static int findExtensionEOCSpecificBoneIndex(int genericBoneIndex, const GenericSpecificConverterMappingV8& converterMapping);

    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

        GenericSpecificConverterMappingV8();
        ~GenericSpecificConverterMappingV8();

        /**
	    * \brief Skeleton description assignement
	    * \warning the skeleton description pointer is kept and should not be deleted before the skeleton Mapping
	    * \warning this will reset the mapping configuration
	    */
        void duplicateHierarchicalSkeletonDescription(const HierarchicalSkeletonDescriptionV1& skeletonDescription);

        void setPelvis(const HierarchicalBoneV1* pelvisBone);
        bool setPelvis(const char* pelvisName);

        /**
	    * addSpine / addLimb / addEffector methods return the index of the newly added bone, or a negative value if it could not be added:
	    * -1 : _skeletonDescription == NULL
	    * -2 : boneFirst == NULL || boneLast == NULL
	    * -3 : hierarchy problem
	    */
        int addSpine(const GenericSpecificConverterBoneV1& bone);
        int addSpine(int spineFirstSpecificBoneIndex, int spineLastSpecificBoneIndex, bool computeFullExtentLength, const Vector3& ikNormalPlane = Vector3(0, 0, 0));

        int addLimb(const GenericSpecificConverterBoneV1& bone);
        int addLimb(SkeletonPartType::Value type,
                    int firstAuxiliarySpecificBoneIndex, int lastAuxiliarySpecificBoneIndex,
                    int firstExtensionSpecificBoneIndex, int lastExtensionSpecificBoneIndex,
                    CoronalPlaneLocation::Value coronalLocation, TransversePlaneLocation::Value transverseLocation, SagittalPlaneLocation::Value sagittalLocation,
                    bool auxiliaryComputeFullExtentLength, bool extensionComputeFullExtentLength,
                    const Vector3& auxiliaryIkNormalPlane = Vector3(0, 0, 0), const Vector3& extensionIkNormalPlane = Vector3(0, 0, 0));

        int addEffector(const GenericSpecificConverterBoneV1& bone);
        int addEffector(SkeletonPartType::Value type,
                        int firstAuxiliarySpecificBoneIndex, int lastAuxiliarySpecificBoneIndex,
                        int firstExtensionSpecificBoneIndex, int lastExtensionSpecificBoneIndex,
                        bool auxiliaryComputeFullExtentLength, bool extensionComputeFullExtentLength,
                        const Vector3& auxiliaryIkNormalPlane = Vector3(0, 0, 0), const Vector3& extensionIkNormalPlane = Vector3(0, 0, 0));

        void addIkExcludedBonesByBoneName(const char* bones[] = NULL, int nbBones = 0);
        void addIkExcludedBonesByBoneId(glm::PODArray<const HierarchicalBoneV1*> bones);
        const glm::PODArray<int>& getIkExcludedBones() const;

        /**
	    * \brief Finish the GenericSpecificConverterMappingV8 (removes the pointer to the HierarchicalSkeletonDescription, delete all bone generalizations)
	    */
        void finish();
        void clearMappingPart();
        void clearSpecificAnimationBlindData(); //will only remove _blindDataInfo that are of the type BlindDataV1::Type::SPECIFIC_ANIMATION
        static void clearSpecificAnimationBlindData(glm::Array<BlindDataV1>& blindDataInfo);
        //************************************************************
        //! @name Loading & saving
        //************************************************************
        //@{

        /**
	    * \brief load the Mapping configuration from a binary file
	    * \param file
	    */
        LoadSkeletonMappingErrorCode::Value loadMapping(const char* file);

        /**
	    * \brief load the Mapping configuration from a file stream
	    */
        LoadSkeletonMappingErrorCode::Value loadMapping(std::ifstream& istream);

        /**
	    * \brief save the Mapping configuration into a binary file
	    * \param file
	    */
        SaveSkeletonMappingErrorCode::Value saveMapping(const char* file) const;

        /**
	    * \brief save the Mapping configuration into a file stream
	    */
        void saveMapping(std::ofstream& ostream) const;

        /**
	    * \brief load the Mapping configuration from a xml file
	    * \param file
	    */
        LoadSkeletonMappingErrorCode::Value loadMappingXML(const char* file);
        LoadSkeletonMappingErrorCode::Value loadMappingXML(XMLStream& stream, DataItem& gskmContainer);

        /**
	    * \brief save the Mapping configuration into a xml file
	    * \param file
	    */
        SaveSkeletonMappingErrorCode::Value saveMappingXML(const char* file) const;
        SaveSkeletonMappingErrorCode::Value saveMappingXMLStream(XMLStream& stream, DataItem& dataItem) const;

        /**
	    * \brief Try to autodetect the mapping
	    * \param auxToExtMinAngleDifferenceRatio minimum percent of the average angles between bones that defines a change between auxiliary and extension (5% is a "popular" level of signifiance)
	    * \param rollBoneMaxAngleValue maximum angle that a bone should have to be excluded from IK (0.05f is a value giving good results in most cases)
	    */
        bool computeAutomaticMapping(float auxToExtMinAngleDifferenceRatio = 0.05f, Angle rollBoneMaxAngleValue = 0.05f);
        void computeAutomaticNames();
        //@}

        //************************************************************
        //! @name Skeleton shape
        //************************************************************
        //@{
        void computeAutomaticBoneConfigurationSizes(bool forceManulValuesAlso, float boneWidthLengthRatio = 0.3);
        void computeAutomaticJointLimits(GenericSpecificConverterBoneV1& converterBone, Angle angularLimitOnIkPlane, Angle angularLimitOnFirstAndLastAuxiliary, Angle angularLimitOnFirstAndLastExtension);
        void computeAutomaticJointLimits();
        void autoConfigureRagdoll(bool resetCurrentValues);
        //@}

        //************************************************************
        //! @name Accessors
        //************************************************************
        //@{
        /**
	    * \brief Get the upper direction
	    */
        inline const Vector3& getUpAxis() const;

        /**
	    * \brief Set the upper direction
	    */
        void setUpAxis(const Vector3& upAxis);

        /**
	    * \brief Get the front direction
	    */
        inline const Vector3& getFrontAxis() const;

        /**
	    * \brief Set the front direction
	    */
        void setFrontAxis(const Vector3& frontAxis);

        /**
	    * \brief Get the orientation to apply to get the skeleton Zup Xfront
	    * \note This orientation is computed thanks to the front and up axis.
	    */
        inline const Quaternion& getOrientationToZUpXFront() const;

        /**
	    * \brief Get the orientation to apply to get the skeleton Zup Xfront
	    * \note This orientation is computed thanks to the front and up axis.
	    */
        inline const Quaternion& getOrientationToZUpXFrontInverse() const;

        /**
	    * \brief Bone configurations. The bone configuration for the specific bone index i is at getBoneConfigurations()[i]
	    */
        inline int getBoneConfigurationsCount() const;
        inline const BonePhysicConfigurationV1* getBoneConfigurations() const;
        inline BonePhysicConfigurationV1* touchBoneConfigurations();

        /**
	    * \brief Sources for channel mirror. Mirror source for channel i is getChannelMirrorSources()[i]
	    */
        inline int getChannelMirrorSourcesCount() const;
        inline const int* getChannelMirrorSources() const;
        inline int* touchChannelMirrorSources();
        void initChannelMirrorSources(int channelMirrorSourcesCount);

        /**
	    * \brief Sources for effector mirror. Mirror indices for channel i are at getEffectorMirrorSources()[i].
	    */
        inline int getEffectorMirrorSourcesCount() const;
        inline const EffectorMirrorSourceV0* getEffectorMirrorSources() const;
        inline EffectorMirrorSourceV0* touchEffectorMirrorSources();

        /**
	    * \brief Sources for spine mirror. Mirror source for spine i is getSpineMirrorSources()[i]
	    */
        inline int getSpineMirrorSourcesCount() const;
        inline const int* getSpineMirrorSources() const;
        inline int* touchSpineMirrorSources();
        void initSpineMirrorSources(int spineMirrorSourcesCount);

        /**
	    * \brief Use the channel mirror info to estimate the mirror bone of a specific bone index
	    */
        int getEstimatedMirrorBoneIndex(int specificBoneIndex) const;

        /**
	    * \brief Blind data
	    */
        inline const glm::Array<BlindDataV1>& getBlindDataInfo() const;
        inline glm::Array<BlindDataV1>& touchBlindDataInfo();

        /**
	    * \brief Squash & Stretch
	    */
        inline const float* getSquashStretchWeightsArray() const;
        inline float* touchSquashStretchWeightsArray();

        inline bool isBoneUsingSnSScale(int specificBoneId) const;
        inline SnsScaleMode::Value getBoneSnsScaleMode(int specificBoneId, Axis::Value boneAxis) const;
        inline void setBoneSnsScaleMode(int specificBoneId, Axis::Value boneAxis, SnsScaleMode::Value snsScaleValue);
        //@}

        //************************************************************
        //! @name Configuration accessors
        //************************************************************
        //@{
        /**
	    * \brief Set the quality for the IK chain resolution (betwen 0 and 1), default is 0.99 (99%)
	    * \param quality
	    */
        inline void setQuality(float quality);

        //------------------------------------------------------------
        /** Get the tolerance for the end of chain position
	    *
	    * \return the max distance between end of chain's position and normalized link's end position
	    */
        inline float getQuality() const;

        //------------------------------------------------------------
        /** Set the max number of iterations trough the inverse kinematic algorithm
	    *
	    * \param nbMaxIteration
	    */
        inline void setMaxIteration(const unsigned int& nbMaxIteration);

        //------------------------------------------------------------
        /** Get the max number of iterations trough the inverse kinematic algorithm
	    *
	    * \return the max distance between end of chain's position and normalized link's end position
	    */
        inline unsigned int getMaxIteration() const;

        /**
	    * \brief Tells if the IK cache was allowed for a specific converter bone
	    */
        bool isCacheAllowedOn(const GenericSpecificConverterBoneV1* boneConverterPtr, int auxExt) const;
        /**
	    * \brief Tells if the IK cache is allowed for a specific converter bone
	    */
        void setCacheAllowedOn(const GenericSpecificConverterBoneV1* boneConverterPtr, int auxExt, bool allowed);

        //@}

        /**
	    * \brief check that the currently configured skeleton mapping is valid (should be called AFTER buildcacheData() )
	    * \param outputErrorMessage stream where to write the error message (can be set to NULL)
	    * \return true if the skeleton is valid
	    */
        bool checkSkeletonMapping(std::ostream* outputErrorMessage) const;
        /**
	    * \brief computes (estimate) normalized ground height delta per limb
	    */
        void setGroundHeights(float groundWorldHeight = 0.f);

        /**
	    * \brief returns a normalized ground height delta for one limb
	    */
        float getGroundHeight(int limbIndex) const;

        int computeLowestBoneIndexOnLimb(int limbIndex, float* returnLowestHeight=NULL) const;


        //************************************************************
        //! @name Bones accessors
        //************************************************************
        //@{
        inline const HierarchicalBoneV1* getPelvisBone() const;

        inline size_t getSpineBonesSize() const;
        inline const glm::Array<GenericSpecificConverterBoneV1>& getSpineBones() const;
        inline glm::Array<GenericSpecificConverterBoneV1>& touchSpineBones();

        inline size_t getLimbBonesSize() const;
        inline const glm::Array<GenericSpecificConverterBoneV1>& getLimbBones() const;
        inline glm::Array<GenericSpecificConverterBoneV1>& touchLimbBones();

        inline size_t getEffectorBonesSize() const;
        inline const glm::Array<GenericSpecificConverterBoneV1>& getEffectorBones() const;
        inline glm::Array<GenericSpecificConverterBoneV1>& touchEffectorBones();

        const glm::Array<glm::GlmString>& getSpineBoneNames() const;
        void getSpineBoneName(glm::GlmString& name, size_t spineIdx) const;
        void setSpineBoneName(size_t spineIdx, const glm::GlmString& name);

        const glm::Array<glm::GlmString>& getLimbBoneNames() const;
        void getLimbBoneName(glm::GlmString& name, size_t limbIdx) const;
        void setLimbBoneName(size_t limbIdx, const glm::GlmString& name);

        void getEndOfChainBoneNames(glm::Array<glm::GlmString>& endOfChainBoneNames) const;

        const glm::Array<glm::GlmString>& getEffectorBoneNames() const;
        void getEffectorBoneName(glm::GlmString& name, size_t effectorIdx) const;
        void setEffectorBoneName(size_t effectorIdx, const glm::GlmString& name);
        //@}

        //************************************************************
        //! @name Retrocompatibility methods
        //************************************************************
        //@{
        /**
	    * \brief V0 backwards compatibility
	    */
        void createFromV0(GenericSpecificConverterMappingV0& masterV0);

        /**
	    * \brief V1 backwards compatibility
	    */
        void createFromV1(GenericSpecificConverterMappingV1& masterV1);

        /**
	    * \brief V2 backwards compatibility
	    */
        void createFromV2(GenericSpecificConverterMappingV2& masterV2);

        /**
	    * \brief V3 backwards compatibility
	    */
        void createFromV3(GenericSpecificConverterMappingV3& masterV3);

        /**
	    * \brief V4 backwards compatibility
	    */
        void createFromV4(GenericSpecificConverterMappingV4& masterV4);

        /**
	    * \brief V5 backwards compatibility
	    */
        void createFromV5(GenericSpecificConverterMappingV5& masterV5);
        void exportToV5(GenericSpecificConverterMappingV5& masterV5) const;

        /**
	    * \brief V6 backwards compatibility
	    */
        void createFromV6(GenericSpecificConverterMappingV6& masterV6);

        /**
	    * \brief V7 backwards compatibility
	    */
        void createFromV7(const GenericSpecificConverterMappingV7& masterV7);

        /**
	    * \brief "Steal" Data structures from the source, source should not be used anymore after this call
	    */
        //void createFrom(GenericSpecificConverterMappingV8& source);
        void createFromConst(const GenericSpecificConverterMappingV8& source); // for GolaemCharacter serialization
        //@}

        size_t countExportedBlindData() const;

        void computePerceptionBoundingBox(FastBoundingBox& boundingBox) const;
        float computeRootHeight(const Array<Vector3>& bonelocalPosOverrides, const Array<Quaternion>& bonelocalOriOverrides) const;

        int getBlindDataTotalFloatCount() const;
        void updateBlindDataFloatCount();

    protected:
        /**
         * \brief register the inspectable attributes to the class RTTI
         */
        static void addAttributes();

    private:
        GLM_DISALLOW_COPY_AND_ASSIGN(GenericSpecificConverterMappingV8);

        //************************************************************
        //! @name Automatic mapping dedicated methods
        //************************************************************
        //@{
        /**
	    * \brief Add limbs from the set that have opposites sagittal scores. Other limbs will be added with a generic type isntead of the given one
	    */
        void addLimbs(GlmSet<const HierarchicalBoneV1*> isomorphicBones, const Vector3& leftToRightAxis,
                      float minCoronal, float maxCoronal,
                      float minTransverse, float maxTransverse,
                      float minSagittal, float maxSagittal,
                      float auxToExtMinAngleDifferenceRatio, Angle rollBoneMaxAngleValue,
                      bool forceTypeToGeneric,
                      int minChainLength = 2);

        /**
	    * \brief Refine limb: find the correct auxiliary/extension parts, add ik exclusions and roll attributes
	    * \param auxToExtMinAngleDifferenceRatio minimum percent of the average angles between bones that defines a change between auxiliary and extension (5% is a "popular" level of signifiance)
	    * \param rollBoneMaxAngleValue maximum angle that a bone should have to be excluded from IK (0.05f is a value giving good results in most cases)
	    */
        void refineLimb(GenericSpecificConverterBoneV1& limb, float auxToExtMinAngleDifferenceRatio, Angle rollBoneMaxAngleValue);
        void addEffectors(GenericSpecificConverterBoneV1& limb, glm::PODArray<int>& addedEffectorIndices);

        /**
	    * \brief Compute the IK normal planes only for a simple biped (Human)
	    */
        void computeSimpleBipedIkNormalPlanes();
        Vector3 getIkNormalPlaneFromWorldBendingSide(const Vector3& axis, int firstBoneId, int lastBoneId);
        //@}

    public:
        bool _cacheAllowedGlobal; //value returned by the isCacheAllowedOn(...) method when the bone is not found in the _cacheAllowed array
        HierarchicalSkeletonDescriptionV1* _skeletonDescription;
        Array<BoneDisplayConfigurationV0>* _skeletonDisplay = NULL; // pointer from GolaemCharacter, not serialized here

    private:
        //************************************************************
        //! @name Serialized data
        //************************************************************
        //@{

        int _pelvisBoneIndex;
        glm::Array<GenericSpecificConverterBoneV1> _spineBones;
        glm::Array<GenericSpecificConverterBoneV1> _limbBones;
        glm::Array<GenericSpecificConverterBoneV1> _effectorBones;

        glm::Array<glm::GlmString> _spineBoneNames;
        glm::Array<glm::GlmString> _limbBoneNames;
        glm::Array<glm::GlmString> _effectorBoneNames;

        glm::PODArray<int> _ikExcludedBones; // bones excluded from IK solving

        Vector3 _upAxis;
        Vector3 _frontAxis;

        glm::Array<BonePhysicConfigurationV1> _boneConfigurations;

        glm::PODArray<int> _channelMirrorSources;

        glm::Array<EffectorMirrorSourceV0> _effectorMirrorSources;

        glm::PODArray<int> _spineMirrorSources;

        glm::PODArray<float> _groundHeightArray;

        glm::Array<BlindDataV1> _blindDataInfo;

        glm::PODArray<float> _squashStretchWeightsArray;

        glm::PODArray<int> _bonesSnsScaleModes; //int because XML serialization does not support char (only 6bits are needed here)

        glm::PODArray<bool> _cacheAllowed;
        //@}

        //************************************************************
        //! @name configuration
        //************************************************************
        //@{
        float _quality;
        unsigned int _maxIterations;
        static int _currentVersionNumber;
        //@}

        Quaternion _orientationToZUpXFront;
        Quaternion _orientationToZUpXFrontInverse;

        bool _updateBlindDataFloatCount = true;
        int _blindDataFloatCount = 0;

        glm::RWSpinLock _buildCacheDataLock;

        friend class GenericSpecificConverter;
        friend class SkeletonMappingContainer; //for charamaker access
    };

    //************************************************************
    // Definition of inline methods
    //************************************************************

    //-------------------------------------------------------------------------
    inline bool GenericSpecificConverterMappingV8::isBoneUsingSnSScale(int specificBoneId) const
    {
        return getBoneSnsScaleMode(specificBoneId, Axis::X) != SnsScaleMode::NO_SCALE || getBoneSnsScaleMode(specificBoneId, Axis::Y) != SnsScaleMode::NO_SCALE || getBoneSnsScaleMode(specificBoneId, Axis::Z) != SnsScaleMode::NO_SCALE;
    }

    //-------------------------------------------------------------------------
    // getBoneSnsScaleMode
    //-------------------------------------------------------------------------
    inline SnsScaleMode::Value
    GenericSpecificConverterMappingV8::getBoneSnsScaleMode(int specificBoneId, Axis::Value boneAxis) const
    {
        //The 8 bits (char) signification of the _bonesSnsScaleModes[specificBoneId] are for axis uuZZYYXX (first two bits are unused)

        int retValue = _bonesSnsScaleModes[specificBoneId] >> (boneAxis * 2); //make the bits we're interested in the last 2 of the char
        retValue = retValue & 3;                                              //then mask all other bits (3 = 00000011)
        return (SnsScaleMode::Value)retValue;
    }

    //-------------------------------------------------------------------------
    // setBoneSnsScaleMode
    //-------------------------------------------------------------------------
    inline void
    GenericSpecificConverterMappingV8::setBoneSnsScaleMode(int specificBoneId, Axis::Value boneAxis, SnsScaleMode::Value snsScaleValue)
    {
        //The 8 bits (char) signification of the _bonesSnsScaleModes[specificBoneId] are for axis uuZZYYXX (first two bits are unused)

        int newValue = (snsScaleValue & 3) << (boneAxis * 2); // mask all other bits than the last 2 (3 == 00000011) and put the 2 bits at the desired place

        int axisMask = 3 << (boneAxis * 2);
        int currentValue = _bonesSnsScaleModes[specificBoneId] & (~axisMask); //mask current value by the axis mask

        _bonesSnsScaleModes[specificBoneId] = currentValue | newValue; //add the new value to the current ones
    }

    //-------------------------------------------------------------------------
    // setQuality
    //-------------------------------------------------------------------------
    inline void GenericSpecificConverterMappingV8::setQuality(float quality)
    {
        GLM_ASSERT(quality > 0. && quality <= 1.);
        _quality = quality;
    }

    //-------------------------------------------------------------------------
    inline float GenericSpecificConverterMappingV8::getGroundHeight(int limbIndex) const
    {
        if (limbIndex < static_cast<int>(_groundHeightArray.size()))
        {
            return _groundHeightArray[limbIndex];
        }
        return 0.f;
    }

    //-------------------------------------------------------------------------
    inline const Vector3& GenericSpecificConverterMappingV8::getUpAxis() const
    {
        return _upAxis;
    }

    //-------------------------------------------------------------------------
    inline const Vector3& GenericSpecificConverterMappingV8::getFrontAxis() const
    {
        return _frontAxis;
    }

    //-------------------------------------------------------------------------
    inline const Quaternion& GenericSpecificConverterMappingV8::getOrientationToZUpXFront() const
    {
        return _orientationToZUpXFront;
    }

    //-------------------------------------------------------------------------
    inline const Quaternion& GenericSpecificConverterMappingV8::getOrientationToZUpXFrontInverse() const
    {
        return _orientationToZUpXFrontInverse;
    }

    //-------------------------------------------------------------------------
    inline int GenericSpecificConverterMappingV8::getBoneConfigurationsCount() const
    {
        return static_cast<int>(_boneConfigurations.size());
    }

    //-------------------------------------------------------------------------
    inline const BonePhysicConfigurationV1* GenericSpecificConverterMappingV8::getBoneConfigurations() const
    {
        if (!_boneConfigurations.empty())
        {
            return &_boneConfigurations[0];
        }
        return NULL;
    }

    //-------------------------------------------------------------------------
    inline BonePhysicConfigurationV1* GenericSpecificConverterMappingV8::touchBoneConfigurations()
    {
        if (!_boneConfigurations.empty())
        {
            return &_boneConfigurations[0];
        }
        return NULL;
    }

    //-------------------------------------------------------------------------
    inline int GenericSpecificConverterMappingV8::getChannelMirrorSourcesCount() const
    {
        return static_cast<int>(_channelMirrorSources.size());
    }

    //-------------------------------------------------------------------------
    inline const int* GenericSpecificConverterMappingV8::getChannelMirrorSources() const
    {
        if (!_channelMirrorSources.empty())
        {
            return &_channelMirrorSources[0];
        }
        return NULL;
    }

    //-------------------------------------------------------------------------
    inline int* GenericSpecificConverterMappingV8::touchChannelMirrorSources()
    {
        if (!_channelMirrorSources.empty())
        {
            return &_channelMirrorSources[0];
        }
        return NULL;
    }

    //-------------------------------------------------------------------------
    inline int GenericSpecificConverterMappingV8::getSpineMirrorSourcesCount() const
    {
        return static_cast<int>(_spineMirrorSources.size());
    }

    //-------------------------------------------------------------------------
    inline const int* GenericSpecificConverterMappingV8::getSpineMirrorSources() const
    {
        if (!_spineMirrorSources.empty())
        {
            return &_spineMirrorSources[0];
        }
        return NULL;
    }

    //-------------------------------------------------------------------------
    inline int* GenericSpecificConverterMappingV8::touchSpineMirrorSources()
    {
        if (!_spineMirrorSources.empty())
        {
            return &_spineMirrorSources[0];
        }
        return NULL;
    }

    //-------------------------------------------------------------------------
    inline float GenericSpecificConverterMappingV8::getQuality() const
    {
        return _quality;
    }

    //------------------------------------------------------------
    inline void GenericSpecificConverterMappingV8::setMaxIteration(const unsigned int& nbMaxIteration)
    {
        _maxIterations = nbMaxIteration;
    }

    //------------------------------------------------------------
    inline unsigned int GenericSpecificConverterMappingV8::getMaxIteration() const
    {
        return _maxIterations;
    }

    //------------------------------------------------------------
    inline const HierarchicalBoneV1* GenericSpecificConverterMappingV8::getPelvisBone() const
    {
        GLM_DEBUG_ASSERT(_skeletonDescription != NULL);
        return _skeletonDescription->getBones()[_pelvisBoneIndex];
    }

    //------------------------------------------------------------
    inline size_t GenericSpecificConverterMappingV8::getSpineBonesSize() const
    {
        return _spineBones.size();
    }

    //------------------------------------------------------------
    inline const glm::Array<GenericSpecificConverterBoneV1>& GenericSpecificConverterMappingV8::getSpineBones() const
    {
        return _spineBones;
    }

    //------------------------------------------------------------
    inline glm::Array<GenericSpecificConverterBoneV1>& GenericSpecificConverterMappingV8::touchSpineBones()
    {
        return _spineBones;
    }

    //------------------------------------------------------------
    inline size_t GenericSpecificConverterMappingV8::getLimbBonesSize() const
    {
        return _limbBones.size();
    }

    //------------------------------------------------------------
    inline const glm::Array<GenericSpecificConverterBoneV1>& GenericSpecificConverterMappingV8::getLimbBones() const
    {
        return _limbBones;
    }

    //------------------------------------------------------------
    inline glm::Array<GenericSpecificConverterBoneV1>& GenericSpecificConverterMappingV8::touchLimbBones()
    {
        return _limbBones;
    }

    //------------------------------------------------------------
    inline size_t GenericSpecificConverterMappingV8::getEffectorBonesSize() const
    {
        return _effectorBones.size();
    }

    //------------------------------------------------------------
    inline const glm::Array<GenericSpecificConverterBoneV1>& GenericSpecificConverterMappingV8::getEffectorBones() const
    {
        return _effectorBones;
    }

    //------------------------------------------------------------
    inline glm::Array<GenericSpecificConverterBoneV1>& GenericSpecificConverterMappingV8::touchEffectorBones()
    {
        return _effectorBones;
    }

    //------------------------------------------------------------
    inline const glm::Array<glm::GlmString>& GenericSpecificConverterMappingV8::getSpineBoneNames() const
    {
        return _spineBoneNames;
    }

    //------------------------------------------------------------
    inline const glm::Array<glm::GlmString>& GenericSpecificConverterMappingV8::getLimbBoneNames() const
    {
        return _limbBoneNames;
    }

    //------------------------------------------------------------
    inline const glm::Array<glm::GlmString>& GenericSpecificConverterMappingV8::getEffectorBoneNames() const
    {
        return _effectorBoneNames;
    }

    //------------------------------------------------------------
    inline void GenericSpecificConverterMappingV8::getEndOfChainBoneNames(glm::Array<glm::GlmString>& endOfChainBoneNames) const
    {
        endOfChainBoneNames.clear();
        endOfChainBoneNames.reserve(2 * (_limbBones.size() + _effectorBones.size()));
        for (size_t iLimb = 0; iLimb < _limbBones.size(); iLimb++)
        {
            if (_limbBones[iLimb]._lastBoneOfChain[0] != -1)
                endOfChainBoneNames.push_back(_skeletonDescription->getBones()[_limbBones[iLimb]._lastBoneOfChain[0]]->getName());
            if (_limbBones[iLimb]._lastBoneOfChain[1] != -1)
                endOfChainBoneNames.push_back(_skeletonDescription->getBones()[_limbBones[iLimb]._lastBoneOfChain[1]]->getName());
        }

        for (size_t iEffector = 0; iEffector < _effectorBones.size(); iEffector++)
        {
            if (_effectorBones[iEffector]._lastBoneOfChain[0] != -1)
                endOfChainBoneNames.push_back(_skeletonDescription->getBones()[_effectorBones[iEffector]._lastBoneOfChain[0]]->getName());
            if (_effectorBones[iEffector]._lastBoneOfChain[1] != -1)
                endOfChainBoneNames.push_back(_skeletonDescription->getBones()[_effectorBones[iEffector]._lastBoneOfChain[1]]->getName());
        }
    }

    //------------------------------------------------------------
    inline int GenericSpecificConverterMappingV8::getEffectorMirrorSourcesCount() const
    {
        return static_cast<int>(_effectorMirrorSources.size());
    }

    //------------------------------------------------------------
    inline const EffectorMirrorSourceV0* GenericSpecificConverterMappingV8::getEffectorMirrorSources() const
    {
        if (!_effectorMirrorSources.empty())
        {
            return &_effectorMirrorSources[0];
        }
        return NULL;
    }

    //------------------------------------------------------------
    inline EffectorMirrorSourceV0* GenericSpecificConverterMappingV8::touchEffectorMirrorSources()
    {
        if (!_effectorMirrorSources.empty())
        {
            return &_effectorMirrorSources[0];
        }
        return NULL;
    }

    //------------------------------------------------------------
    inline const glm::Array<BlindDataV1>& GenericSpecificConverterMappingV8::getBlindDataInfo() const
    {
        return _blindDataInfo;
    }

    //------------------------------------------------------------
    inline glm::Array<BlindDataV1>& GenericSpecificConverterMappingV8::touchBlindDataInfo()
    {
        _updateBlindDataFloatCount = true;
        return _blindDataInfo;
    }

    //------------------------------------------------------------
    inline const float* GenericSpecificConverterMappingV8::getSquashStretchWeightsArray() const
    {
        if (!_squashStretchWeightsArray.empty())
        {
            return &_squashStretchWeightsArray[0];
        }
        return NULL;
    }

    //------------------------------------------------------------
    inline float* GenericSpecificConverterMappingV8::touchSquashStretchWeightsArray()
    {
        if (!_squashStretchWeightsArray.empty())
        {
            return &_squashStretchWeightsArray[0];
        }
        return NULL;
    }

    //------------------------------------------------------------
    inline int GenericSpecificConverterMappingV8::getBlindDataTotalFloatCount() const
    {
        return _blindDataFloatCount;
    }

} // namespace glm
