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

#pragma once

#include "glmSDKAPI.h"
#include "glmMotionDefinitions.h"
#include "glmGenericSpecificConverterBone.h"
#include "glmBonePhysicConfiguration.h"
#include "glmGenericSpecificConverterMapping.h"
#include "glmGenericSpecificConverterIKCache.h"
#include "glmGenericSkeleton.h"
#include <glmSpecificPosture.h>
#include <glmQuaternion.h>
#include <glmGolaemCharacter.h>
#include <glmBlindData.h>

#include <glmGolaemCharacterDeclarations.h> // GolaemCharacter declarations

namespace glm
{
    class GenericSpecificConverterIKCache;

    class AbstractSpecificAnimation;
    class BlendTreePosture;

    class SkeletonRenderingCache;

    //************************************************************
    //! @name Structures and types
    //************************************************************
    //@{
    /**
	* \brief Represent the generalization of a skeleton (the conversion from the hierarchical skeleton to the generic skeleton)
	*
	* \author dev@golaem.com
	*
	*/
    class GLM_SDK_API GenericSpecificConverter : public ReferenceCounter
    {
    public:
        struct SpecificPostureBlindData
        {
            enum Value
            {
                NONE = 0,      //no blind data for the specific posture
                UNMAPPED_ONLY, //only blind data for the bones that aren't mapped and have to mapped children
                ALL,           //blind data for all bones
                END
            };
        };

        GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

        friend class GenericSpecificConverterIKCache;

        typedef glm::SmartPointer<GenericSpecificConverter> SP;

        GenericSpecificConverter(GenericSpecificConverterMapping* converterMapping = NULL, SpecificPostureBlindData::Value addSpecificAnimationBlindData = SpecificPostureBlindData::NONE);
        virtual ~GenericSpecificConverter();

        void setConverterMapping(GenericSpecificConverterMapping* converterMapping);

        //************************************************************
        //! @name Converting
        //************************************************************
        //@{
        /**
		* \brief Convert a specific animation into a GenericAnimation
		* \param source
		* \param destination
		* \param preferIkOverRollCoeficient if set to 0, only the roll orientation of the first bone of bone chains will be taken into account to fll the generic bone iK plane. If set to 1, only the real chain flexion plane will be taken into account. Any value inbetween is an interpolation between the 2 ik planes values
		* \note The destination will create its own copy of the equivalent generic skeleton, and can be used even after the GenericSpecificConverter is destructed
		*/
    protected:
        void convertAnimation(const AbstractSpecificAnimation& source, GenericAnimation& destination, float preferIkOverRollCoeficientOnSpines, float preferIkOverRollCoeficientOnLimbs, float preferIkOverRollCoeficientOnEffectors, const Array<float>& rollOffsets) const;
        void fillSpecificAnimationToBlindData(const AbstractSpecificAnimation& source, GenericAnimation& destination) const;

    public:
        void convertAnimationWithBlindData(const AbstractSpecificAnimation& source, GenericAnimation& destination, float preferIkOverRollCoeficientOnSpines, float preferIkOverRollCoeficientOnLimbs, float preferIkOverRollCoeficientOnEffectors, const Array<float>& rollOffsets, const glm::Array<BlindData>& blindDataInfo, const glm::Array<glm::Array<float>>& blindData) const;

        /**
		* \brief converts a generic posture to a specific one using IK
		*/
        void convertGenericToSpecific(const GenericPosture& source, SpecificPosture& destination, const HierarchicalSkeletonDescription::Override* skeletonOverride=NULL) const;
        void convertGenericToSpecific(const GenericPosture& source, BlendTreePosture& destination, float entityRootHeight, const HierarchicalSkeletonDescription::Override* skeletonOverride) const;
        int convertGenericChainLengthToSpecificChainLength(const char* endOfChainSpecificBoneName, int genericChainLength) const;

