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

#pragma once

#include <glmCore.h>
#include <glmArray.h>
#include <glmString.h>
#include <glmPODArray.h>
#include <glmVector3.h>
#include <glmMatrix4.h>

#include "glmCrowdIOAPI.h"
#include "glmSimulationData.h"
#include "glmFrameData.h"
#include "glmGeometryFile.h"
#include "glmDualQuaternion.h"

#include "glmFbx.h"
#include "glmFurCache.h"

#include <glmGolaemCharacterDeclarations.h> // GolaemCharacter declarations

namespace at
{
    class Tensor;
}

namespace glm
{
    class Log;

    struct ShaderAssetDataContainer;

    namespace crowdio
    {
        class CrowdGcgCharacter;
        class AlembicScene;
        class PVData;

        //-----------------------------------------------------------------------------
        class BoneSkinningData
        {
        public:
			GLM_CLASS_STAT_ID(glm::MemoryStatIds::Cache)
                
            BoneSkinningData();

            Matrix4 _bindPoseToPoseMx;
            Matrix4 _worldToBoneMx;
            DualQuaternion _dualQuats;
            char _boneUsesSnS; // -1 or snsValueIndex

            // only stores data for bones using SnS (and coef != -1)
            Matrix4 _snsScaleInBoneOffsetMatrices; 
        };

        class BonesSkinningData
        {
        public:
			GLM_CLASS_STAT_ID(glm::MemoryStatIds::Cache)
            Array<BoneSkinningData> _bonesData;

            bool _characterUseSnS;

        private:
            uint32_t _boneCount;

        public:
            // constructor allocated for a given number of bones
            BonesSkinningData(unsigned int boneCount = 0);

            ~BonesSkinningData();

            // constructor allocated for a given number of bones
            void reuse(unsigned int boneCount);
        };

        //**************************************************************
        /*! \class CrowdGcgBaker
        *   \brief Manages applying postures on FBX characters, and exporting them as keyframed or per frame baked FBX files
        *   \author Golaem <dev@golaem.com>
        *   \bug
        *   \warning
        */
        //*******************************************************
        class GLM_CROWDIO_API CrowdGcgBaker
        {
        public:
            CrowdGcgBaker();
            ~CrowdGcgBaker();

            /**
            * \brief Processes a character at the given time
            * This function applies a pose to the FBX character and extracts the geometry of the character
            * into the FBX scene
            * \param entityIndex The index of the entity to process
            * \param character The FBX character to process
            * \param frameCache List of attributes containing the character pose information (\see CrowdGcgCharacter::importCachedAttributes)
            * \param processMode The way the character is processed
            * \param hasGeometryAnimation Tells if character has geometryAnimation
            * \param blendBehavior Tells if the geometryAnimation needs to be blended
            * \param currentTime The time at which the pose is computed
            */
            void processCharacter(
                unsigned int entityIdx
                , const GolaemCharacter& glmCharacter
                , const CrowdGcgCharacter& crowdCharacter
                , const GlmSimulationData& simulationData
                , const GlmFrameData& frameCache
                , const PODArray<int>& meshAssestIndices
                , const PODArray<uint32_t>& meshIndicesInAssetFile
                , Array<Array<Vector3>>& outDeformedVertices
                , Array<Array<Vector3>>& outDeformedNormals
                , Array<Array<Vector3>>* outDeformedTangents
                , Array<Array<Vector3>>& outDeformedFurVertices
                , bool applyCacheMirrorInfo
                , const PODArray<FurIds>& furIds
                , const Array<FurCache::SP>& furCache
                , const Array< Array<Vector3> >& preSkinVerticesOffset
                , BonesSkinningData* skinningData = NULL // filled with the boneMatrices, including sns
                , bool hasGeometryAnimation = false
                //, bool blendBehavior = false
                , const Time& currentTime = 0
                , float (*geometryFrameCacheData)[3] = NULL
);

