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

#pragma once

#include "glmCrowdIOAPI.h"
#include "glmSimulationData.h"
#include "glmFrameData.h"
#include "glmHistory.h"
#include <glmPODArray.h>
#include <glmArray.h>
#include <glmVector3.h>
#include <glmString.h>
#include <glmFbx.h>
#include "glmFurCache.h"

#include <glmGolaemCharacterDeclarations.h> // GolaemCharacter declarations

namespace glm
{
    class Log;

    class GlmString;
    struct ShaderAssetDataContainer;

    namespace crowdio
    {
        class CrowdFBXCharacter;
        class AlembicScene;

        // Manages applying postures on FBX characters, and exporting them as keyframed or per frame baked FBX files
        class GLM_CROWDIO_API CrowdFBXBaker
        {
        public:
            /**
            * \brief Character processing mode:
            * <em>BAKE_TO_GEOMETRY</em> - deforms the characters' meshes and copies the geometry to the bakers' scene
            * <em>DEFORM</em> - deforms the characters' meshes
            * <em>ANIMATION_CURVES</em> - creates animation curves for the characters' bones (no deformation)
            */
            struct ProcessMode
            {
                enum Value
                {
                    BAKE_TO_GEOMETRY,
                    DEFORM,
                    ANIMATION_CURVES,
                    BAKE_TO_GEOMETRY_WITH_CACHE,
                    END
                };
            };

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

            //************************************************************
            /*! @name Accessors
            */
            //*********************************************************
            //@{

            /**
            * \brief Returns the FBXSDK manager
            */
            const FbxManager* getFbxSdkManager() const;

            /**
            * \brief Returns the FBX scene (const)
            */
            const FbxScene* getFbxScene() const;

            /**
            * \brief Returns the FBX scene
            */
            FbxScene* touchFbxScene();

            void resetFbxScene();

            double getSceneFrameRate() const;
            void setSceneFrameRate(double frameRate);

            //@}

            /**
            * \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 CrowdFBXCharacter::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,
                CrowdFBXCharacter& character,
                float characterScale,
                const GlmSimulationData& simulationData,
                const GlmFrameData& frameCache,
                const ProcessMode::Value& processMode,
                const PODArray<int>& gchaMeshIds,
                Array<Array<Vector3>>& outDeformedVertices,
                Array<Array<Vector3>>& outDeformedNormals,
                Array<Array<Vector3>>* outDeformedTangents,
                Array<Array<Vector3>>& outDeformedFurVertices,
                FbxBakerVertexCacheData* pointCacheData,
                const PODArray<FurIds>& furIds,
                const Array<FurCache::SP>& furCache,
                bool hasGeometryAnimation,
                bool blendBehavior,
                const FbxTime& geometryTime,
                const FbxTime& sceneTime,
                bool applyCacheMirrorInfo,
                bool useNamespaces,
                bool visible);

            /**
            * \brief Processes a character to Alembic at the given time
            * This function applies a pose to the FBX character and extracts the geometry of the character
            * into the Alembic scene
            * \param entityIndex The index of the entity to process
            * \param character The FBX character to process
            * \param poseInformation List of attributes (sorted by name) containing the character pose information (\see CrowdFBXCharacter::importCachedAttributes)
            * \param materialInformation List of surface shader (sorted by mesh)
            * \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
            */
#if defined(GLM_EXPORT_ALEMBIC)
            /**
            * \brief Returns the Alembic scene
            */
            AlembicScene* touchAlembicScene();

            void processCharacterToAlembic(
                AlembicScene* alembicScene,
                int entityIdx,
                int characterIdx,
                CrowdFBXCharacter& fbxCharacter,
                float characterScale,
                const GlmSimulationData& simulationData,
                const GlmFrameData& frameCache,
                const GolaemCharacter& glmCharacter,
                const PODArray<int>& gchaMeshIds,
                const ShaderAssetDataContainer* shaderDataContainer,
                bool hasGeometryAnimation,
                bool blendBehavior,
                const PODArray<FurIds>& furIds,
                const Array<FurCache::SP>& furCache,
                const FbxTime& currentTime);
#endif