        /**
		* \brief converts a specific posture to a generic one
		* \param preferIkOverRollCoeficient if set to 0, only the roll orientation of the first bone of bone chains will be taken into account to fll the generic bone iK plane. If set to 1, only the real chain flexion plane will be taken into account. Any value inbetween is an interpolation between the 2 ik planes values
		*/
        void convertSpecificToGeneric(const SpecificPosture& source, GenericPosture& destination, float preferIkOverRollCoeficientOnSpines, float preferIkOverRollCoeficientOnLimbs, float preferIkOverRollCoeficientOnEffectors, const Array<float>& rollOffsets) const;
        void convertSpecificToGeneric(const BlendTreePosture& source, GenericPosture& destination) const;

        /**
		* \brief Check if a generic posture and a specific posture have the same positions for equivalent bones
		*/
        bool isEquivalentPostures(const GenericPosture& genericPosture, const SpecificPosture& specificPosture, float tolerance = GLM_NUMERICAL_PRECISION_COARSE) const;

        /**
		* \brief Check if a specific posture and another have the same positions for equivalent bones
		*/
        bool isEquivalentPostures(const SpecificPosture& specificPosture1, const SpecificPosture& specificPosture2, float tolerance = GLM_NUMERICAL_PRECISION_COARSE) const;

        /**
		* \brief Check if a generic skeleton has the same bone positions than the converter skeleton
		*/
        bool isEquivalentSkeleton(const GenericSkeleton& genericSkeleton, float tolerance = GLM_NUMERICAL_PRECISION_COARSE) const;
        //@}

        //************************************************************
        //! @name Accessors
        //************************************************************
        //@{
        /**
		* \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 returns a normalized ground height delta for one limb
		 */
        inline float getScaledGroundHeight(int limbIndex, float scale) const;

        inline const HierarchicalSkeletonDescription* getSkeletonDescription() const;

        inline const glm::Array<Quaternion>& getLocalOffsetsInverse() const;
        inline const glm::Array<Quaternion>& getLocalOffsets() const;

        inline int getBoneConfigurationsCount() const;
        inline const BonePhysicConfiguration* getBoneConfigurations() const;

        inline SpecificPostureBlindData::Value getHasSpecificAnimationBlindData() const { return _hasSpecificAnimationBlindData; }
        inline glm::Array<int> getBoneIndicesForSpecificAnimationBlindData() const { return _boneIndicesForSpecificAnimationBlindData; }
        //@}

        //************************************************************
        //! @name Configuration accessors
        //************************************************************
        //@{

        //------------------------------------------------------------
        /** 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 from bone chains, compute minimal and maximal bone ratios for generic bones
		 */
        static void computeMinimalAndMaximalGenericBoneLengths(const GenericSpecificConverterMapping& mapping, GenericSkeleton* genericSkeleton);

        inline const GenericSkeleton& getGenericSkeleton() const;

        // setter should now be useless
        inline void setSkeletonRenderingCache(SkeletonRenderingCache* renderingCache);
        inline const SkeletonRenderingCache* getSkeletonRenderingCache() const;
        inline SkeletonRenderingCache* touchSkeletonRenderingCache();

        SpecificPostureBlindData::Value getSpecificAnimationBlindDataMode() const { return _hasSpecificAnimationBlindData; }

        void getEffectorBoneName(glm::GlmString& name, size_t channelIdx, size_t effectorInChannelIdx) const;
        void setEffectorBoneName(size_t channelIdx, size_t effectorInChannelIdx, const glm::GlmString& name);

        static const int _ikCacheConfigCountDefault = 51; //51 means sample one every 0.02, it's a good default value

        /**
		* \brief build converter cache data. must be called before making any conversion
		*/
        void buildCacheData(SpecificPostureBlindData::Value addSpecificAnimationBlindData, const int ikCacheConfigCount = _ikCacheConfigCountDefault);
        void clearCacheData();
        bool isCacheDataEmpty() const;

        int getPreInitIkCacheConfigurationCount() const;

    protected:
        int getIkCacheConfigurationCount() const;
        void setIkCacheConfigurationCount(int value);

        /**
		* \brief build cache data for an array of converterbone
		*/
        static void buildCacheData(
            const PODArray<HierarchicalBone*>& hierarchicalSkeletonBones,
            const glm::PODArray<int>& ikExcludedBones,
            const Vector3& upAxis,
            const bool isSpine,
            glm::Array<GenericSpecificConverterBone>& converterBone,
            unsigned int& genericBoneIndex,
            glm::PODArray<int>& unassociatedSpecificBones);

