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

#ifndef GLM_HISTORY_H
#define GLM_HISTORY_H

#include <glmCrowdIOAPI.h>
#include "glmSimulationCacheCommon.h"
#include "glmSimulationCacheTransforms.h"
#include "glmFrameData.h"
#include "glmSimulationData.h"
#include "glmString.h"
#include "glmMatrix4.h"
#include "stdint.h"
#include "stdio.h"

namespace glm
{
    namespace crowdio
    {
        //pre-declarations
		class SimulationCacheVisualFeedbackArray;

		namespace crowdTerrain
        {
            struct TerrainMesh;
        }

        //-------------------------------------------------------------------------
        // History nodal structure
        //-------------------------------------------------------------------------

        typedef enum
        {
            GTV_none,
            GTV_char,   // Golaem Typed Values: char
            GTV_int32,  // Golaem Typed Values: int32_t
            GTV_uint32, // Golaem Typed Values: uint32_t
            GTV_int64,  // Golaem Typed Values: int64_t
            GTV_float,  // Golaem Typed Values: float
            GTV_vec3,   // Golaem Typed Values: float[3]
            GTV_vec4    // Golaem Typed Values: float[4]
        } GlmTypedValues;

        typedef struct GlmCharValues_v1
        {
            uint32_t _count;
            char* _values; //size is _count
        } GlmCharValues_v1;
        typedef struct GlmInt32Values_v1
        {
            uint32_t _count;
            int32_t* _values; //size is _count
        } GlmInt32Values_v1;
        typedef struct GlmInt64Values_v1
        {
            uint32_t _count;
            int64_t* _values; //size is _count
        } GlmInt64Values_v1;
        typedef struct GlmFloatValues_v1
        {
            uint32_t _count;
            float* _values; //size is _count
        } GlmFloatValues_v1;
        typedef struct GlmVec3Values_v1
        {
            uint32_t _count;
            float (*_values)[3]; //size is _count
        } GlmVec3Values_v1;
        typedef struct GlmVec4Values_v1
        {
            uint32_t _count;
            float (*_values)[4]; //size is _count
        } GlmVec4Values_v1;
        typedef struct GlmAttribute_v1
        {
            GlmCharValues_v1 _name;

            uint32_t _frameCount;
            float* _frames;  //size is frameCount
            uint32_t _flags; // bunch of enum flags values, defined in layoutEditorUtils.py for python interface usage (search for GlmAttributeFlags())

            GlmTypedValues _type;
            GlmCharValues_v1* _char;      //size is max(frameCount,1) or 0 if _type is not char
            GlmInt32Values_v1* _int32val; //size is max(frameCount,1) or 0 if _type is not GTV_int32 or GTV_uint32. Can be either int32 or uint32 casted as int32
            GlmInt64Values_v1* _int64val; //size is max(frameCount,1) or 0 if _type is not GTV_int64
            GlmFloatValues_v1* _float;    //size is max(frameCount,1) or 0 if _type is not float
            GlmVec3Values_v1* _vec3;      //size is max(frameCount,1) or 0 if _type is not vec3
            GlmVec4Values_v1* _vec4;      //size is max(frameCount,1) or 0 if _type is not vec4
        } GlmAttribute_v1;
        typedef struct GlmLayoutOperatorNode_v1
        {
            SimulationCacheTransformType::Value _type;

            uint32_t _attributesCount;
            GlmAttribute_v1* _attributes;
        } GlmLayoutOperatorNode_v1;
        typedef struct GlmLayoutSelectorNode_v1
        {
            GlmCharValues_v1 _entitiesExpression; //selection expression: separated by ',', select range with '-', entity type with 'et(x), crowdfield with 'cf(x)', all with '*'
            uint8_t _filterInput;                 // selector is a "standalone" if anything else than 1 here, else its expression filters its input
            uint8_t _percent;                     // selector will keep only x percent of selection as real selection. Must be between 0 and 100
            uint32_t _randomSeed;                 // seed when randomizing percent kept
            uint8_t _selectFromMeshMode;          // select from a mesh mode 0 = do not select from mesh, 1 = 2d mode, 2 = 3d mode
            float _maxRaycastDistance;            // max raycast distance when selecting from a mesh (_selectFromMeshMode set to 1 or 2)
        } GlmLayoutSelectorNode_v1;
        typedef struct GlmLayoutNode_v1
        {
            uint32_t _ID;
            GlmCharValues_v1 _name;
            int8_t _active; // on/off

            GlmLayoutSelectorNode_v1* _selector; //null if the type is not a selector
            GlmLayoutOperatorNode_v1* _operator; //null if the type is not an operator

            int32_t _GUI_pos[2]; // position of the transform in the GUI
        } GlmLayoutNode_v1;
        typedef struct GlmLayoutGroup_v1
        {
            uint32_t _ID;
            GlmCharValues_v1 _name;
            int8_t _active;   // on/off
            int8_t _expanded; // collapsed/expanded

            uint32_t _layoutNodesCount;
            uint32_t* _layoutNodesIds;

            int32_t _GUI_pos[2]; // position of the transform in the GUI
        } GlmLayoutGroup_v1;
        typedef struct GlmHistory_v1
        {
            uint32_t _licenseHashKey;
            uint32_t _rootTransformId;
            //parameters
            uint32_t _parametersCount;
            GlmAttribute_v1* _parameters;
            //nodes
            uint32_t _layoutNodesCount;
            GlmLayoutNode_v1* _layoutNodes; //size = _layoutNodesCount
            //groups
            uint32_t _layoutNodesGroupsCount;
            GlmLayoutGroup_v1* _layoutNodesGroups; //size = _layoutNodesGroupsCount
            //connections
            uint32_t _connectionsCount;
            uint32_t* _connections; //array of size _connectionsCount*2, content is srcId0, dstId0, srcId1, dstId2, ...
        } GlmHistory_v1;
        typedef GlmHistory_v1 GlmHistory;