            /**
            * \brief Exports the given scene to a FBX file
            * \param scene The FBX scene to export
            * \param filePath The path of the file to save
            * \brief fbxSdkManager The FBX SDK manager
            * \param log Golaem Log in which to report status messages
            * \return true if the export was successful, false otherwise
            */
            static bool exportScene(FbxScene* scene, const GlmString& filePath, FbxManager* fbxSdkManager, bool exportAsAscii = false);

            /**
            * \brief Exports the given Alembic scene to a abc file
            * \param scene The Alembic scene to export
            * \param filePath The path of the file to save
            * \param frameRate The frame rate of the scene
            * \param startFrame The frame to start the export
            * \param log Golaem Log in which to report status messages
            * \return true if the export was successful, false otherwise
            */
#if defined(GLM_EXPORT_ALEMBIC)
            static bool exportAlembicScene(AlembicScene* scene, const GlmString& filePath, float frameRate, int startFrame, bool groupFurPerAsset);
#endif
            /**
            * \brief Returns all the meshes that are under the given root node
            */
            static void retrieveMeshes(FbxNode* rootNode, PODArray<FbxNode*>& meshes);

            /**
            * \brief Computes the global transform of a node in a scene
            */
            static void getGlobalTransform(FbxAMatrix& globalTransform, FbxNode* node, const FbxTime& time = FBXSDK_TIME_INFINITE);

            /**
            * \brief Computes the geometry transform local to a node
            */
            static void getGeomTransform(FbxAMatrix& geomTransform, FbxNode* node);

            /**
            * \brief Scales a character
            * \param scale Non uniform scale to apply on the character
            * resetToBindPose must be used on the character to reset the scale
            */
            static void applyScale(
                CrowdFBXCharacter& character,
                const FbxVector4& scale,
                bool createAnimationCurves = false,
                bool hasGeometryAnimation = false,
                bool blendGeom = false,
                const FbxTime& time = 0);

            /**
            * \brief Resets a character to its default bind pose
            */
            static void resetToBindPose(CrowdFBXCharacter& character, const FbxTime& time = 0);

            /**
            * \brief Clears all the animation curves of a character in its motion animation stack
            */
            static void clearAnimationCurves(CrowdFBXCharacter& character);

            /**
            * \brief Moves the character contents to another scene
            */
            static void moveToScene(CrowdFBXCharacter& character, FbxScene* scene, bool hasGeometryAnimation = false);

            /**
            * \brief Copies the source material to the destination material
            */
            static void copyMaterial(const FbxSurfaceMaterial* srcMat, FbxSurfaceMaterial*& dstMat, FbxScene* dstScene);

            // have to create in crowdIO even for crowd, to be compliant with its static fbxsdk lib classIDs
            FbxScene* createScene(const char* name); // not static, need the manager
            static void destroyScene(FbxScene* scene, bool recursive);
            static FbxSurfaceLambert* createLambert(FbxScene* dstScene, const char* name);
            static FbxSurfacePhong* createPhong(FbxScene* dstScene, const char* name);
            static FbxFileTexture* createFbxFileTexture(FbxScene* dstScene, const char* name, const char* fileName);
            static void setFbxPropertyTextureOrColor(FbxScene* dstScene, FbxPropertyT<FbxDouble3>& property, const char* textureFile, double (&color)[3]);
            static bool materialExists(FbxScene* fbxMaterialScene, const char* name);
            static void reimportMaterials(FbxScene* fbxMaterialScene, FbxScene& fbxScene);

