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

#pragma once

#include "glmAbstractPhysicsRigidBodyInfo.h"
#include "glmPhysicsDefinitions_PhysX.h"
#include "glmCollisionFilter.h"
#include <glmMotionDefinitions.h>
#include <glmAnimationData.h>
#include <glmSpecificPosture.h>
#include <glmSimuEntityHandle.h>
#include <glmEulerAngles.h>
#include <glmApexEntityClothes.h>

namespace physx
{
    class PxVec3;
    class PxPhysics;
    class PxRigidDynamic;
    class PxD6Joint;
    class PxFixedJoint;
    class PxAggregate;
    class PxController;
    class PxControllerFilters;
    class PxControllerDesc;
} // namespace physx

namespace nvidia
{
    namespace apex
    {
        class ClothingAsset;
        class ClothingActor;
    } // namespace apex
} // namespace nvidia

namespace glm
{
    class MotionEntityTypeData;
    class RagdollEntityTypeData_PhysX;
    class PhysicsContext_PhysX;
    class PhysicsWorld_PhysX;
    class CharacterController_PhysX;
    class SpecificPosture;
    class Entity;
    class EntityType;
    class AbstractPhysicsRigidBodyInfo;
    class Simulation;
    struct ClothingAssetWithCacheData;

    class AbstractUserBehavior;
    class ForceBehavior;
    class GenericSpecificConverter;

    /**
	* \brief Callback called by the ForceBehavior at start time to obtain the force to apply on rigid bodies
	*/
    class GLM_SDK_API IForceCallback
    {
    public:
        GLM_ABSTRACTCLASS_STAT_ID( glm::MemoryStatIds::Simulation);

        virtual ~IForceCallback(){};
        /**
		* \brief return the force for a given entity's transform
		* \return the correct force to apply for the given transformation
		*/
        virtual Vector3 getForceAt(EntityType& entityType, ForceBehavior* forceBehavior, int64_t entityIndex) = 0;
    };

    struct GLM_SDK_API ForceParameters
    {
        ForceParameters()
            : _forceType(ForceType::LINEARFORCE)
            , _massIndependant(false)
            , _impulse(0.f, 0.f, 0.f)
            , _forceCallback(NULL)
            //, _boneIndices
            , _boneForceMode(0)
            , _multiplierMin(1.f)
            , _multiplierMax(1.f)
            , _forceBehavior(NULL)
        {
        }

        ForceParameters(const glm::Vector3& impulse, ForceType::Value forceType, bool massIndependant, const glm::PODArray<int>& boneIndices, float boneForceMode, float multiplierMin, float multiplierMax, ForceBehavior* forceBehavior)
            : _forceType(forceType)
            , _massIndependant(massIndependant)
            , _impulse(impulse)
            , _forceCallback(NULL)
            , _boneIndices(boneIndices)
            , _boneForceMode(boneForceMode)
            , _multiplierMin(multiplierMin)
            , _multiplierMax(multiplierMax)
            , _forceBehavior(forceBehavior)
        {
        }

        ForceParameters(IForceCallback* forceCallback, ForceType::Value forceType, bool massIndependant, const glm::PODArray<int>& boneIndices, float boneForceMode, float multiplierMin, float multiplierMax, ForceBehavior* forceBehavior)
            : _forceType(forceType)
            , _massIndependant(massIndependant)
            , _impulse(0.f, 0.f, 0.f)
            , _forceCallback(forceCallback)
            , _boneIndices(boneIndices)
            , _boneForceMode(boneForceMode)
            , _multiplierMin(multiplierMin)
            , _multiplierMax(multiplierMax)
            , _forceBehavior(forceBehavior)
        {
        }

        bool isValid() const
        {
            return glmFinite(_impulse.x) && glmFinite(_impulse.y) && glmFinite(_impulse.z) && glmFinite(_boneForceMode) && glmFinite(_multiplierMin) && glmFinite(_multiplierMax) && (_multiplierMax >= _multiplierMin);
        }

        ForceType::Value _forceType;
        bool _massIndependant;
        glm::Vector3 _impulse;           //impulse to apply
        IForceCallback* _forceCallback;  //force value callback
        glm::PODArray<int> _boneIndices; //an array of specific bone indexes
        float _boneForceMode;            //boneForceMode -1. == force-(force*mass) / 0. == uniform force on all bones / 1. == force*mass on all bones
        float _multiplierMin;            //multiplier per bon random min
        float _multiplierMax;            //multiplier per bon random max
        ForceBehavior* _forceBehavior;
    };

