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

#pragma once

//golaem motion includes
#include "glmSDKAPI.h"
#include "glmMotionDefinitions.h"
#include "glmGenericBone.h"
#include "glmBlindData.h"
#include "glmHierarchicalSkeletonDescription.h"

#include <glmGolaemCharacterDeclarations.h> // GolaemCharacter declarations
#include <glmGolaemMotionDeclarations.h> // GolaemMotion declarations

namespace glm
{
    struct GenericSkeletonV0;
    struct GenericSkeletonV1;
    struct GenericSkeletonV2;
    struct GenericSkeletonV3;

    /**
	* \brief Represent a generic normalized skeleton
	*
	* \author dev@golaem.com
	*
	*/
    class GLM_SDK_API GenericSkeletonV4
    {
        GLM_RTTI("78997410-51c7-4f18-87b2-769f75082a1b", glm::GenericSkeletonV4, addAttributes)

    public:
        /**
		* \brief Overrides for some of the skeleton bone's properties
		*/
        struct Override
        {
			GLM_CLASS_STAT_ID(glm::MemoryStatIds::GolaemShared)

            Override()
                : _rootHeightUnscaled(1.f)
            {
            }

            /**
			* \brief Normalize the position given in parameter
			*/
            inline void normalizePosition(Vector3& position, float skeletonScale) const
            {
                position *= (1.f / (_rootHeightUnscaled * skeletonScale));
            }

            /**
			* \brief Denormalize the position given in parameter
			*/
            inline void denormalizePosition(Vector3& position, float skeletonScale) const
            {
                position *= _rootHeightUnscaled * skeletonScale;
            }

            float _rootHeightUnscaled;
            Array<Vector3> _boneLocalPositions;
            Array<Quaternion> _boneLocalOrientations;
            Array<float> _boneLengths;
        };

    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared)

        GenericSkeletonV4();
        GenericSkeletonV4(const GenericSkeletonV4& source);
        ~GenericSkeletonV4();

        GenericSkeletonV4& operator=(const GenericSkeletonV4& source);

        //************************************************************
        //! @name Initialization & construction
        //************************************************************
        //@{
        /**
		* \brief Create the bones of the GenericSkeletonV4 according to the hierarchical skeleton and its generalization class
		* \param skeleton
		* \note call to initMirrorSources is done during the init
		*/
        void init(const GenericSpecificConverterMapping& mapping, const HierarchicalSkeletonDescription::Override* skeletonOverride);

        /**
		* \brief Finish the GenericSkeletonV4 (remove all bones)
		*/
        void finish();
        //@}

        /**
		* \brief backwards compatibility
		*/
        void createFromV0(GenericSkeletonV0& genericSkeletonV0);
        void createFromV1(GenericSkeletonV1& genericSkeletonV1);
        void createFromV2(GenericSkeletonV2& genericSkeletonV2);
        void createFromV3(GenericSkeletonV3& genericSkeletonV3);

        //************************************************************
        //! @name Accessors
        //************************************************************
        //@{
        /**
		* \brief Get the number of blinddata
		*/
        inline size_t getBlindDataCount() const
        {
            return _blindDataCount;
        }
        inline size_t getBlindDataTotalFloatCount() const
        {
            return _blindDataTotalFloatCount;
        }
        inline const Array<BlindData>& getBlindDataInfos() const
        {
            return _blindDataInfos;
        }
        /**
		* \brief Get the number of spine bones
		*/
        inline size_t getSpineBonesSize() const
        {
            return _nbSpineBones;
        }
        /**
		* \brief Get the number of limb bones
		*/
        inline size_t getLimbBonesSize() const
        {
            return _nbLimbBones;
        }
        /**
		* \brief Get the number of effector bones
		*/
        inline size_t getEffectorBonesSize() const
        {
            return _nbEffectorBones;
        }
        /**
		* \brief Get the total count of bones
		*/
        inline size_t getTotalBonesCount() const
        {
            return static_cast<int>(_bones.size());
        }