        static void buildCacheOffsets(
            const Quaternion& orientationToZUpXFront,
            const glm::PODArray<HierarchicalBone*>& hierarchicalSkeletonBones,
            const glm::Array<GenericSpecificConverterBone>& converterBone,
            const GenericSkeleton& equivalentGenericSkeleton,
            glm::Array<Quaternion>& localOffset,
            glm::Array<Quaternion>& localOffsetInverse);

        static void buildCacheConversionBones(
            const glm::Array<GenericSpecificConverterBone>& spineBones,
            const glm::Array<GenericSpecificConverterBone>& limbBones,
            const glm::Array<GenericSpecificConverterBone>& effectorBones,
            const GenericSpecificConverterMapping& ikCacheAuthorizor,
            glm::PODArray<const GenericSpecificConverterBone*>& oneLinkBones,
            glm::PODArray<int>& oneLinkBonesAuxExtIdx,
            glm::PODArray<const GenericSpecificConverterBone*>& twoLinksConstLengthBones,
            glm::PODArray<int>& twoLinksConstLengthBonesAuxExtIdx,
            glm::PODArray<const GenericSpecificConverterBone*>& twoLinksNonConstLengthBones,
            glm::PODArray<int>& twoLinksNonConstLengthBonesAuxExtIdx,
            glm::PODArray<const GenericSpecificConverterBone*>& nLinksNonConstLength_FABRIKBones,
            glm::PODArray<int>& nLinksNonConstLength_FABRIKBonesAuxExtIdx,
            glm::PODArray<const GenericSpecificConverterBone*>& cachedIkBonesBones,
            glm::PODArray<int>& cachedIkBonesBonesAuxExtIdx);

        /**
		* \brief computes the bones not associated with a generic bone. So we have to compute their world matrix in a second pass
		*/
        struct UnassociatedBone;
        static void buildUnassociatedSpecificBonesList(
            const PODArray<HierarchicalBone*>& hierarchicalSkeletonBones,
            const glm::PODArray<int>& unassociatedSpecificBones,
            glm::PODArray<UnassociatedBone>& unassociatedBones);

        static void convertSpecificToGenericBones(
            const glm::Array<GenericSpecificConverterBone>& converterBones,
            const PODArray<HierarchicalBone*>& hierarchicalSkeletonBones,
            const glm::PODArray<UnassociatedBone>& unassociatedBones,
            const SpecificPosture& source,
            GenericPosture& destination,
            const GenericBone* destinationBones,
            const float preferIkOverRollCoeficient,
            const glm::Array<Quaternion>& converterLocalOffsetsInverse,
            const float* squashStretchWeightsArray,
            const Quaternion& orientationToZUpXFront,
			const Array<float>& rollOffsets);

        static void convertSpecificToGenericBone(
            const SpecificPosture& source,
            GenericPosture& destination,
            const glm::Array<Quaternion>& converterLocalOffsetInverse,
            const float* squashStretchWeightsArray,
            const Quaternion& orientationToZUpXFront,
            const GenericBone& genericBone,
            const int genericBoneIndex,
            const int genericBoneFatherIndex,
            const glm::Array<float>& bonesLengthUnscaled,
            const HierarchicalBone* firstSpecificBoneOfChain,
            const HierarchicalBone* lastSpecificBoneOfChain,
            const glm::PODArray<UnassociatedBone>& unassociatedBones,
            const float preferIkOverRollCoeficient,
			const float rollOffset);

        static void correctGenericAuxiliaryExtensionBone(
            GenericPosture& destination,
            const int auxiliaryGenericBoneIndex,
            const int extensionGenericBoneIndex,
            const Quaternion& extensionBoneLocalOrientation);

        static void convertGenericToSpecificBone_oneLink(
            const GenericPosture& source,
            SpecificPosture& destination,
            const PODArray<HierarchicalBone*>& boneArray,
            float* perBoneScales, const HierarchicalSkeletonDescription::Override* skeletonOverride,
            const glm::Array<Quaternion>& converterLocalOffsets,
            const int genericBoneIndex,
            const HierarchicalBone* firstBoneOfChain,
            const HierarchicalBone* lastBoneOfChain);