    struct GLM_SDK_API CollisionData
    {
        CollisionData()
            : _collided_bodyInfo(0)
            , _collided_simulation(NULL)
            , _collided_SpecificBoneIndex(0) //0 by default because default value for external rigid bodies is 0
            , _collider_bodyInfo(0)
            , _collider_simulation(NULL)
            , _collider_SpecificBoneIndex(0) //0 by default because default value for external rigid bodies is 0
        {
        }

        CollisionData(int collidedRigidBodyInfo, const Simulation* collidedSimulation, SimuEntityHandle collidedEntity, int collidedSpecificBoneIndex,
                      int colliderRigidBodyInfo, const Simulation* colliderSimulation, SimuEntityHandle colliderEntity, int colliderSpecificBoneIndex)
            : _collided_bodyInfo(collidedRigidBodyInfo)
            , _collided_simulation(collidedSimulation)
            , _collided_Entity(collidedEntity)
            , _collided_SpecificBoneIndex(collidedSpecificBoneIndex)
            , _collider_bodyInfo(colliderRigidBodyInfo)
            , _collider_simulation(colliderSimulation)
            , _collider_Entity(colliderEntity)
            , _collider_SpecificBoneIndex(colliderSpecificBoneIndex)
        {
        }

        CollisionData(int collidedRigidBodyInfo, const Simulation* collidedSimulation, SimuEntityHandle collidedEntity, int collidedSpecificBoneIndex,
                      int colliderRigidBodyInfo)
            : _collided_bodyInfo(collidedRigidBodyInfo)
            , _collided_simulation(collidedSimulation)
            , _collided_Entity(collidedEntity)
            , _collided_SpecificBoneIndex(collidedSpecificBoneIndex)
            , _collider_bodyInfo(colliderRigidBodyInfo)
            , _collider_simulation(NULL)
            , _collider_SpecificBoneIndex(0)
        {
        }

        bool operator==(const CollisionData& other) const
        {
            return (_collided_bodyInfo == other._collided_bodyInfo) && (_collided_simulation == other._collided_simulation) && (_collided_Entity == other._collided_Entity) && (_collided_SpecificBoneIndex == other._collided_SpecificBoneIndex) && (_collider_simulation == other._collider_simulation) && (_collider_Entity == other._collider_Entity) && (_collider_SpecificBoneIndex == other._collider_SpecificBoneIndex);
        }

        //collided
        //AbstractPhysicsRigidBodyInfo*	_collided_bodyInfo;
        int _collided_bodyInfo;
        const Simulation* _collided_simulation;
        SimuEntityHandle _collided_Entity;
        int _collided_SpecificBoneIndex;

        //collider
        //AbstractPhysicsRigidBodyInfo*	_collider_bodyInfo;
        int _collider_bodyInfo;
        const Simulation* _collider_simulation; //if it's an PhysicsObjectType::RAGDOLL_PHYSX (needed to find the correct Entity with the EntityHandle)
        SimuEntityHandle _collider_Entity;      //if it's an PhysicsObjectType::RAGDOLL_PHYSX
        int _collider_SpecificBoneIndex;        //if it's an PhysicsObjectType::RAGDOLL_PHYSX
    };

    struct GLM_SDK_API DriveParameters
    {
        DriveParameters()
            : _stiffness(100.f)
            , _damping(100.f)
            , _forceLimit(FLT_MAX)
            , _isAcceleration(true)
            , _useSLERP(true)
        {
        }

        float _stiffness;
        float _damping;
        float _forceLimit;
        bool _isAcceleration;
        bool _useSLERP; // has no meaning when the drive is a position drive
    };

    class GLM_SDK_API PhysicsCommand
    {
    public:
        class PhysicsCommandType
        {
        public:
            enum Value
            {
                START = 0,
                REQUEST_STOP,
                STOP,
                CREATE_CHARA_CONTROLLER,
                CLEAR_CHARA_CONTROLLER,
                ADD_CONSTANT_FORCE,
                REMOVE_CONSTANT_FORCE,
                ADD_IMPULSE,
                DETACH,
                END
            };
        };

        // start
        PhysicsCommandType::Value _type;

        AbstractUserBehavior* _callerBehavior; // cast to force / physicalize / charaController behavior

        // if REQUEST_STOP
        bool _physicalize;

        // if STOP
        bool _mustSetPhyMode;
        float _interpolationCoefficient;
        PhysicsMode::Value _physicalizeMode;