        /** 
		* \brief Get the root bone of the skeleton
		* \return a const pointer to the bone
		*/
        inline const GenericBone* getRootBone() const
        {
            return _rootBone;
        }

        /** 
		* \brief Get the bones of the skeleton
		* \return a const pointer to the bone array
		*/
        inline const GenericBone* getBones() const
        {
            if (!_bones.empty())
                return &_bones[0];
            return NULL;
        }
        /** 
		* \brief Touch the bones of the skeleton
		* \return a pointer to the bone array
		*/
        inline GenericBone* touchBones()
        {
            if (!_bones.empty())
                return &_bones[0];
            return NULL;
        }
        //@}

        //************************************************************
        //! @name Bone world position/orientation accessors
        //************************************************************
        //@{
        /**
		* \brief get the world position of the bone
		* \return the world position of the bone (ie combined with the position and orientation of the bone hierarchy)
		*/
        //const Vector3& getBoneWorldPosition(int boneIndex, float skeletonScale) const {return _boneWorldPositions[boneIndex] * skeletonScale;}
        const Vector3& getBoneWorldPositionUnscaled(int boneIndex) const
        {
            return _boneWorldPositions[boneIndex];
        }

        /**
		* \brief get the position of the end of the bone in the world coordinate system
		* \return the world position of the end of the bone (ie combined with the position and orientation of the bone hierarchy)
		*/
        //const Vector3& getEndOfBoneWorldPosition(int boneIndex, float skeletonScale) const {return _EOBWorldPositions[boneIndex] * skeletonScale;}
        const Vector3& getEndOfBoneWorldPositionUnscaled(int boneIndex) const
        {
            return _EOBWorldPositions[boneIndex];
        }

        /**
		* \brief get the orientation of the bone in the world coordinate system
		* \return the world orientation of the bone (ie combined with the orientation of the bone hierarchy)
		*/
        const Quaternion& getBoneWorldOrientation(int boneIndex) const
        {
            return _boneWorldOrientations[boneIndex];
        }

        /**
		* \brief get the orientation of the end of the bone in the world coordinate system
		* \return the world orientation of the end of the bone (ie combined with the orientation of the bone hierarchy)
		*/
        const Quaternion& getEndOfBoneWorldOrientation(int boneIndex) const
        {
            return _EOBWorldOrientations[boneIndex];
        }

        /**
		* \brief get the minimal bone ratio 
		* \return minimal ratio
		*/
        inline float getBoneMinimalRatio(int boneIndex) const;
        inline void setBoneMinimalRatio(int boneIndex, float value);
        /**
		* \brief get the maximal bone ratio 
		* \return maximal ratio
		*/
        inline float getBoneMaximalRatio(int boneIndex) const;
        inline void setBoneMaximalRatio(int boneIndex, float value);
        //@}

        //************************************************************
        //! @name Limb locations arrays accessors This is the location of each limb. The location at index i is for the genericBone at index 1+getSpineBonesSize()+(i*2)
        //************************************************************
        //@{
        int getLocationsArraysSize() const
        {
            return static_cast<int>(_limbCoronalLocations.size());
        }
        const int* getLimbCoronalLocations() const
        {
            if (!_limbCoronalLocations.empty())
                return &_limbCoronalLocations[0];
            return NULL;
        }
        const int* getLimbTransverseLocations() const
        {
            if (!_limbTransverseLocations.empty())
                return &_limbTransverseLocations[0];
            return NULL;
        }
        const int* getLimbSagittalLocations() const
        {
            if (!_limbSagittalLocations.empty())
                return &_limbSagittalLocations[0];
            return NULL;
        }
        //@}

        /**
		* \brief Sources for channel mirror. Mirror source for channel i is getChannelMirrorSources()[i]
		*/
        inline const glm::PODArray<int>& getChannelMirrorSources() const
        {
            return _channelMirrorSources;
        }

