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

#pragma once

#include "glmMap.h"
#include "glmArray.h"
#include "glmPODArray.h"
#include "glmString.h"
#include "glmAssetManagementUtils.h"
#include <glmCrowdIOAPI.h>
#include <glmTypes.h>
//#include <glmFbx.h>
#include "glmSimulationData.h"
#include "glmFrameData.h"
#include "glmHistory.h"
//#include <glmSimulationCacheFactory.h>
#include "glmGeometryFile.h"
#include "glmFurCache.h"

#include <glmGolaemCharacterDeclarations.h> // GolaemCharacter declarations
#include <glmHierarchicalSkeletonDescription.h>

#define GLM_MESHASSET_OVERRIDE_BASENAME "glmMeshAssetOverride"
#define GLM_SHADERASSET_INTATTRIBUTE_OVERRIDE_BASENAME "glmShaderAssetIntAttribute"
#define GLM_SHADERASSET_FLOATATTRIBUTE_OVERRIDE_BASENAME "glmShaderAssetFloatAttribute"
#define GLM_SHADERASSET_VECTORATTRIBUTE_OVERRIDE_BASENAME "glmShaderAssetVectorAttribute"

namespace glm
{
    struct ShaderAssetDataContainer;

    template <class T>
    class Singleton;

    namespace crowdio
    {
        struct  GlmGeometryGenerationContext_0;
        typedef GlmGeometryGenerationContext_0 GlmGeometryGenerationContext;

        struct GlmEntityGeometry_0;
        typedef GlmEntityGeometry_0 GlmEntityGeometry;

        class CrowdFBXCharacter;
        class CrowdFBXBaker;
        class CrowdFBXStorage;

        class CrowdGcgCharacter;
        class CrowdGcgBaker;
        class CrowdGcgStorage;

        class SimulationCacheFactory;

        //----------------------------------------------------------------------------
        struct GLM_CROWDIO_API GeometryContextData
        {
            SimulationCacheFactory* _factory;   //contains the dirmap rules
            Array<CachedSimulation*> _factoryCachedSimulationPerCrowdFieldIndex; //the CachedSimulation pointers belong to the _factory and should not be deleted
            PODArray<uint16_t> _characterShdGrpIndexes;
            short _geometryTag;

            GlmMap<GlmString, size_t> _furCacheMap;
            Array<FurCache::SP> _furCacheArray;

            CrowdFBXStorage* _fbxStorage;
            CrowdFBXBaker* _fbxBaker;
            CrowdGcgStorage* _gcgStorage;
            CrowdGcgBaker* _gcgBaker;

            GeometryContextData();
            GeometryContextData(SimulationCacheFactory* factory);
            ~GeometryContextData();
			inline bool isOwnFactory() const { return _ownFactory; }

		private:
			bool _ownFactory;
        };

        //----------------------------------------------------------------------------
        struct GLM_CROWDIO_API GeometryContextDataContainer
        {
            GlmMap<GlmGeometryGenerationContext*, GeometryContextData*> _contextsData;

            GeometryContextData* getOrCreateContextData(GlmGeometryGenerationContext* geoContext);
            GeometryContextData* getContextData(GlmGeometryGenerationContext* geoContext) const;
            void removeContextData(GlmGeometryGenerationContext* geoContext);

            ~GeometryContextDataContainer();

            static GeometryContextDataContainer& getInstance();

        private:
            GeometryContextDataContainer();
            friend class Singleton<GeometryContextDataContainer>;
        };

        class GLM_CROWDIO_API ShaderNameReplacer
        {
        private:
            CachedSimulation* _cachedSimulation;
            int64_t _entityId;
            glm::PODArray<uint16_t> _shaderReplacementIds;

        public:
            ShaderNameReplacer(CachedSimulation* cachedSimulation, int64_t entityId);

            void replaceShaderName(const glm::GlmString& inputName, glm::GlmString& outputName) const;
        };

