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

#pragma once

#include "glmString.h"
#include "glmArray.h"
#include "glmPODArray.h"
#include "glmMap.h"
#include "glmSet.h"
#include "glmCrowdIOAPI.h"
#include "glmBlindData.h"
#include "glmFbx.h"

#include <glmGolaemCharacterDeclarations.h> // GolaemCharacter declarations

namespace glm
{
    class Log;

    namespace crowdio
    {
        struct GLM_CROWDIO_API FrameClothData
        {
            glm::Array<glm::Array<int>> _entitiesFbxMeshesIndices;                  //list of cloth meshes indices per entity
            glm::Array<glm::Array<glm::Array<FbxVector4>>> _entitiesMeshesVertices; //list of vertices per entity, and per cloth

            void clear();

            /**
	        * \brief Tells if the mesh index for a given entity index is a cloth or not
	        */
            bool isCloth(int entityIdx, int meshIdx) const;

            /**
	        * \brief Fill in the meshes vertices for a given cloth
	        */
            bool getClothVertices(glm::Array<FbxVector4>& deformedControlPoints, int entityIdx, int meshIdx) const;
        };

        // FBX character stored in a scene
        class GLM_CROWDIO_API CrowdFBXCharacter
        {
        public:
			GLM_CLASS_STAT_ID(glm::MemoryStatIds::Cache)

            CrowdFBXCharacter(FbxManager* fbxSdkManager);
            virtual ~CrowdFBXCharacter();

            const glm::GlmString& getName() const;
            const glm::GlmString& getFileName() const;
            void setName(const glm::GlmString& name, bool updateNames = true);

            const FbxManager* getFbxSdkManager() const;
            const FbxScene* getFBXScene() const;
            FbxScene*& touchFBXScene();

            /**
	        * \brief Returns the FBX meshes of the character
	        */
            const glm::PODArray<FbxNode*>& getCharacterFBXMeshes() const;

            FbxMesh* getCharacterFBXMesh(size_t meshIdx) const;

            /**
	        * \brief Returns the list of fbx bones (including the root bone) of the character sorted width first
	        */
            const glm::Array<glm::PODArray<FbxNode*>>& getSortedBones() const;

            FbxNode* getNodeParent(FbxNode* node) const;

            //void getAnimationCurves(FbxAnimCurve* animationCurves[6], FbxAnimLayer* animLayer, FbxNode* fbxNode);

            /**
	        * \brief Returns the geometry animation layer
	        */
            FbxAnimLayer* getGeometryAnimationLayer(bool createIfNeeded = true);

            /**
	        * \brief Returns the default animation layer
	        */
            FbxAnimLayer* getDefaultAnimationLayer(bool createIfNeeded = true);

            /**
	        * \brief Updates the meshes of the character
	        * \param meshNames List of meshes that make up the character. The meshes must be present in the FBX file.
	        */
            void updateMeshes(const glm::Array<glm::GlmString>& meshNames, bool tryMatch = true);

            /**
	        * \warning Should only be called after having updated the character meshes
	        */
            void updateBlendShapes(const glm::Array<BlindData>& blindData);

            void updateSnSValueIndices(const GolaemCharacter& glmCharacter);

            /**
	        * \brief Lists all the mesh nodes of the character (even the hidden ones)
	        */
            void findAllMeshNodes(glm::PODArray<FbxNode*>& meshNodes);

            /**
	        * \brief Returns the complete name of a node ([...]|Parent|name)
	        */
            static void getFullPathName(const FbxNode* node, glm::GlmString& completeName);

            /**
	        * \brief Returns the global transform of the given mesh node
	        */
            void getMeshGlobalTransform(FbxAMatrix& globalTransform, FbxNode* meshNode, const FbxTime& time = FBXSDK_TIME_INFINITE);

            inline const glm::PODArray<int>& getSnsValuesIndexToBoneIndex() const;

            inline const glm::Array<FbxLayerElementNormal*>& getInitialNormals() const;

            inline const glm::Array<glm::Array<FbxVector4>>& getInitialControlPoints() const;

            inline int getCurrentSortedBonesIndex() const;

            void computeVertexMaxWeights(size_t meshIdx, glm::Array<std::pair<int, double>>& vertexMaxWeights) const;

            int getParentBoneIndex(size_t meshIdx) const;

        protected:
            /**
	        * \brief Loads an FBX file and imports it into the FBX scene as a character
	        * \param filePath The file path of the FBX file containing the character
	        * \param skeletonRootName The name of the characters' skeleton root in the FBX file
	        * \param[in] triangulate If true, all the meshes are triangulated
	        * \param[in] generateTangents Generate tangent data
            * \return true if the import was successful, false otherwise
	        */
            bool loadCharacter(const glm::GlmString& filePath, const glm::GlmString& skeletonRootName, bool triangulate, bool generateTangents);