        /** 
		* \brief Sources for channel mirror. Mirror source for bone i is getMirrorSources()[i]
		*/
        inline const int* getMirrorSources() const
        {
            if (!_mirrorSources.empty())
                return &_mirrorSources[0];
            return NULL;
        }

        //************************************************************
        //! @name Utils
        //************************************************************
        //@{
        /**
		* \brief get the ground height to the ankle delta by channel
		*/
        float getGroundToAnkleHeightDelta(int channelIndex, float skeletonScale) const;
        float getGroundToAnkleHeightDeltaUnscaled(int channelIndex) const;

        /**
		* \brief get the bone used to determine the lowest Z (nearest to ground)
		*/
        int getGroundLowestGenericBoneIndex(int channelIndex) const;

        /**
		* \brief get the ground height to the lowest bone delta by channel
		*/
        float getGroundToLowestBoneHeightDelta(int channelIndex, float skeletonScale) const;
        float getGroundToLowestBoneHeightDeltaUnscaled(int channelIndex) const;

        /**
		* \note http://en.wikipedia.org/wiki/Coronal_plane
		*/
        CoronalPlaneLocation::Value computeCoronalLocation(int boneIndex) const;

        /**
		* \note http://en.wikipedia.org/wiki/Transverse_plane
		*/
        TransversePlaneLocation::Value computeTransverseLocation(int boneIndex) const;

        /**
		* \note http://en.wikipedia.org/wiki/Sagittal_plane
		*/
        SagittalPlaneLocation::Value computeSagittalLocation(int boneIndex) const;

        /**
		* \brief Allocate and compute channel infos for the skeleton 
		* \param channelCount the number of allocated channel info will be returned in this pointer
		* \param channelsInfo the allcoated array pointer will be put in this 
		*/
        void computeChannelsInfos(int* channelCount, ChannelInfo** channelsInfo) const;
        void computeChannelsInfos(glm::Array<ChannelInfo>& channelsInfo) const;
        void computeChannelsInfosInternal(const int channelCount, ChannelInfo* channelsInfo) const;

        /**
		* \brief Compute the channel info for one channel
		*/
        ChannelInfo computeChannelInfo(int channelIndex) const;

        /**
		* \brief for any bone get the final bones in the hierarchy
		*/
        void recursiveFindEndBone(const GenericBone* bone, glm::PODArray<int>& endBoneIndices) const;
        //@}

        inline int getBonesSize() const
        {
            return static_cast<int>(_bones.size());
        }
        inline int getParentingSize() const
        {
            return static_cast<int>(_parenting.size());
        }
        inline int getLocationArraysSize() const
        {
            return static_cast<int>(_limbCoronalLocations.size());
        }
        inline int getMirrorSourcesCount() const
        {
            return static_cast<int>(_mirrorSources.size());
        }
        inline int getCacheArraysSize() const
        {
            return static_cast<int>(_boneWorldPositions.size());
        }
        inline int getBoneRatiosCount() const
        {
            return static_cast<int>(_minimalBoneRatios.size());
        }

        //************************************************************
        //! @name Initialization & construction
        //************************************************************
        //@{
        // caleld by CharaMaker, so public
        void initMirrorSources(const GenericSpecificConverterMapping& mapping);

        //FIXME: this code was in initMirrorSources(), but looked like an edge case that could only be encountered in character maker
        void reallocateMirrorSourcesForOldMotionFiles();
        //@}

        void buildDefaultOverride();
        const Override& getDefaultOverride() const
        {
            return _defaultOverride;
        }

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

        /**
		* \brief Build and compute the cache data for the skeleton
		*/
        void buildCacheData();

        /**
		* \brief Force children arrays computation in all bones (by default they are NULL)
		*/
        inline bool getComputeBonesChildren() const;
        void setComputeBonesChildren(bool);