        //************************************************************
        /*! @name Compute Functions
        */
        //*********************************************************
        //@{
		/**
		* \brief computes the mesh names of a character without aliases
		*/
		bool GLM_CROWDIO_API computeMeshNames(
			const GolaemCharacter* character,
			int64_t entityId,
			const glm::PODArray<int>& entityAssets,
			glm::Array<glm::GlmString>& meshAssetNames,
			glm::PODArray<int>& furAssetIds,
			glm::PODArray<size_t>& meshAssetNameIndices,
			glm::PODArray<int>& meshAssetMaterialIndices,
			glm::PODArray<int>* entityMeshAssetindices = NULL);

        /**
		* \brief computes the mesh names of a character
		*/
        bool GLM_CROWDIO_API computeMeshNames(
            const GolaemCharacter* character,
            int64_t entityId,
            const glm::PODArray<int>& entityAssets,
            const glm::PODArray<int>& entityDeepAssets,
            glm::Array<glm::GlmString>& meshAssetNames,
			glm::Array<glm::GlmString>& meshAssetAliases,
            glm::PODArray<int>& furAssetIds,
            glm::PODArray<size_t>& meshAssetNameIndices,
            glm::PODArray<int>& meshAssetMaterialIndices,
            glm::PODArray<int>* entityMeshAssetindices = NULL,
            glm::Array<GlmString>* meshDeepConfFile = NULL,
            bool selectDeepMeshIfAvailable = false);

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

        //------------------------------------------------------------
        //! Return the file path of an exported file
        //------------------------------------------------------------
        void GLM_CROWDIO_API getExportedFilePath(glm::GlmString& outputPath, const char* directory, const char* cacheName, const char* crowdField, int frame, const char* extension);
        void GLM_CROWDIO_API getExportedFilePath(glm::GlmString& outputPath, const char* directory, const char* cacheName, const char* crowdField, const char* extension);
        void GLM_CROWDIO_API getExportedFilePath(glm::GlmString& outputPath, const char* directory, const char* cacheName, const char* extension);

        //------------------------------------------------------------
        //! Return the file path prefix of an exported file (same as getExportedFilePath but without the frame and the extension)
        //! note: ends with a "."
        //------------------------------------------------------------
        glm::GlmString GLM_CROWDIO_API getExportedFilePrefix(const glm::GlmString& directory, const glm::GlmString& cacheName, const glm::GlmString& crowdField);

        //------------------------------------------------------------
        //! Return the output directory corresponding to a crowdField index
        //------------------------------------------------------------
        glm::GlmString GLM_CROWDIO_API getOutputDir(const glm::Array<glm::GlmString>& outputDirs, size_t crowdFieldIdx);

        //------------------------------------------------------------
        //! Return the name of a Character
        //------------------------------------------------------------
        glm::GlmString GLM_CROWDIO_API getCharacterName(const GolaemCharacter* character);
        //@}

        //************************************************************
        /*! @name Function for handling node/shader name with special characters
        */
        //*********************************************************
        //@{
        //------------------------------------------------------------
        //! Return false if a character in considered invalid in a vrscene element name
        /*	\note This function has been provided by Chaos Group
        */
        //---------------------------------------------------------
        extern bool GLM_CROWDIO_API isCharInvalid(char c);

        //------------------------------------------------------------
        //! Convert a string in a valid vrscene element name
        /*	\note This function has been provided by Chaos Group
        */
        //---------------------------------------------------------
        extern void GLM_CROWDIO_API convertToValidName(const glm::GlmString& strIn, glm::GlmString& strOut);

        //************************************************************
        /*! @name List parser
        */
        //*********************************************************
        //@{
        //------------------------------------------------------------
        //! Return a list of crowdfields from a string
        /*	\param inputString separator is ;
        * \return list of crowdfields
        */
        //------------------------------------------------------------
        //extern void GLM_CROWDIO_API parseCrowdFieldsFromList(const glm::GlmString& inputString, glm::Array<glm::GlmString>& crowdFields);