        glm::Vector3 _impulse; //impulse to apply
    };

    /**
	* \brief abstract interface realization for the motion entity
	*/
    class GLM_SDK_API RagdollEntityComponent_PhysX
    {
        friend class PhysicsContext_PhysX;
        friend class PhysicsWorld_PhysX;
        template <class TValue>
        friend class Array;

    public: //methods
        //************************************************************
        //! @name Helpers
        //************************************************************
        //@{
        static void rigidBodyTransformsFromPosture(Vector3& rigidBodyPosition, Quaternion& rigidBodyOrientation, const glm::SpecificPosture& specificPosture, int boneIndex, const GenericSpecificConverter* converter, const Array<Vector3>& boneLocalPosScales, const Array<Vector3>& skinningScales, const PODArray<unsigned int>& bonePhysicsConfigurationOverrideIndices, const Array<BonePhysicConfiguration>& bonePhysicsConfigurationOverrides);
        static void rigidBodyTransformsFromSpecificPostureTransforms(Vector3& rigidBodyPosition, Quaternion& rigidBodyOrientation, const Vector3& specificPostureBonePosition, const Quaternion& specificPostureBoneOrientation, int boneIndex, const GenericSpecificConverter* converter, const Array<Vector3>& boneLocalPosScales, const Array<Vector3>& skinningScales, const PODArray<unsigned int>& bonePhysicsConfigurationOverrideIndices, const Array<BonePhysicConfiguration>& bonePhysicsConfigurationOverrides);
        static void rigidBodyTransformsToPosture(glm::SpecificPosture& specificPosture, const Vector3& rigidBodyPosition, const Quaternion& rigidBodyOrientation, int boneIndex, const GenericSpecificConverter* converter, const Array<Vector3>& boneLocalPosScales, const Array<Vector3>& skinningScales, const PODArray<unsigned int>& bonePhysicsConfigurationOverrideIndices, const Array<BonePhysicConfiguration>& bonePhysicsConfigurationOverrides);
        //static void characterControllerFromPosture(glm::SpecificPosture& specificPosture, const Vector3& controllerPosition, const GenericSpecificConverter* converter, float scaleFromConverter);
        static void characterControllerToPosture(glm::SpecificPosture& specificPosture, const Vector3& controllerPosition, const GenericSpecificConverter* converter, float scaleFromConverter);

        //@}

        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

        //************************************************************
        //! @name Constructor and destructor
        //************************************************************
        //@{
        RagdollEntityComponent_PhysX();
        /**
		* \brief constructor
		* \param collisiongroup group bit in which putting the rigid body (only 32 groups are availables (0-31). If the value is out of bound, it will be set to default value. 1 is the default value for dynamic objects, and 2 for static objects)
		* \param collisionMask mask of groups with wich the body will collide (-1 for collision with every other objects)
		*/
        RagdollEntityComponent_PhysX(const short int collisionGroup, const short int collisionMask);
        ~RagdollEntityComponent_PhysX();
        //@}

        inline SimuEntityHandle getHandle() const
        {
            return _entityHandle;
        }
        inline PhysicsContext_PhysX* getPhysicsContext() const
        {
            return _thePhysicsContext;
        }

        void preUpdate(Time dt);
        void postUpdate(Time dt);
        void preUpdatePosture(bool computeKinematicRigidBodiesVelocities = false, Time deltaTimeForVelocity = 0.);
        void postUpdatePosture();

        //************************************************************
        //! @name Other Accessors
        //************************************************************
        //@{
        /// Returns the physics mode for each rigid body
        inline const int* getRigidBodiesPhysicModes() const
        {
            return _rigidBodiesPhysicModes;
        }

        /// Returns the table of rigid bodies
        inline const glm::Array<physx::PxRigidDynamic*>& getRigidDynamicBodies() const
        {
            return _rigidDynamicBodies;
        }

        const glm::Array<Vector3>& getRigidBodiesLinearVelocities() const
        {
            return _rigidBodiesLinearVelocities;
        }
        const glm::Array<EulerAngles>& getRigidBodiesAngularVelocities() const
        {
            return _rigidBodiesAngularVelocities;
        }

        const Array<Vector3>& getBoneLocalPosScales() const
        {
            return _boneLocalPosScales;
        }
        const Array<Vector3>& getBoneSkinScales() const
        {
            return _boneSkinScales;
        }

        //@}

        //************************************************************
        //! @name Inherited virtual methods
        //************************************************************
        //@{
        /// Add or remove the ragdoll in the physic simulation
        void doPhysicalize(bool physicalize);
        inline bool isPhysicalized() const
        {
            return _physicalized;
        }