    protected:
        //************************************************************
        // transient structures
        //************************************************************
        struct assoBones
        {
            GenericBone* father;
            GenericBone* bone;
        };

        //************************************************************
        //! @name
        //************************************************************
        //@{
        void initParenting(const size_t assoBoneCount, GenericSkeletonV4::assoBones* asso);
        void configureBone(int& boneIndex, GenericBone* father, int firstConverterBoneId, const GenericSpecificConverterBone& genSpecBone, const GenericSpecificConverterMapping& mapping, assoBones* asso, const HierarchicalSkeletonDescription::Override* skeletonOverride);
        GenericBone* findFatherInSpines(const GenericSpecificConverterBone& source, const GenericSpecificConverterMapping& mapping);
        GenericBone* findFatherInLimbs(const GenericSpecificConverterBone& source, const GenericSpecificConverterMapping& mapping);
        static int sortBoneFathers(const void* arg1, const void* arg2);
        //@}

    private:
        void allocateParenting(const size_t parentingSize);
        void deallocateParenting();

        void allocateLimbLocationArrays(const size_t limbLocationArraySize);
        void deallocateLimbLocationArrays();
        void copyLimbLocationArrays(const GenericSkeletonV4& source);

        void allocateMirrorSources(const size_t mirrorSourceCount);
        void deallocateMirrorSources();
        void copyMirrorSources(const GenericSkeletonV4& source);

        void allocateGroundHeightDeltaArrays(const size_t groundHeightDeltaArraySize);
        void deallocateGroundHeightDeltaArrays();
        void copyGroundHeightDeltaArrays(const GenericSkeletonV4& source);
        void initGroundHeightDeltaArrays(const GenericSpecificConverterMapping& mapping);

        void allocateBoneRatioArrays(const size_t boneRatioArraySize);
        void deallocateBoneRatioArrays();
        void copyBoneRatioArrays(const GenericSkeletonV4& source);

        int _blindDataCount; // info/type count
        int _blindDataTotalFloatCount; // total float count, to dimension float arrays
        glm::Array<BlindData> _blindDataInfos;

    protected:
        //************************************************************
        // Bones
        //************************************************************
        //@{
        GenericBoneV0* _rootBone;
        int _nbSpineBones; //int for serialization purposes... should be size_t
        int _nbLimbBones;
        int _nbEffectorBones;
        glm::Array<GenericBoneV0> _bones;
        glm::PODArray<GenericBoneV0*> _parenting;
        //@}

        //************************************************************
        // Additional data
        //************************************************************
        //@{
        glm::PODArray<int> _limbCoronalLocations;    //!< an array which size is _locationArraysSize. The value at index i is the coronal location of the generic bone 1+_nbSpineBones+(i*2)
        glm::PODArray<int> _limbTransverseLocations; //!< an array which size is _locationArraysSize. The value at index i is the coronal location of the generic bone 1+_nbSpineBones+(i*2)
        glm::PODArray<int> _limbSagittalLocations;   //!< an array which size is _locationArraysSize. The value at index i is the coronal location of the generic bone 1+_nbSpineBones+(i*2)

        glm::PODArray<int> _channelMirrorSources;
        glm::PODArray<int> _mirrorSources;

        int _groundHeightDeltaArraySize;
        float* _groundToAnkleHeightDelta;
        float* _groundToLowestBoneHeightDelta;
        int* _groundLowestGenericBoneIndex;
        //@}

        //************************************************************
        // Cached bones positions and orientations
        //************************************************************
        //@{
        glm::Array<Vector3> _boneWorldPositions;
        glm::Array<Vector3> _EOBWorldPositions;
        glm::Array<Quaternion> _boneWorldOrientations;
        glm::Array<Quaternion> _EOBWorldOrientations;

        glm::PODArray<float> _minimalBoneRatios;
        glm::PODArray<float> _maximalBoneRatios;
        //@}