            // write a character deformed meshes in an alembic scene
#if defined(GLM_EXPORT_ALEMBIC)
            void writeCharacterInAlembic(
                AlembicScene& alembicScene,
                const GlmString& alembicCharacterName,
                const PODArray<uint32_t>& meshIndicesInAsset,
                const CrowdGcgCharacter& currentGcgCharacter,
                const Array<Array<Vector3>>& inDeformedVertices,
                const Array<Array<Vector3>>& inDeformedNormals,
                const Array<Array<Vector3>>& inDeformedFurVertices,
                const PODArray<glm::crowdio::FurIds>& furIds,
                const Array<glm::crowdio::FurCache::SP>& furCache,
                const GolaemCharacter& sdkCharacter,
                int entityIdx,
                int characterIdx,
                const ShaderAssetDataContainer* shaderDataContainer,
                const PODArray<int>& meshAssets,
                const GlmMap<GlmString, PVData>& pvData,
                bool visible);
#endif

            // write a character skeleton in an alembic scene
#if defined(GLM_EXPORT_ALEMBIC)
            void writeSkeletonInAlembic(
                AlembicScene& alembicScene,
                unsigned int entityIdx,
                const GlmString& alembicCharacterName,
                const GolaemCharacter& glmCharacter,
                const GlmSimulationData& simulationData,
                const GlmFrameData& frameCache);
#endif

            // write a character deformed meshes in an fbx scene
            void writeFBXCharacterGeometry(
                FbxScene& fbxScene,
                const GlmString& fbxCharacterName,
                const PODArray<uint32_t>& meshIndicesInAsset,
                const CrowdGcgCharacter& currentGcgCharacter,
                const Array<Array<Vector3>>& inDeformedVertices,
                const Array<Array<Vector3>>& inDeformedNormals,
                const GolaemCharacter& sdkCharacter,
                const PODArray<int>& meshAssets,
                FbxTime sceneTime,
                FbxBakerVertexCacheData* pointCacheData,
                bool useNamespaces,
                bool visible);

            class FbxCharacterData
            {
            public:

                FbxCharacterData() : _skeletonHierarchyIndex(-1)
                {
                }

                void setBoneCount(size_t boneCount)
                {
                    _scaleCurves.clear();
                    _translateCurves.clear();
                    _rotateCurves.clear();

                    _boneCount = boneCount;

                    _scaleCurves.resize(_boneCount * 3, NULL);
                    _translateCurves.resize(_boneCount * 3, NULL);
                    _rotateCurves.resize(_boneCount * 3, NULL);

                    _fbxJoints.clear();
                    _fbxJointsMatrices.clear();

                    _fbxJoints.resize(boneCount, NULL);
                    _fbxJointsMatrices.resize(boneCount);
                }

                size_t _boneCount;

                // boneCount x3 (x y z) or empty (possible NULL entries)
                FbxAnimCurve* _visibilityCurves;
                Array<FbxAnimCurve*> _scaleCurves;     // currentBone->LclScaling.GetCurve(animLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
                Array<FbxAnimCurve*> _translateCurves; // currentBone->LclTranslation.GetCurve(animLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
                Array<FbxAnimCurve*> _rotateCurves;    // currentBone->LclRotation.GetCurve(animLayer, FBXSDK_CURVENODE_COMPONENT_X, true);

                Array<FbxMesh*> _meshes;
                Array<Array<Array<int>>> _blendShapeChannelsToBlindInfoIndex; // per mesh, per deformer, per channel (usually one deformer)

                Array<FbxNode*> _fbxJoints;
                Array<FbxAMatrix> _fbxJointsMatrices;

                int _skeletonHierarchyIndex;
            };

            void createFBXSkeleton(
                const GlmString& characterName,
                FbxAnimLayer* animLayer,
                FbxNode* fbxRootNode,
                const CrowdGcgCharacter& currentGcgCharacter,
                FbxCharacterData& fbxCharacterData,
                float boneSize,
                float characterScale,
                FbxPose*& fbxBindPose);