        //------------------------------------------------------------
        //! Return a list of ids from a string
        /*	\param list list of ids : IDs can be listed as "1", "2-5", "6,11,13", or any combination of those
        * \return list of ids
        */
        //------------------------------------------------------------
        extern void GLM_CROWDIO_API parseIdsFromList(const glm::GlmString& inputString, glm::PODArray<int>& listIds);

        //------------------------------------------------------------
        //! Return a list of ids as a string
        /*	\param list list of ids
        * \return list of IDs listed as "1", "2-5", "6,11,13", or any ordered combination of those
        */
        //------------------------------------------------------------
        extern glm::GlmString GLM_CROWDIO_API getStringIdsFromList(const glm::PODArray<int>& list);

        //------------------------------------------------------------
        //! Return true if a Shader Attribute is to be applied to the rendered mesh
        /*	\param renderer name of the renderer for which the attribute should be parsed
		*	\param attributeStr attribute name to parse of scheme renderer.attrName.[subAttrName.]typeOverride ([subAttrName.] is for Renderman only)
		*	\param out attributeName
		*	\return true if the attribute is valid for parsing
		*/
        //------------------------------------------------------------
        struct GLM_CROWDIO_API RendererAttributeType
        {
            enum Value
            {
                BOOL,
                INT,
                UINT,
                BYTE,
                FLOAT,
                STRING,
                VECTOR,
                COLOR,
                END
            };
        };

        extern bool GLM_CROWDIO_API parseRendererAttribute(const glm::GlmString& renderer, const glm::GlmString& attributeStr, glm::GlmString& attributeName, glm::GlmString& subAttributeName, RendererAttributeType::Value& typeOverride);
        //@}

        extern GLM_CROWDIO_API CrowdGcgStorage& getDefaultGcgStorage();
        extern GLM_CROWDIO_API CrowdGcgBaker& getDefaultGcgBaker();
        extern GLM_CROWDIO_API void clearDefaultStorages();

        extern GLM_CROWDIO_API void transformOriginExtentsByProxy(float (&origin)[3], float (&extents)[3], float (&proxyTransform)[4][4], Vector3& bboxMin, Vector3& bboxMax);

        extern GLM_CROWDIO_API bool isAssetExcludedFromRendering(unsigned int shaderGroupIdx, GlmGeometryGenerationContext* geometryContext, const GlmEntityGeometry& entityRenderData);

        extern GLM_CROWDIO_API void computeAssetAssociations(
            glm::Array<glm::PODArray<int>>& assetAssociations,
            glm::Array<glm::PODArray<int>>& deepDeformerAssociations,
            glm::Array<glm::PODArray<int>>& skeletonOverrideAssocations,
            glm::Array<glm::PODArray<int>>& blindDataOverrideAssocations,
            glm::Array<glm::PODArray<int>>& physicsOverrideAssocations,
            uint32_t entityCount,
            const int64_t* entityIds,
            const int32_t* characterIndices,
            const int32_t* renderingTypeIndices,
            uint32_t assetGroupCount,
            uint32_t* assetResetIndex,
            const glm::PODArray<GolaemCharacter*>& characters,
            const glm::Array<AssetOverride>& assetOverrides,
            const GlmMap<GlmString, PODArray<int>>& weightAttributeByNames,
            uint32_t duplicatedEntitiesCount = 0,
            const int64_t* duplicatedEntitiesIDs = NULL,
            const int64_t* duplicatedEntitiesSrcIDs = NULL,
            bool copyDuplicateAssets = true);