        Override _defaultOverride;

    private:
        float _rootHeight_deprecated;
    };

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

    //------------------------------------------------------------
    // getComputeBonesChildren
    //------------------------------------------------------------
    inline bool GenericSkeletonV4::getComputeBonesChildren() const
    {
        return true;
    }

    //------------------------------------------------------------
    // setBoneMinimalRatio
    //------------------------------------------------------------
    inline float GenericSkeletonV4::getBoneMinimalRatio(int boneIndex) const
    {
        return _minimalBoneRatios[boneIndex];
    }

    //------------------------------------------------------------
    // setBoneMinimalRatio
    //------------------------------------------------------------
    inline void GenericSkeletonV4::setBoneMinimalRatio(int boneIndex, float value)
    {
        GLM_DEBUG_ASSERT(boneIndex < getBoneRatiosCount() && boneIndex >= 0);
        _minimalBoneRatios[boneIndex] = value;
    }

    //------------------------------------------------------------
    // getBoneMaximalRatio
    //------------------------------------------------------------
    inline float GenericSkeletonV4::getBoneMaximalRatio(int boneIndex) const
    {
        return _maximalBoneRatios[boneIndex];
    }

    //------------------------------------------------------------
    // setBoneMaximalRatio
    //------------------------------------------------------------
    inline void GenericSkeletonV4::setBoneMaximalRatio(int boneIndex, float value)
    {
        GLM_DEBUG_ASSERT(boneIndex < getBoneRatiosCount() && boneIndex >= 0);
        _maximalBoneRatios[boneIndex] = value;
    }

    //------------------------------------------------------------
    // getGroundToAnkleHeightDelta
    //------------------------------------------------------------
    inline float GenericSkeletonV4::getGroundToAnkleHeightDelta(int channelIndex, float skeletonScale) const
    {
        GLM_DEBUG_ASSERT(static_cast<size_t>(channelIndex) < (getLimbBonesSize() / 2));
        return _groundToAnkleHeightDelta[channelIndex] * skeletonScale;
    }

    //------------------------------------------------------------
    // getGroundToAnkleHeightDeltaUnscaled
    //------------------------------------------------------------
    inline float GenericSkeletonV4::getGroundToAnkleHeightDeltaUnscaled(int channelIndex) const
    {
        GLM_DEBUG_ASSERT(static_cast<size_t>(channelIndex) < (getLimbBonesSize() / 2));
        return _groundToAnkleHeightDelta[channelIndex];
    }

    //------------------------------------------------------------
    // getGroundToLowestBoneHeightDelta
    //------------------------------------------------------------
    inline float GenericSkeletonV4::getGroundToLowestBoneHeightDelta(int channelIndex, float skeletonScale) const
    {
        GLM_DEBUG_ASSERT(static_cast<size_t>(channelIndex) < (getLimbBonesSize() / 2));
        return _groundToLowestBoneHeightDelta[channelIndex] * skeletonScale;
    }

    //------------------------------------------------------------
    // getGroundToLowestBoneHeightDeltaUnscaled
    //------------------------------------------------------------
    inline float GenericSkeletonV4::getGroundToLowestBoneHeightDeltaUnscaled(int channelIndex) const
    {
        GLM_DEBUG_ASSERT(static_cast<size_t>(channelIndex) < (getLimbBonesSize() / 2));
        return _groundToLowestBoneHeightDelta[channelIndex];
    }

    //------------------------------------------------------------
    // getGroundLowestGenericBoneIndex
    //------------------------------------------------------------
    inline int GenericSkeletonV4::getGroundLowestGenericBoneIndex(int channelIndex) const
    {
        GLM_DEBUG_ASSERT(static_cast<size_t>(channelIndex) < (getLimbBonesSize() / 2));
        return _groundLowestGenericBoneIndex[channelIndex];
    }

} // namespace glm