        static void convertGenericToSpecificBone_twoLinksConstLength(
            const GenericPosture& source,
            SpecificPosture& destination,
            float* perBoneScales,
            const glm::Array<Quaternion>& converterLocalOffset,
            const int genericBoneIndex,
            const glm::PODArray<unsigned int>& correspondingIndexInSpecificPosture,
            const glm::Array<float>& bonesLengthUnscaled);

        static void convertGenericToSpecificBone_twoLinksNonConstLength(
            const GenericPosture& source,
            SpecificPosture& destination,
            const PODArray<HierarchicalBone*>& boneArray,
            float* perBoneScales, const HierarchicalSkeletonDescription::Override* skeletonOverride,
            const Quaternion& orientationToZUpXFront, 
            const glm::Array<Quaternion>& converterLocalOffsets,
            const int genericBoneIndex,
            const glm::PODArray<unsigned int>& correspondingIndexInSpecificPosture,
            const HierarchicalBone* firstBoneOfChain,
            const HierarchicalBone* lastBoneOfChain,
            const glm::PODArray<int>& ikExcludedBones);

        static void convertGenericToSpecificBone_nLinksConstLength_FABRIK(
            const GenericPosture& source,
            SpecificPosture& destination,
            const PODArray<HierarchicalBone*>& boneArray,
            float* perBoneScales,
            const float ikSolverEndOfChainPositionTolerance,
            const int ikSolverIterationCountMax,
            const glm::Array<Quaternion>& converterLocalOffset,
            const int genericBoneIndex,
            const glm::PODArray<unsigned int>& correspondingIndexInSpecificPosture,
            const glm::Array<float>& bonesLengthUnscaled);

        static void convertGenericToSpecificBone_nLinksNonConstLength_FABRIK(
            const GenericPosture& source,
            SpecificPosture& destination,
            float* perBoneScales, const HierarchicalSkeletonDescription::Override* skeletonOverride,
            const float ikSolverEndOfChainPositionTolerance,
            const int ikSolverIterationCountMax,
            const glm::Array<Quaternion>& converterLocalOffset,
            const int genericBoneIndex,
            const glm::PODArray<unsigned int>& correspondingIndexInSpecificPosture,
            const HierarchicalBone* firstBoneOfChain,
            const HierarchicalBone* lastBoneOfChain,
            const glm::PODArray<int>& ikExcludedBones);

        /**
		* \brief Fill an array of bone scales for the specific posture bones
		* \return true if any bone scale is different than 1, false if all bone scales are 1
		*/
        static bool convertGenericToSpecificBoneScales(
            float* retBoneScales,
            const GenericPosture& source,
            const float* squashStretchWeightsArray,
            const int genericBoneIndex,
            const glm::PODArray<unsigned int>& correspondingIndexInSpecificPosture,
            const glm::Array<float>& bonesLengthUnscaled,
            int firstSBoneIndex,
            int lastSBoneIndex,
            const glm::Array<GlmString>& debugBoneNames,
            const int debugBoneNameIndex,
            const bool debugIsAuxiliaryBone);

        /**
		* \brief Do the roll extraction on the specific posture
		*/
        void rollExtraction(SpecificPosture& destination, const GenericSpecificConverterBone& genSpecBoneConverter) const;

        /**
		 * \brief
		 */
        static void computeMinimalAndMaximalGenericBoneLengths(const glm::Array<GenericSpecificConverterBone>& converterBone, GenericSkeleton* genericSkeleton);

    private:
        GLM_DISALLOW_COPY_AND_ASSIGN(GenericSpecificConverter);

    public:
        GenericSpecificConverterMapping* _converterMapping;
        float _rollExtractionBaseOffset;

    protected:
        //************************************************************
        //! @name Runtime data
        //************************************************************
        //@{

        GenericSpecificConverterIKCache _ikCache;

        struct UnassociatedBone
        {
        public:
            GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