        // c-style access for direct GlmFrameData usage
        extern GLM_CROWDIO_API void computeCharacterShaderData(
            ShaderAssetDataContainer& shaderDataContainer,
            const GlmSimulationData* simulationData,
            const glm::PODArray<GolaemCharacter*>& characters,
            unsigned int ppFloatAttrCount,
            const char (*ppFloatAttrNames)[GSC_PP_MAX_NAME_LENGTH],
            const float** ppFloatAttrValues,
            unsigned int ppVectorAttrCount,
            const char (*ppVectorAttrNames)[GSC_PP_MAX_NAME_LENGTH],
            const float (**ppVectorAttrValues)[3],
            const glm::Array<ShaderOverrides>& shaderOverrides);

        //------------------------------------------------------------
        //! Parse Channels Ids name ("et(0) et(2)" => [0, 2])
        //------------------------------------------------------------
        extern GLM_CROWDIO_API void parseChannelIds(const glm::GlmString& inputString, const glm::GlmString& channelName, glm::PODArray<unsigned int>& outChannels);

        extern GLM_CROWDIO_API void glmComputeBoneMatchingStatic(const GlmGeometryFile& geometry, const HierarchicalSkeletonDescription& skelDesc, glm::PODArray<uint16_t>& geometryBoneIndicesToSkeletonDescriptionBoneIndices);

        /**
         * \brief Computes the distance from the camera to the entity, taking into account the entity's position, character, scale, and geometry tag.
         * \param cameraPosition The position of the camera.
         * \param entityPosition The position of the entity.
         * \param entityCharacter The character data for the entity.
         * \param entityScale The scale factor applied to the entity.
         * \param geometryTag The geometry tag used for selection.
         * \return The computed distance as a float.
         */
        extern GLM_CROWDIO_API float computeDistanceToCamera(const Vector3& cameraPosition, const Vector3& entityPosition, const GolaemCharacter& entityCharacter, float entityScale, short geometryTag);

        //------------------------------------------------------------
        //! Fill array attributes in cache, using only AttributeNameX float scalar attributes (X being the index in the array)
        //------------------------------------------------------------
        
        //----------------------------------------------------------------------------
        template <typename DataType>
        void getCacheDynamicFloatAttributesAsArray(const GlmSimulationData& simulationData, const GlmFrameData& frameData, const char* attributeName, int entityIndexInSimData, const DataType& defaultFillValue, PODArray<DataType>& retAttributeArray, bool displayWarning = true);

        template <typename DataType>
        void setCacheDynamicFloatAttributesAsArray(const GlmSimulationData& simulationData, const GlmFrameData& frameData, const char* attributeName, int entityIndexInSimData, const DataType& defaultFillValue, const PODArray<DataType>& attributeArray, bool displayWarning = true);
        
        //----------------------------------------------------------------------------
        template <typename DataType>
        void getCacheStaticFloatAttributesAsArray(const GlmSimulationData& simulationData, const char* attributeName, int entityIndexInSimData, const DataType& defaultFillValue, PODArray<DataType>& retAttributeArray, PODArray<bool>& valueUseDefault, bool displayWarning = true, bool stripDefaultValues = false);

        template <typename DataType>
        void setCacheStaticFloatAttributesAsArray(const GlmSimulationData& simulationData, const char* attributeName, int entityIndexInSimData, const DataType& defaultFillValue, const PODArray<DataType>& attributeArray, bool displayWarning = true);

        //----------------------------------------------------------------------------
        extern GLM_CROWDIO_API void getCacheStaticVectorAttributesAsArray(const GlmSimulationData& simulationData, const char* attributeName, int entityIndexInSimData, const Vector3& defaultFillValue, Array<Vector3>& retAttributeArray, PODArray<bool>& valueUseDefault, bool displayWarning = true, bool stripDefaultValues = false);
        extern GLM_CROWDIO_API void setCacheStaticVectorAttributesAsArray(const GlmSimulationData& simulationData, const char* attributeName, int entityIndexInSimData, const Vector3& defaultFillValue, const Array<Vector3>& attributeArray, bool displayWarning = true);