        //-------------------------------------------------------------------------
        // Transformation per entity for whole cache. *Not saved.* Only used for modification on cache
        //-------------------------------------------------------------------------

        // history options
        typedef enum
        {
            GroundAdaptNone = 0,
            GroundAdaptSnapHeight,
            GroundAdaptSnapHeightAndOri,
            GroundAdaptWithIK,
            GroundAdaptGroundOffset,
            GroundAdaptGroundOffsetAndOri
        } GlmGroundAdaptationOptions;

        //---------------------------------------------------------------------------
        class GLM_CROWDIO_API glmHistoryRuntimeStructure
        {
        public: //methods
            GLM_CLASS_STAT_ID(glm::MemoryStatIds::Cache);

        public:
            // data
            const GlmHistory* _layoutHistory = NULL; // layout history used to build the runtime structure
            uint32_t _layoutNodesCount = 0;
            GlmMap<uint32_t, uint32_t> _layoutNodeIndexById;
            PODArray<uint32_t> _layoutExecutionOrder;
            //per layout node data
            Array<PODArray<int64_t>> _entitiesAffectedPerLayoutNodeIn;  //entities connected to the input of a layout node
            Array<PODArray<int64_t>> _entitiesAffectedPerLayoutNodeOut; //entities outputted from a layout node (ie duplicates for nodes that duplicates entities, same as input otherwize)
            //duplication data
            PODArray<int64_t> _duplicatedEntitiesIDs;
            PODArray<int64_t> _duplicatedEntitiesSrcIDs;

            class SrcAndCreationIndex
            {
            public:
                SrcAndCreationIndex()
                    : _sourceId(-1)
                    , _creationExecutionOrderIndex(UINT32_MAX)
                {
                }

                SrcAndCreationIndex(int64_t srcId, size_t creationIndex)
                    : _sourceId(srcId)
                    , _creationExecutionOrderIndex(creationIndex)
                {
                }

                int64_t _sourceId;
                size_t _creationExecutionOrderIndex; // the entity is created at this node execution order index, used to know which noise must be applied (parent or specific)
            };

            GlmMap<int64_t, SrcAndCreationIndex> _duplicateToSrcAndCreationIndex;
            //simulation transform matrix
            Matrix4 _simulationTransformMatrix;  // world matrix of the proxy node holding the simulation
            Matrix4 _simulationTransformInverse; // inverse of the world matrix
            //terrain
            GlmGroundAdaptationOptions _groundAdaptationMode = GroundAdaptSnapHeight;
            Array<GlmGroundAdaptationOptions> _groundAdaptationModePerEntity; //is empty by default, otherwise the size is the modified sim data entity count
            PODArray<float> _groundAdaptation_raycastStartOffset;             //is empty by default, otherwise the size is the modified sim data entity count
            PODArray<float> _groundAdaptation_raycastUpSideLength;            //is empty by default, otherwise the size is the modified sim data entity count
            PODArray<float> _groundAdaptation_raycastDownSideLength;          //is empty by default, otherwise the size is the modified sim data entity count
            PODArray<float> _ikGroundAdaptation_attitudeCorrectionRatio;      //is empty by default, otherwise the size is the modified sim data entity count
            PODArray<float> _ikGroundAdaptation_bankCorrectionRatio;          //is empty by default, otherwise the size is the modified sim data entity count
            PODArray<float> _ikGroundAdaptation_normalFromLimbsRatio;         //is empty by default, otherwise the size is the modified sim data entity count
            PODArray<float> _ikGroundAdaptation_adaptEndOfLimbsOriRatio;      //is empty by default, otherwise the size is the modified sim data entity count