        /// Set the posture to play when not physicalized
        void setReplayPosture(bool replayPosture, const SpecificPosture* replayedPosture);

        /**
		* \brief Change the mode of specific bones of the ragdoll. A ragdoll can't be in "static" mode.
		* \param mode new mode to set
		* \param boneIndices an array of specific bone indexes to change the mode
		*/
        void setPhysicsMode(const PhysicsMode::Value mode, const glm::PODArray<int>& boneIndices, DriveParameters orientationDrive = DriveParameters(), DriveParameters positionDrive = DriveParameters(), float entityMaxDistanceRatio = 0.5f);

        /// change the mode of all bones of the ragdoll
        void setPhysicsMode(const PhysicsMode::Value mode, DriveParameters orientationDrive = DriveParameters(), DriveParameters positionDrive = DriveParameters(), float entityMaxDistanceRatio = 0.5f);

        PhysicsMode::Value getPhysicsMode() const;

        void setInterpolateWithAnimationValue(float animCoefficient, const glm::PODArray<int>& boneIndices);

        /**
		* \brief Apply impulse on specific bones of the ragdoll
		* \param impulse impulse to apply
		*/
        void applyImpulse(const ForceParameters& impulse);

        /**
		* \brief Add/Remove a specific force to apply at each update on the rigid bodies
		* \note Once applied, the force is kep and never returned to normal (exept when setConstantForce is called again)
		* \param force force to apply
		* \warning the force must not be destroyed between calls to addConstantForce and removeConstantForce (it is no copied, but used directly)
		*/
        inline void addConstantForce(const ForceParameters* force)
        {
            _activeForces.push_back(force);
        }
        void removeConstantForce(const ForceParameters* force);

        /**
		* \brief Enable or disable the joint the holds a given rigid body to its parent
		*/
        void enableJoint(int rigidBodyIndex, bool enabled);

        /**
		* \brief Character Controller
		*/
        void createCharacterController(physx::PxControllerDesc* descriptor, bool useScale, const glm::Vector3& positionOffset, const CollisionFilter* collisionFilter, bool collideWithCC);
        void clearCharacterController();
        void updateCharacterController(double dt);
        //@}

        //************************************************************
        //! @name Cloth relative methods
        //************************************************************
        //@{
        ClothingActor* createAndAddClothingActor(const glm::GlmString& meshName, int entityMeshAssetIndex, int characterMeshAssetIndex, ClothingAssetWithCacheData* clothAsset, const glm::Array<PhysicsRigidBody*> rigidBodiesColliders, float additionalClothScale, bool scaleClothBones);
        void removeClothingActor(ClothingActor* clothingActor, bool freezeOnStop, int frozenClothBoneBinding);
        glm::Array<int>& getClothingUsedBones(nvidia::apex::ClothingAsset* clothAsset);
        //@}

        //************************************************************
        //! @name Inherited Collision relative methods
        //************************************************************
        //@{
        inline bool isColliding() const
        {
            return _isColliding;
        }
        const glm::Array<CollisionData>& getCollisionsData() const
        {
            return _collidingEntities;
        }
        glm::Array<CollisionData>& touchCollisionsData()
        {
            return _collidingEntities;
        }
        //@}

        // used to prepare the entity location before motion gets its physic ground
        void followGround(const Matrix4& transformation, bool adaptOri);

        void addPhysicsCommand(PhysicsCommand& currentPhyCmd);

    public: //data
        float _entityMaxDistanceRatio;
        bool _useKinematicTarget;
        bool _unphysicalizedChildrenFromAnimation; //will be from skeleton if set to false

    protected: //methods
        void doInit(PhysicsContext_PhysX& physicsContext);
        //void doUpdate(Time dt);
        void doFinish();

        void applyRagdollConstantForcesOnPhysX();
        void applyClothingActorsTransforms();

        void processPhysicsCommands();

    private: //methods
        // default copy constructor and assign
        //GLM_DISALLOW_COPY_AND_ASSIGN(RagdollEntityComponent_PhysX);

        void clearRagdoll();
        void createRagdoll(Entity& entity, RagdollEntityTypeData_PhysX& physicsEntityTypeData, MotionEntityTypeData& motionEntityTypeData, physx::PxPhysics& physics, bool useAggregates, bool useTriggerShapes);