        protected:
            /**
            * \brief Applies the cluster (skin) or vertex cache deformation on the vertices and normals
            * of the given source mesh
            */
            void applyDeformation(
                FbxNode* srcMeshNode,
                const GlmSimulationData& simulationData,
                const GlmFrameData& frameCache,
                const PODArray<FbxNode*>& sortedBones,
                int entityIdx,
                int meshAssetId,
                const FbxAMatrix& globalPosition,
                const FbxAMatrix& rootTransform,
                const FbxTime& currentTime,
                FbxAnimLayer* animLayer,
                Array<Vector3>& deformedControlPoints,
                Array<Vector3>& deformedPolygonVertexNormals,
                Array<Vector3>* deformedPolygonVertexTangents,
                Array<PODArray<int>>& normalGroups,
                const PODArray<int>& snsBoneIndices,
                bool applyCacheMirrorInfo);
            bool computeDeformationCloth(
                FbxNode* srcMeshNode,
                const GlmFrameData& frameCache,
                int entityToBakeIndex,
                int meshAssetId,
                Array<Vector3>& deformedControlPoints,
                Array<Vector3>& deformedPolygonVertexNormals,
                Array<Vector3>* deformedPolygonVertexTangents,
                Array<PODArray<int>>& normalGroups);
            void computeDeformationVertexCache(
                FbxNode* srcMeshNode,
                const FbxTime& currentTime,
                FbxAnimLayer* animLayer,
                Array<Vector3>& deformedControlPoints,
                Array<Vector3>& deformedPolygonVertexNormals,
                Array<Vector3>* deformedPolygonVertexTangents,
                Array<PODArray<int>>& normalGroups);
            void computeDeformationSkinning(
                FbxNode* srcMeshNode,
                const GlmSimulationData& simulationData,
                const GlmFrameData& frameCache,
                const PODArray<FbxNode*>& sortedBones,
                int entityIdx,
                const FbxAMatrix& globalPosition,
                const FbxAMatrix& rootTransform,
                const FbxTime& currentTime,
                FbxAnimLayer* animLayer,
                Array<Vector3>& deformedControlPoints,
                Array<Vector3>& deformedPolygonVertexNormals,
                Array<Vector3>* deformedPolygonVertexTangents,
                Array<PODArray<int>>& normalGroups,
                const PODArray<int>& snsBoneIndices);
            bool recomputeNormals(FbxNode* srcMeshNode,
                                  FbxMesh* srcMesh,
                                  Array<Vector3>& deformedControlPoints,
                                  Array<Vector3>& deformedPolygonVertexNormals,
                                  Array<Vector3>* deformedPolygonVertexTangents,
                                  Array<PODArray<int>>& normalGroups);

            /**
            * \brief Applies orientations/positions on a FBX skeleton
            */
            static void applyPosture(unsigned int entityIdx, FbxAnimLayer* animLayer, const PODArray<FbxNode*>& sortedBones, const GlmSimulationData& simulationData, const GlmFrameData& frameCache, bool createAnimationCurves, bool hasGeometryAnimation, bool blendGeom, const FbxTime& currentTime);

            /**
            * \brief Applies blend shapes on FBX meshes
            */
            static void applyBlendShapes(const CrowdFBXCharacter& character, unsigned int entityIdx, FbxAnimLayer* animLayer, const GlmSimulationData& simuData, const GlmFrameData& frameCache, const FbxTime& currentTime);

            /**
            * \brief Copy all curves from an animation layer into another
            */
            static void copyAnimationLayers(FbxNode* currentBone, FbxAnimLayer* fromAnimLayer, FbxAnimLayer* toAnimLayer, bool createCurveSource = false);

            void processFur(
                const CrowdFBXCharacter& fbxCharacter,
                float characterScale,
                const Array<Array<Vector3>>& deformedVertices,
                Array<Array<Vector3>>& outDeformedFurVertices,
                const PODArray<FurIds>& furIds,
                const Array<FurCache::SP>& furCache);

            /// The FBXSDK manager
            FbxManager* _fbxSdkManager;

            /// The FBX scene
            FbxScene* _fbxScene;
        };
    } // namespace crowdio
} // namespace glm