            crowdTerrain::TerrainMesh* _terrainMeshSource = NULL;
            crowdTerrain::TerrainMesh* _terrainMeshDestination = NULL;
            //new data
            PODArray<int64_t> _newEntitiesIDs; // will also contains all IDs contained in _duplicatedEntitiesIDs
            Array<std::pair<size_t, GlmMap<int64_t, float>>> _modifiedEntityScalesByExecutionOrderIndex;

            bool _refreshAssetDistributionEachFrame = false;
            glm::Time _lastRefreshAssetTime = std::numeric_limits<double>::lowest();

            struct NodeIdAndDupIndex
            {
                uint32_t _layoutSrcNodeId;
                uint32_t _duplicateNodeIndex;
            };

            PODArray<uint32_t> _firstNodeIndexInDuplicates;
            GlmMap<int64_t, NodeIdAndDupIndex> _mapDuplicateIdToExpression; // we can make an expression o(_layoutSrcNodeId, _duplicateNodeIndex) with that

            glm::GlmSet<int64_t> _entitiesAffectedByPermanentKill; // entities that are affected by a permanent kill in layout

        public: // methods
            glmHistoryRuntimeStructure(const GlmSimulationData* simulationData, const GlmHistory* history, PODArray<uint32_t>& duplicateCountPerNode, bool firstToCreateEntities);
            ~glmHistoryRuntimeStructure();

            // int64_t getSourceId(int64_t duplicateEntityId) const; // ready but unused until now
            int64_t getTopLevelSourceId(int64_t duplicateEntityId) const;

            uint32_t findPreviousNodeIDInExecutionOrder(uint32_t nodeId, bool skipSelectorsAndUnusedNodes, bool returnFirstExecutionNodeIfNoneFound) const;

            void computeDstEntitiesSrcIndicesInSrcSimu(PODArray<uint32_t>& dstEntitiesSrcIndicesInSrcSimu, const GlmSimulationData* simulationDataSource) const;

            const GlmMap<int64_t, float>& getEntityScales(size_t executionOrderIndex) const;
        };

        //-------------------------------------------------------------------------
        // History creation and deletion
        //-------------------------------------------------------------------------

        // create a transform layer
        extern GLM_CROWDIO_API void createHistory(GlmHistory** history, uint32_t layoutNodesCount, uint32_t connectionsCount, uint32_t layoutNodesGroupsCount, uint32_t rootTransformId, uint32_t licenseHashKey);

        // deallocate history and set it to NULL
        extern GLM_CROWDIO_API void destroyHistory(GlmHistory** history);

        // open an history JSON, create the *jsonvalue content and fill the historyFileVersion
        extern GLM_CROWDIO_API GlmSimulationCacheStatus readHistoryJSONVersion(const char* file, int* historyFileVersion, void* jsonvalue);

        // allocate and initialize *history from a JSON .gscla file
        // return GSC_SUCCESS || GSC_FILE_OPEN_FAILED || GSC_FILE_MAGIC_NUMBER_ERROR || GSC_FILE_VERSION_ERROR
        extern GLM_CROWDIO_API GlmSimulationCacheStatus createAndReadHistoryJSON(GlmHistory_v1** history, const char* file);
        extern GLM_CROWDIO_API GlmSimulationCacheStatus createAndReadHistoryJSONFromFileContents(GlmHistory_v1** history, const glm::GlmString& fileContents);
        extern GLM_CROWDIO_API GlmSimulationCacheStatus createAndParseHistoryJSON_v1(GlmHistory_v1** history, const void* jsonvalue);

        // write *history in a JSON .gscl filecreateAndParseHistoryJSON_v1
        // return GSC_SUCCESS || GSC_FILE_OPEN_FAILED
        extern GLM_CROWDIO_API GlmSimulationCacheStatus writeHistoryJSON(const char* file, GlmHistory* history, const uint32_t licenseHashKey);

        //-------------------------------------------------------------------------
        // Entity lists
        //-------------------------------------------------------------------------

        //convert an input string into a list of entities
        extern void GLM_CROWDIO_API parseEntitySelectionExpression(const GlmSimulationData* simulationData, const glmHistoryRuntimeStructure* historyRuntime, const glm::GlmString& entitySelectionExpression, glm::PODArray<int64_t>& entitySelectionIDs);

        //-------------------------------------------------------------------------
        // Simulation data and Frame data manipulation
        //-------------------------------------------------------------------------