            /**
	        * \brief Loads an FBX file and imports it into the FBX scene as a character
	        * \param[in] filePath The file path of the FBX file containing the character
	        * \param[in] triangulate If true, all the meshes are triangulated
            * \param[in] generateTangents Generate tangents
	        * \return true if the import was successful, false otherwise
	        */
            bool loadFBX(const glm::GlmString& filePath, bool triangulate, bool generateTangents);

            /**
	        * \brief Triangulates all the meshes in the sene of the character
	        */
            void triangulateMeshes();

            /**
	        * \brief Updates the skeleton root if it is NULL
	        * \return true if the update was successful, false otherwise
	        */
            bool updateSkeletonRoot(const glm::GlmString& skeletonRootName);

            /**
	        * \brief Returns the skeleton root node found in a parent node
	        */
            static bool findSkeletonRootNodes(glm::Array<FbxNode*>& outputSkeletonRootNodes, const glm::GlmString& skeletonRootName, FbxNode* sceneNode);

            /**
	        * \brief Returns the list of fbx bones sorted width first
	        */
            static void computeSortedBones(FbxNode* rootNode, glm::PODArray<FbxNode*>& sortedBones);

            /**
            * \brief Retrieve the list of all childrent fbx bones
            */
            static void retrieveAllChildren(FbxNode* node, PODArray<FbxNode*>& children);

            /**
	        * \brief Applies a namespace on all the nodes of the character scene using the character name
	        */
            void updateNodeNames();

            /**
	        * \brief Returns the geometry motion animation stack name
	        */
            static const glm::GlmString& getGeometryMotionAnimationStackName();

            /**
	        * \brief Returns the motion animation stack name
	        */
            static const glm::GlmString& getMotionAnimationStackName();

            /**
            * \brief Returns the bind pose animation stack name
            */
            static const glm::GlmString& getBindPoseAnimationStackName();

            /**
            * \brief Match mesh names
            */
            void matchMeshNames(const glm::GlmSet<glm::GlmString>& meshNames);

            /// The character root node
            FbxNode* _rootNode;

            /// The FBXSDK manager
            FbxManager* _fbxSdkManager;

            /// The FBX scene
            FbxScene* _fbxScene;

            /// The FBX meshes of the character
            glm::PODArray<FbxNode*> _characterFBXMeshes;

            /// Character meshes by name
            glm::GlmMap<glm::GlmString, FbxNode*> _matchedFBXMeshes;

            /// Character meshes for which the cam name was not found
            glm::PODArray<FbxNode*> _unmatchedFBXMeshes;

            /// Stores the initial control points of the character meshes (useful for restoring deformed meshes)
            glm::Array<glm::Array<FbxVector4>> _initialControlPoints;

            /// Stores the initial normals of the character meshes (useful for restoring deformed meshes)
            glm::Array<FbxLayerElementNormal*> _initialNormals;

            /// The list of fbx bones of the character sorted width first
            glm::Array<glm::PODArray<FbxNode*>> _cacheToSpecificSortedBones;

            /// The character name
            glm::GlmString _name;
            glm::GlmString _filename;

            /// blend shape ids
            glm::GlmMap<FbxBlendShape*, glm::PODArray<int>> _blendShapeIds;

            // mapping from SnSValueIndex to boneIndex it applies on :
            glm::PODArray<int> _snsValuesIndexToBoneIndex; // used when reading SnS Values, each value matches bone[_snsValuesIndexToBoneIndex[i]]

            int _currentSortedBonesIndex = 0;

            friend class CrowdFBXBaker;
            friend class CrowdFBXStorage;
        };

        //-----------------------------------------------------------------------------
        inline const glm::Array<FbxLayerElementNormal*>& CrowdFBXCharacter::getInitialNormals() const
        {
            return _initialNormals;
        }

        //-----------------------------------------------------------------------------
        inline const glm::Array<glm::Array<FbxVector4>>& CrowdFBXCharacter::getInitialControlPoints() const
        {
            return _initialControlPoints;
        }

        //-----------------------------------------------------------------------------
        inline const glm::PODArray<int>& CrowdFBXCharacter::getSnsValuesIndexToBoneIndex() const
        {
            return _snsValuesIndexToBoneIndex;
        }

        //-----------------------------------------------------------------------------
        inline int CrowdFBXCharacter::getCurrentSortedBonesIndex() const
        {
            return _currentSortedBonesIndex;
        }
    } // namespace crowdio
} // namespace glm
