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

#pragma once

#include "glmCoreAPI.h"

#include "glmMap.h"
#include "glmPODArray.h"
#include "glmArray.h"
#include "glmString.h"
#include "glmMap.h"
#include "glmBlindData.h"
#include "glmRandomNumberGenerator.h"

#include <glmGolaemCharacterDeclarations.h> // GolaemCharacter declarations

namespace glm
{
    struct GLM_CORE_API ShaderAssetDataContainer
    {
        glm::Array<glm::PODArray<int>> intData = glm::Array<glm::PODArray<int>>(glm::MemoryStatIds::GolaemShared);
        glm::Array<glm::PODArray<float>> floatData = glm::Array<glm::PODArray<float>>(glm::MemoryStatIds::GolaemShared);
        glm::Array<glm::Array<Vector3>> vectorData = glm::Array<glm::Array<Vector3>>(glm::MemoryStatIds::GolaemShared);
        glm::Array<glm::Array<glm::GlmString>> stringData = glm::Array<glm::Array<glm::GlmString>>(glm::MemoryStatIds::GolaemShared);

        glm::Array<PODArray<size_t>> globalToSpecificShaderAttrIdxPerChar = glm::Array<PODArray<size_t>>(glm::MemoryStatIds::GolaemShared);
        glm::Array<PODArray<size_t>> specificShaderAttrCountersPerChar = glm::Array<PODArray<size_t>>(glm::MemoryStatIds::GolaemShared);

        void clear();
    };

    class GLM_CORE_API AssetOverride
    {

    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

        int64_t _entityId;

    private:
        glm::PODArray<int> _meshAssets;         // override list
        glm::PODArray<int> _meshAssetsToAdd;    // force add
        glm::PODArray<int> _meshAssetsToRemove; // force remove

        bool _useOverrideList; // can be combined with override add-remove
        bool _useOverrideAddRemove;

    public:
        AssetOverride();

        void overrideMeshAssetList(const glm::PODArray<int>& overrideList);
        void forceAddMeshAsset(int);
        void forceRemoveMeshAsset(int);

        void getMeshAssetWithOverride(const PODArray<int>& originalMeshAssetList, PODArray<int>& overridenMeshAssetList) const;

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

    class GLM_CORE_API ShaderOverride
    {
    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);
        glm::GlmString _name;
        glm::GlmString _stringValue; // for string attributes
        glm::Vector3 _value;
        glm::Vector3 _min = glm::Vector3(0.f); // for int/float/vector attribute
        glm::Vector3 _max = glm::Vector3(0.f);
        RandomNumberGenerator::SP _rng;
        int32_t _rngSeed = 1; // to be able to reset it before computing shader data
        bool _useValue = true;
    };

    class GLM_CORE_API ShaderOverrides
    {
    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

        int64_t _entityId;
        glm::Array<ShaderOverride> _overrides;

        // for GlmSet
        bool operator<(const ShaderOverrides& other)
        {
            return _entityId < other._entityId;
        }

        ShaderOverrides();
    };

    // There is an array of int per entity, matching entries in main arrays (to avoid exploding memory by copying strings per entity)
    class GLM_CORE_API ShaderNameReplacement
    {
    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

        glm::GlmMap<int64_t, glm::PODArray<uint16_t>> _replacementsPerEntityId;

        glm::Array<glm::GlmString> _replacedShaderNames;
        glm::Array<glm::GlmString> _replacementShaderNames;

        //ShaderNameReplacement() {} // default constructor
    };

    class GLM_CORE_API MeshAssetAssociation
    {
    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

        const glm::PODArray<int>& getMeshAssetAssociationForEntity(int meshIndex);

        glm::Array<glm::PODArray<int>> _meshAssociations;
    };

    void GLM_CORE_API loadCharacterFiles(glm::PODArray<GolaemCharacter*>& characters, const glm::GlmString& characterFiles, bool resolveRelativePaths = true, bool multithreaded = true, const Array<GlmString>* dirmapRules = NULL);
    void GLM_CORE_API loadCharacterFiles(glm::PODArray<GolaemCharacter*>& characters, const glm::GlmString& characterFiles, const glm::GlmString& shadingNamespaces, bool resolveRelativePaths = true, bool multithreaded = true, const Array<GlmString>* dirmapRules = NULL);

    /**
	* \brief Loads an assets association file and computes mesh associations
	* \param[in] caaPath caa file to load
	* \param[out] assetAssoData Loaded content of the caa file
	* \param[out] meshAssociations Correspondance entityId <-> meshesIds
	* \param[in] log Log to output
	* \warning loadCAMFile must have been called before calling this method
	*/
    bool GLM_CORE_API loadCAAFile(AssetAssociations& assetAssoData, const glm::GlmString& caaPath);

    /**
	* \brief
	* \param blindDataMappingType 0 - map by id, 1 - map by name
	*/
    void GLM_CORE_API automapBlindData(glm::PODArray<int>& blindDataMapping, const glm::Array<BlindData>& skeletonBlindData, const glm::Array<BlindData>& motionBlindData, int blindDataMappingType, const glm::Array<BlindData::Type::Value>& typesToNotMap = glm::Array<BlindData::Type::Value>());

    void GLM_CORE_API computeWeights(glm::PODArray<float>& weights, const GolaemCharacter& golaemCharacter, int renderingTypeIdx, int assetIndex, WeightOverrideType::Value overrideType);

    int GLM_CORE_API computeNextIndex(glm::PODArray<int>& counters, const glm::PODArray<float>& weights, RandomNumberGenerator& rng);

    // returns map with keys but no entries, to be filled with simulation KBs or simdata
    void GLM_CORE_API getAssetGroupWeightAttributeNames(const glm::PODArray<GolaemCharacter*>& characters, GlmMap<GlmString, PODArray<int>>& weightAttributeNames);

    void GLM_CORE_API computeWeightedSequence(glm::PODArray<int>& returnedSequence, const glm::PODArray<int>& ids, const glm::PODArray<int>& weights, bool enableRandomShuffle, unsigned int rngSeed);

    void GLM_CORE_API computeWeightedSequence(glm::PODArray<int>& returnedSequence, const glm::PODArray<int>& ids, const glm::PODArray<float>& weights, size_t sequenceSize, bool enableRandomShuffle, unsigned int rngSeed);

    void GLM_CORE_API computePercents(glm::PODArray<int>& percents, const glm::PODArray<float>& weights);
    void GLM_CORE_API computePercents(glm::PODArray<float>& percents, const glm::PODArray<float>& weights);

    // manage memory inside glm crowd dll
    void GLM_CORE_API clearAssetAssociation(AssetAssociations& assetAssoData);
    void GLM_CORE_API clearMeshAssetAssociations(MeshAssetAssociation& meshAssociations);
    void GLM_CORE_API clearShaderNameReplacement(glm::ShaderNameReplacement& shaderNameReplacement);

    bool GLM_CORE_API findDirmappedFile(glm::GlmString& correctedFilePath, const GlmString& filePath, const glm::Array<glm::GlmString>& dirmapRules);

    //-----------------------------------------------------------------------------
    inline const glm::PODArray<int>& AssetOverride::getOverrideMeshList()
    {
        return _meshAssets;
    }
} // namespace glm