        // Create a new GlmSimulationData pointed by simulationDataDestination made of simulationDataSource and modifications. User is responsible of simulationDataDestination deletion
        extern GLM_CROWDIO_API void glmCreateModifiedSimulationData(const GlmSimulationData* simulationDataSource, GlmSimulationData** simulationDataDestination, CachedSimulation& cachedSimulation, const GlmHistory* history, glmHistoryRuntimeStructure* historyAccelerationStructure);

        // Create a new GlmFrameData pointed by frameDataDestination made of frameDataSource and modifications. User is responsible of frameDataDestination deletion
        extern GLM_CROWDIO_API GlmSimulationCacheStatus glmCreateModifiedFrameData(size_t historyIndex, CachedSimulation& cachedSimulation, GlmFrameData** frameDataOut, int currentFrameIndex, uint32_t stopNodeID, bool includeStopNodeId, SimulationCacheVisualFeedbackArray* visualFeedbackArray);

        //-------------------------------------------------------------------------
        // Assets manipulation
        //-------------------------------------------------------------------------
        extern void GLM_CROWDIO_API glmApplyHistoryOnAssetAssociationInternal(size_t historyIndex, glm::Time currentTime, CachedSimulation* cachedSimulation, glm::Array<AssetOverride>& assetOverrides, glm::Array<ShaderOverrides>& shaderOverrides, ShaderNameReplacement& shaderNameReplacements);
        extern void GLM_CROWDIO_API glmApplyHistoryOnAssetAssociation(size_t historyIndex, glm::Time currentTime, CachedSimulation* cachedSimulation, glm::Array<AssetOverride>& assetOverrides, glm::Array<ShaderOverrides>& shaderOverrides, ShaderNameReplacement& shaderNameReplacements);

        //-------------------------------------------------------------------------
        // Utility functions
        //-------------------------------------------------------------------------

        // Generate the next duplication IDs for an entity, make sure it's not already in the given list of existing IDs
        extern GLM_CROWDIO_API int64_t glmGenerateNextDuplicateEntityID(int64_t srcEntityID, const glm::Array<int64_t>& alreaydExistingEntitiesIDs);
        // Types manipulations ----------------------------------------------------
        extern GLM_CROWDIO_API void glmCopyString(GlmCharValues_v1* output, const char* inputString);
        extern GLM_CROWDIO_API void glmCopyString_s(GlmCharValues_v1* output, const char* inputString, int stringSize);
        extern GLM_CROWDIO_API void glmCopyString(GlmString& outputString, const GlmCharValues_v1& inputString);
		extern GLM_CROWDIO_API void glmDeleteString(GlmCharValues_v1* string);
        // Base values write in JSON ----------------------------------------------------
        extern GLM_CROWDIO_API std::ostream& operator<<(std::ostream& out, const GlmCharValues_v1& values);

        extern GLM_CROWDIO_API void glmCreateAttributes(GlmAttribute_v1** outputAttribute, int count);
        extern GLM_CROWDIO_API void glmFillAttributeFrames(GlmAttribute_v1* output, int32_t* frames, int frameCount);
        extern GLM_CROWDIO_API void glmFillAttributeChar(GlmAttribute_v1* output, const char* name, const char* values); //count is automatic to the next '\0' char
        extern GLM_CROWDIO_API void glmFillAttributeInt32(GlmAttribute_v1* output, const char* name, int32_t* values, int count);
        extern GLM_CROWDIO_API void glmFillAttributeUint32(GlmAttribute_v1* output, const char* name, uint32_t* values, int count);
        extern GLM_CROWDIO_API void glmFillAttributeInt64(GlmAttribute_v1* output, const char* name, int64_t* values, int count);
        extern GLM_CROWDIO_API void glmFillAttributeFloat(GlmAttribute_v1* output, const char* name, float* values, int count);
        extern GLM_CROWDIO_API void glmFillAttributeVec3(GlmAttribute_v1* output, const char* name, float (*values)[3], int count);
        extern GLM_CROWDIO_API void glmFillAttributeVec4(GlmAttribute_v1* output, const char* name, float (*values)[4], int count);

        extern GLM_CROWDIO_API void glmCreateSelectorNode(GlmLayoutNode_v1& layoutNode);
        extern GLM_CROWDIO_API void glmCreateOperatorNode(GlmLayoutNode_v1& layoutNode);
        extern GLM_CROWDIO_API void glmCopyLayoutAttribute(GlmAttribute_v1& destAttribute, const GlmAttribute_v1& srcAttribute); // copy attribute, does not dealocate anything but allocate what needs to be
        extern GLM_CROWDIO_API void glmCopyLayoutNode(GlmLayoutNode_v1& destLayoutNode, const GlmLayoutNode_v1& srcLayoutNode);  // copy node, does not dealocate anything but allocate what needs to be

    } // namespace crowdio
} // namespace glm
#endif // GLM_HISTORY_H