        void createRigidBodies(const glm::Array<shapePair*>& shapes, RagdollEntityTypeData_PhysX& physicsEntityTypeData, MotionEntityTypeData& motionEntityTypeData, const SpecificPosture& referenceSpecificPosture, physx::PxPhysics& physics, const PODArray<unsigned int>& bonePhysicsConfigurationOverrideIndices, const Array<BonePhysicConfiguration>& bonePhysicsConfigurationOverrides, bool useAggregates);
        void createJoints(RagdollEntityTypeData_PhysX& physicsEntityTypeData, MotionEntityTypeData& motionEntityTypeData, const SpecificPosture& referenceSpecificPosture, physx::PxPhysics& physics, const PODArray<unsigned int>& bonePhysicsConfigurationOverrideIndices, const Array<BonePhysicConfiguration>& bonePhysicsConfigurationOverrides);
        void createPositionDriveConstraints(RagdollEntityTypeData_PhysX& physicsEntityTypeData, MotionEntityTypeData& motionEntityTypeData, physx::PxPhysics& physics);
        void cleanPositionDriveConstraints();

        void removeClothingActor(size_t actorIndex, bool freezeOnStop, int frozenClothBoneBinding);

        void sendPostureToPhysX(bool computeKinematicRigidBodiesVelocities = false, double deltaTimeForVelocity = 0.);                                                                                             /// set the graphical posture from the physic posture
        void computePostureFromPhysX(AnimationData& animationData, const MotionEntityTypeData& theMotionEntityTypeData, const RagdollEntityTypeData_PhysX& thePhysicsEntityTypeData_PhysX, float conversionScale); /// set the graphical posture from the physic posture

        inline void setPhysicsModeAsInt(int mode)
        {
            setPhysicsMode((PhysicsMode::Value)mode);
        } /// for serialization only
        inline int getPhysicsModeAsInt() const
        {
            return getPhysicsMode();
        } /// for serialization only

    private: //data
        //************************************************************
        //! @name Configuration
        //************************************************************
        //@{
        bool _automaticCollisionGroupAndMask;
        short int _collisionGroup;
        short int _collisionMask;
        bool _physicalized;
        bool _replayPosture;
        SpecificPosture _replayedPosture;
        //@}

        //************************************************************
        //! @name Ragdoll
        //************************************************************
        //@{
        float _scaleFromOriginalConfiguration; //TODO: delme
        Array<Vector3> _boneLocalPosScales;
        Array<Vector3> _boneSkinScales;
        int* _rigidBodiesPhysicModes;
        //@}

        //************************************************************
        //! @name Temp data
        //************************************************************
        //@{
        glm::PODArray<const HierarchicalBone*> _cache_recomputePosOriBones; //using it as a class member saves a lot of dynamic allocation
        //@}

        //************************************************************
        //! @name Runtime data
        //************************************************************
        //@{
        glm::Array<physx::PxRigidDynamic*> _rigidDynamicBodies;
        glm::Array<physx::PxD6Joint*> _joints;
        glm::Array<Quaternion> _jointsOffsetInverse;
        glm::Array<physx::PxD6Joint*> _positionDriveConstraints;
        glm::Array<physx::PxAggregate*> _ragdollAggregate;
        bool _usingAggregates;

        //Apex
        ApexEntityClothes _apexEntityClothes;
        GlmMap<nvidia::apex::ClothingAsset*, glm::Array<int>> _usedBonesIndices;

        //Character controller
        CharacterController_PhysX* _characterController;
        int _nbCharacterControllerBe; // just to protect that multiple behaviors don't create multiple controllers

        PhysicsContext_PhysX* _thePhysicsContext;
        //@}

        //************************************************************
        //! @name Blending data
        //************************************************************
        //@{
        glm::PODArray<float> _blendingPerSpecificBoneIndices; //bone indices on which applying the blending parameter
        bool _blendingActive;

        //@}

        //************************************************************
        //! @name Collision relative data
        //************************************************************
        //@{
        bool _isColliding;
        glm::Array<CollisionData> _collidingEntities; //may contain the same object multiple times
        //@}

        glm::Array<const ForceParameters*> _activeForces;

        glm::Array<Vector3> _rigidBodiesLinearVelocities;      //size must be the same than _rigidDynamicBodies
        glm::Array<EulerAngles> _rigidBodiesAngularVelocities; //size must be the same than _rigidDynamicBodies

        SimuEntityHandle _entityHandle;

        int _rigidBodyInfoIndex;

        glm::Array<PhysicsCommand> _PhysicsCommands;
    };
} // namespace glm