            // write a character and skeleton in an fbx scene, including blendshapes, vertexCache, skinning information. returns root of currentCharacter
            void writeFBXCharacterAndSkeleton(
                FbxScene& fbxScene,
                unsigned int entityIdx,
                const GlmString& fbxCharacterName,
                const PODArray<int>& meshMaterialIndices,
                const glm::PODArray<int>& meshIndicesInGcha,
                const PODArray<uint32_t>& meshIndicesInGeometry,
                const CrowdGcgCharacter& currentGcgCharacter,
                const GlmSimulationData& simulationData, // for character scale only
                const GolaemCharacter& sdkCharacter,
                short geometryTag,
                FbxCharacterData& fbxCharacterData,
                bool exportGeometry,
                bool visible);

            void updateFBXCharacter(
                const CrowdGcgCharacter& currentGcgCharacter,
                FbxScene& fbxScene,
                const GolaemCharacter& sdkCharacter,
                const GlmGeometryFile& geometry,
                short geometryTag,
                int entityCacheIndex,
                const GlmSimulationData& simulationData,
                const GlmFrameData& frameData,
                const FbxTime& sceneTime,
                float frameTime,
                FbxCharacterData& fbxCharacterData);

            bool computeDeformationCloth(const GlmGeometryFile& geometry, int meshIdx, const GlmFrameData& frameCache, int entityToBakeIndex, int indexOfMeshInClothData, Array<Vector3>& deformedControlPoints, Array<Vector3>& deformedPolygonVertexNormals, Array<Vector3>* deformedPolygonVertexTangents);

        protected:
            /**
            * \brief Applies the cluster (skin) or vertex cache deformation on the vertices and normals
            * of the given source mesh
            */
            void applyDeformation(
                const GlmGeometryFile& geometry
                , int meshAssetId // mesh id in character assets, to find clothes if any
                , int gcgMeshIndex
                , const GlmSimulationData& simuData
                , const GlmFrameData& frameCache
                , int entityIdx
                , const Matrix4& localToWorldMx
                , const Time& currentTime
                , const BonesSkinningData& skinningData
                , Array<Vector3>& deformedControlPoints
                , Array<Vector3>& deformedPolygonVertexNormals
                , Array<Vector3>* deformedPolygonVertexTangents
                , const PODArray<float>& blendShapeValues
                , float (*geometryFrameCacheData)[3]
                , bool applyCacheMirrorInfo
                , const glm::Array<Vector3>* preSkinVerticesOffset
                );
            void computeDeformationVertexCacheAndFillDeformed(const GlmGeometryFile& geometry, int meshIdx, const Time& currentTime, float (*geometryFrameCacheData)[3], Array<Vector3>& deformedControlPoints, Array<Vector3>& deformedPolygonVertexNormals, Array<Vector3>* deformedPolygonVertexTangents);
            void computeBlendShapesAndFillDeformed(const GlmGeometryFile& geometry, int meshIdx, const PODArray<float>& blendShapeValues, Array<Vector3>& deformedControlPoints, Array<Vector3>& deformedPolygonVertexNormals, Array<Vector3>* deformedPolygonVertexTangents);
            void computeDeformationSkinningFromLocalDeformed(
                const GlmGeometryFile& geometry, int meshIdx, const Matrix4& localToWorldMx, const BonesSkinningData& skinningData,
                Array<Vector3>& deformedControlPoints, Array<Vector3>& deformedPolygonVertexNormals, Array<Vector3>* deformedPolygonVertexTangents);

            void recomputeNormals(const GlmFileMesh& srcMesh, const Array<Vector3>& deformedControlPoints, Array<Vector3>& deformedPolygonVertexNormals, Array<Vector3>* deformedPolygonVertexTangents);

            void processFur(
                const GlmGeometryFile& geometryFile,
                float characterScale,
                const Array<Array<Vector3>>& deformedVertices,
                Array<Array<Vector3>>& outDeformedFurVertices,
                const PODArray<FurIds>& furIds,
                const Array<FurCache::SP>& furCache,
                const BonesSkinningData& skinningData);

            void blendChannelTarget(GlmFileMesh& srcMesh,
                                    glm::Array<Vector3>& deformedControlPoints,
                                    glm::Array<Vector3>& deformedNormals,
                                    //glm::Array<Vector3>* deformedTangents,
                                    GlmBlendShapeChannel& blendshapeChannel,
                                    int from, int to, float ratio) const;
        };
    } // namespace crowdio
} // namespace glm