        //------------------------------------------------------------
        //! Skeleton override from/to simulation data
        //------------------------------------------------------------
        extern GLM_CROWDIO_API void skeletonOverrideScalesFromCache(HierarchicalSkeletonDescription::OverrideScales& outSkeletonOverrideScales, const GlmSimulationData_v0* simulationData, int entityIndexInSimData);
        extern GLM_CROWDIO_API void skeletonOverrideScalesToCache(GlmSimulationData_v0& simulationData, int entityIndexInSimData, const HierarchicalSkeletonDescription::OverrideScales& skeletonOverrideScales);

        //------------------------------------------------------------
        //! Template definitions
        //------------------------------------------------------------

        //----------------------------------------------------------------------------
        template <typename DataType>
        void getCacheFloatAttributesAsArray(uint32_t ppFloatAttributeCount, char (*ppFloatAttributeNames)[GSC_PP_MAX_NAME_LENGTH], float** ppFloatAttributeData, const char* attributeName, int entityIndexInSimData, const DataType& defaultFillValue, PODArray<DataType>& retAttributeArray, PODArray<bool>& valueUseDefault, bool displayWarning, bool stripDefaultValues = false)
        {
            bool found(false);
            for (size_t iFloatAttribute = 0; iFloatAttribute < ppFloatAttributeCount; ++iFloatAttribute)
            {
                GlmString currentAttributeName(ppFloatAttributeNames[iFloatAttribute]);
                float* data = ppFloatAttributeData[iFloatAttribute];

                if (currentAttributeName.find(attributeName) == 0)    //should start with the attribute prefix
                {
                    found = true;
                    //get the index for the current attribute
                    unsigned int attributeIndex = atoi(currentAttributeName.substr(GlmString(attributeName).length()).c_str());
                    if (attributeIndex >= retAttributeArray.size())
                    {
                        retAttributeArray.resize(attributeIndex + 1, defaultFillValue);
                        valueUseDefault.resize(attributeIndex + 1, true);
                    }
                    retAttributeArray[attributeIndex] = (DataType)data[entityIndexInSimData];
                    valueUseDefault[attributeIndex] = false;
                }
            }
            if (stripDefaultValues && !retAttributeArray.empty())
            {
                // strip default Values
                typename PODArray<DataType>::iterator foundIte = glmFind(retAttributeArray.begin(), retAttributeArray.end(), defaultFillValue);
                while (foundIte != retAttributeArray.end())
                {
                    retAttributeArray.erase(foundIte);
                    valueUseDefault.erase(&(valueUseDefault.at(foundIte - retAttributeArray.begin())));
                    foundIte = glmFind(retAttributeArray.begin(), retAttributeArray.end(), defaultFillValue);
                }
            }

            if (displayWarning && !found)
            {
                GLM_SDK_TRACE_WARNING_LIMIT("Attribute '" << attributeName << "' was not found in the cache attributes, no data was retrieved.");
            }
        }