            unsigned int _specificBoneIndex;
            int _specificParentBoneIndex;       //may be -1 if no parent bone (in case of modular rigs)
        };
        glm::PODArray<UnassociatedBone> _unassociatedBones;

        glm::Array<Quaternion> _localOffset;        // bone world ori = Z-aligned bone ori * offset
        glm::Array<Quaternion> _localOffsetInverse; // Z-aligned bone ori = bone world ori * offsetInverse

        //@}

        //************************************************************
        //! @name IK precomputed data
        //************************************************************
        //@{
        glm::PODArray<const GenericSpecificConverterBone*> _oneLinkBones;
        glm::PODArray<int> _oneLinkBonesAuxExtIdx;

        glm::PODArray<const GenericSpecificConverterBone*> _twoLinksConstLengthBones;
        glm::PODArray<int> _twoLinksConstLengthBonesAuxExtIdx;

        glm::PODArray<const GenericSpecificConverterBone*> _twoLinksNonConstLengthBones;
        glm::PODArray<int> _twoLinksNonConstLengthBonesAuxExtIdx;

        glm::PODArray<const GenericSpecificConverterBone*> _nLinksNonConstLength_FABRIKBones;
        glm::PODArray<int> _nLinksNonConstLength_FABRIKBonesAuxExtIdx;

        glm::PODArray<const GenericSpecificConverterBone*> _cachedIkBonesBones;
        glm::PODArray<int> _cachedIkBonesBonesAuxExtIdx;
        //@}

        SkeletonRenderingCache* _skeletonRenderingCache;
        GenericSkeleton _genericSkeleton; // the genericSkeleton shared by all instances of this mapping

        int _preInitIkCacheConfigurationCount;

        SpecificPostureBlindData::Value _hasSpecificAnimationBlindData;
        glm::Array<int> _boneIndicesForSpecificAnimationBlindData;
    };

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

    //-------------------------------------------------------------------------
    inline void GenericSpecificConverter::setSkeletonRenderingCache(SkeletonRenderingCache* renderingCache)
    {
        _skeletonRenderingCache = renderingCache;
    }

    //-------------------------------------------------------------------------
    inline const SkeletonRenderingCache* GenericSpecificConverter::getSkeletonRenderingCache() const
    {
        return _skeletonRenderingCache;
    }

    //-------------------------------------------------------------------------
    inline SkeletonRenderingCache* GenericSpecificConverter::touchSkeletonRenderingCache()
    {
        return _skeletonRenderingCache;
    }

    //-------------------------------------------------------------------------
    inline const Quaternion&
    GenericSpecificConverter::getOrientationToZUpXFront() const
    {
        return _converterMapping->getOrientationToZUpXFront();
    }

    //-------------------------------------------------------------------------
    inline const HierarchicalSkeletonDescription*
    GenericSpecificConverter::getSkeletonDescription() const
    {
        return _converterMapping->_skeletonDescription;
    }

    //-------------------------------------------------------------------------
    inline const glm::Array<Quaternion>& GenericSpecificConverter::getLocalOffsetsInverse() const
    {
        return _localOffsetInverse;
    }

    //-------------------------------------------------------------------------
    inline const glm::Array<Quaternion>& GenericSpecificConverter::getLocalOffsets() const
    {
        return _localOffset;
    }

    //-------------------------------------------------------------------------
    inline float GenericSpecificConverter::getScaledGroundHeight(int limbIndex, float scale) const
    {
        return scale * _converterMapping->getGroundHeight(limbIndex);
    }

    //-------------------------------------------------------------------------
    inline int GenericSpecificConverter::getBoneConfigurationsCount() const
    {
        return _converterMapping->getBoneConfigurationsCount();
    }

    //-------------------------------------------------------------------------
    inline const BonePhysicConfiguration* GenericSpecificConverter::getBoneConfigurations() const
    {
        return _converterMapping->getBoneConfigurations();
    }

    //-------------------------------------------------------------------------
    inline const GenericSkeleton& GenericSpecificConverter::getGenericSkeleton() const
    {
        return _genericSkeleton;
    }
} // namespace glm