        //----------------------------------------------------------------------------
        template <typename DataType>
        void setCacheFloatAttributesAsArray(uint32_t ppFloatAttributeCount, char(*ppFloatAttributeNames)[GSC_PP_MAX_NAME_LENGTH], float** ppFloatAttributeData, const char* attributeName, int entityIndexInSimData, const DataType& defaultFillValue, const PODArray<DataType>& attributeArray, bool displayWarning)
        {
            PODArray<size_t> foundIndices;
            for (size_t iFloatAttribute = 0; iFloatAttribute < ppFloatAttributeCount; ++iFloatAttribute)
            {
                GlmString currentAttributeName(ppFloatAttributeNames[iFloatAttribute]);
                float* data = ppFloatAttributeData[iFloatAttribute];

                if (currentAttributeName.find(attributeName) == 0)    //should start with the attribute prefix
                {
                    //get the index for the current attribute
                    unsigned int attributeIndex = atoi(currentAttributeName.substr(GlmString(attributeName).length()).c_str());
                    if (attributeIndex < attributeArray.size())
                    {
                        data[entityIndexInSimData] = (float)attributeArray[attributeIndex];
                        foundIndices.push_back(attributeIndex);
                    }
                    else
                        data[entityIndexInSimData] = (float)defaultFillValue;
                }
            }
            if (displayWarning)
            {
                //check if all indices were found
                PODArray<size_t> notFoundIndices;
                for (size_t i = 0; i < attributeArray.size(); ++i)
                {
                    if (std::find(foundIndices.begin(), foundIndices.end(), i) == foundIndices.end())   //index not found
                        notFoundIndices.push_back(i);
                }
                if (!notFoundIndices.empty())
                {
                    auto new_end = std::unique(notFoundIndices.begin(), notFoundIndices.end()); // move duplicates to end
                    notFoundIndices.erase(new_end, notFoundIndices.end()); // actually removes duplicates

                    std::stringstream notFoundIndicesAsText;
                    notFoundIndicesAsText << notFoundIndices[0];
                    for (size_t i = 1; i < notFoundIndices.size(); ++i)
                        notFoundIndicesAsText << ", " << notFoundIndices[i];
                    GLM_SDK_TRACE_WARNING_LIMIT("Some indices for the attribute '" << attributeName << "' were not found in the cache attributes, they couldn't be filled with the desired value: " << notFoundIndicesAsText.str().c_str());
                }
            }
        }

        //----------------------------------------------------------------------------
        template <typename DataType>
        void getCacheDynamicFloatAttributesAsArray(const GlmSimulationData& simulationData, const GlmFrameData& frameData, const char* attributeName, int entityIndexInSimData, const DataType& defaultFillValue, PODArray<DataType>& retAttributeArray, PODArray<bool>& valueUseDefault, bool displayWarning)
        {
            getCacheFloatAttributesAsArray(simulationData._ppFloatAttributeCount, simulationData._ppFloatAttributeNames, frameData._ppFloatAttributeData, attributeName, entityIndexInSimData, defaultFillValue, retAttributeArray, valueUseDefault, displayWarning);
        }

        //----------------------------------------------------------------------------
        template <typename DataType>
        void setCacheDynamicFloatAttributesAsArray(const GlmSimulationData& simulationData, const GlmFrameData& frameData, const char* attributeName, int entityIndexInSimData, const DataType& defaultFillValue, const PODArray<DataType>& attributeArray, bool displayWarning)
        {
            setCacheFloatAttributesAsArray(simulationData._ppFloatAttributeCount, simulationData._ppFloatAttributeNames, frameData._ppFloatAttributeData, attributeName, entityIndexInSimData, defaultFillValue, attributeArray, displayWarning);
        }

        //----------------------------------------------------------------------------
        template <typename DataType>
        void getCacheStaticFloatAttributesAsArray(const GlmSimulationData& simulationData, const char* attributeName, int entityIndexInSimData, const DataType& defaultFillValue, PODArray<DataType>& retAttributeArray, PODArray<bool>& valueUseDefault, bool displayWarning, bool stripDefaultValues)
        {
            getCacheFloatAttributesAsArray(simulationData._staticPpFloatAttributeCount, simulationData._staticPpFloatAttributeNames, simulationData._staticPpFloatAttributeData, attributeName, entityIndexInSimData, defaultFillValue, retAttributeArray, valueUseDefault, displayWarning, stripDefaultValues);
        }

        //----------------------------------------------------------------------------
        template <typename DataType>
        void setCacheStaticFloatAttributesAsArray(const GlmSimulationData& simulationData, const char* attributeName, int entityIndexInSimData, const DataType& defaultFillValue, const PODArray<DataType>& attributeArray, bool displayWarning)
        {
            setCacheFloatAttributesAsArray(simulationData._staticPpFloatAttributeCount, simulationData._staticPpFloatAttributeNames, simulationData._staticPpFloatAttributeData, attributeName, entityIndexInSimData, defaultFillValue, attributeArray, displayWarning);
        }
    } // namespace crowdio
} // namespace glm
