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

#pragma once

#include "glmSDKAPI.h"
#include <glmRagdollEntityComponent_PhysX.h>
#include "glmApexEntityClothes.h"
#include <glmCharacterController_PhysX.h>
#include <glmAbstractPhysicsRigidBodyInfo.h>

//golaem core includes
#include <glmSimulation.h>

#include "glmPhysX.h" //needed for inheritance to physx::PxSimulationEventCallback
#include "glmApex.h"

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4275)
#endif

namespace glm
{
    class PhysicsContext_PhysX;
    class PhysicsRigidBody;
    class RagdollEntityComponent_PhysX;
    struct ApexMaterial;

    namespace crowd
    {
        class PhysicsLocator;
    }

    struct GLM_SDK_API PhysXFileType
    {
    public:
        enum Value
        {
            EXTENSION_DETERMINED = 0,
            BINARY = 1,
            XML = 2,
            END = 3
        };
    };

    struct ClothingAssetWithCacheData
    {
        ClothingAssetWithCacheData()
            : _clothingAsset(NULL)
        {
        }
        ClothingAssetWithCacheData(nvidia::apex::ClothingAsset* clothingAsset)
            : _clothingAsset(clothingAsset)
        {
        }
        nvidia::apex::ClothingAsset* _clothingAsset;
        glm::Array<glm::Array<int>> _srcToApexVertexIndices; //source vertex index to apex vertex index, par geometry type (fbx/gcg/...)

        glm::SpinLock _srcToApexVertexIndicesLock; //"The API in the Clothing Module is not thread safe. It is not allowed at any time to call two methods on the same or two different APEX objects."
    };

    class GLM_SDK_API PhysicsWorld_PhysX : public physx::PxSimulationEventCallback, public physx::PxSimulationFilterCallback
    {
        friend class RagdollEntityComponent_PhysX; //due to physX locks, only the RagdollEntityComponent_PhysX should be able to reach the _mPhysics and _mScene
        friend class glm::crowd::PhysicsLocator;
        friend class CharacterController_PhysX;

    public:
		GLM_CLASS_STAT_ID(glm::MemoryStatIds::Simulation)

        PhysicsWorld_PhysX(
            const glm::GlmString& name, const glm::Vector3& gravity, float approximateObjectSize, int nbThreadToUse, int physicsSubsteps, int ccdMaxPasses, bool useGPU_Broadphase, bool useGPU_GRB, bool useApex = false
            //,const glm::Vector3& worldMin
            //,const glm::Vector3& worldMax
            ,
            bool usePVD = false, bool pvdTransmitConstraints = false, bool pvdTransmitContacts = false, bool pvdTransmitScenequeries = false, GlmString pvd_host_ip = "127.0.0.1", int pvd_port = 5425, unsigned int pvd_timeout = 100);
        ~PhysicsWorld_PhysX();

        // init if not done
        void init(PhysicsContext_PhysX* userContext);

        // finish, and clear world if last user
        void finish(PhysicsContext_PhysX* userContext);

        // update only if frame changes
        void updateForCurrentFrame(const Time dt, const int currentFrame);

        //************************************************************
        //! @name Physic world accessors and configuration
        //************************************************************
        //@{
        /**
		* \brief Save the physic world in a .bullet file
		* \param filename
		*/
        void savePhysicsWorld(const char* filename, PhysXFileType::Value fileType = PhysXFileType::XML) const;

        /**
		* \brief Load the physic world from a .bullet file
		* \param filename
		*/
        bool loadPhysicsWorld(const char* filename, PhysXFileType::Value fileType = PhysXFileType::EXTENSION_DETERMINED);
        //@}

        const glm::Vector3& getGravity() const
        {
            return _gravity;
        }

        /**
		* \brief Set a plane to add in the physics simulation. Will be added now if the simulation is already initialized, or added at initialization time if not
		* \warning The plane will be kept for each simulation start even if finish is called.
		*/
        void addPlane(const Vector3& planeNormal, float planeConstant, float staticFriction = 0.5f, float dynamicFriction = 0.5f, float restitution = 0.1f);

        /**
		* \brief add the rigid objects
		*/
        inline void addRigidObjects(glm::Array<PhysicsRigidBody*>& physicsObjects)
        {
            _physicsObjects.insert(_physicsObjects.end(), physicsObjects.begin(), physicsObjects.end());
        }

        //Get an apex asset, or load it if it does not exist yet
        ClothingAssetWithCacheData* getOrLoadClothAsset(const char* id, const glm::GlmString& assetFilePath, glm::Array<glm::GlmString>& dirmap, float addGravityScale, bool enableMatOverride, const ApexMaterial& matOverride);
        
        //The list of entities that started apex clothes
        const glm::Array<ApexEntityClothes*>& getEntitiesWithClothAssets() const
        {
            return _activeEntitiesWithCloth;
        }
        glm::Array<ApexEntityClothes*>& getEntitiesWithClothAssets()
        {
            return _activeEntitiesWithCloth;
        }

        /**
		* \brief Get the physic simulation world
		* \return
		*/
        inline physx::PxScene* getScene()
        {
            return _mScene;
        }
        inline nvidia::apex::Scene* getApexScene()
        {
            return _mApexScene;
        }

        bool isUsingTriggerShapes()
        {
            return _useTriggerShapes;
        }
        void setUseTriggerShapes(bool useTriggerShapes)
        {
            GLM_DEBUG_ASSERT(_mScene == NULL);
            _useTriggerShapes = useTriggerShapes;
        } //should be configured BEFORE initializing the scene

        inline physx::PxControllerManager* getControllerManager()
        {
            return _pxControllerManager;
        }

        /**
		* \brief Configure the size of phsyX scratch memory (allow to have ess dynamic allocations in physX). 512 is a good default value.
		*/
        void setPhysXScratchMemorySize(unsigned int memorySizeInKilobytes);

        /**
		* \brief Return true if the world was configured to use GPU, but does not necessary mean it is really using GPU
		*/
        inline bool isConfiguredToUseGpu() const
        {
            return _useGPU_Broadphase || _useGPU_GRB;
        }

        //int _id;
        //uint32_t _colorABGR;
        //PhysicsMode::Value _physicsMode;
        int addRigidBodyInfo(CharacterController_PhysX& controller);
        int addRigidBodyInfo(PhysicsRigidBody& physicsBody);
        int addRigidBodyInfo(RagdollEntityComponent_PhysX& ragdoll, AbstractTrigger::SP advancedCollisionFilter);
        void removeRigidBodyInfo(int index);

        AbstractPhysicsRigidBodyInfo& getRigidBodyInfo(int index);
        void getRigidBodyInfoThreadSafe(int index, AbstractPhysicsRigidBodyInfo& result); // collisionFilter can be called by EntityCollisionTrigger, and the rigidBodyInfo array can grow via characterController behavior creating new rigidBodyInfo. In that case, copy the object for read
        PhysicsMode::Value getRigidBodyPhysicsMode(int i);                                // not const because of spinlock
        void setRigidBodyPhysicsMode(int i, PhysicsMode::Value value);

    public:
        //************************************************************
        //! @name Debug draw parameters
        //************************************************************
        //@{
        bool _debugDraw_shapes;
        float _debugDraw_jointLimitsScale;
        float _debugDraw_contactPointsScale;
        //@}

    protected:
        //************************************************************
        //! @name Physic callbacks that have to be implemented
        //************************************************************
        //@{
        void onConstraintBreak(physx::PxConstraintInfo* /*constraints*/, physx::PxU32 /*count*/)
        {
        }
        void onWake(physx::PxActor** /*actors*/, physx::PxU32 /*count*/)
        {
        }
        void onSleep(physx::PxActor** /*actors*/, physx::PxU32 /*count*/)
        {
        }
        void onContact(const physx::PxContactPairHeader& pairHeader, const physx::PxContactPair* pairs, physx::PxU32 nbPairs);
        void onTrigger(physx::PxTriggerPair* pairs, physx::PxU32 count);
        void onAdvance(const physx::PxRigidBody* const* /*bodyBuffer*/, const physx::PxTransform* /*poseBuffer*/, const physx::PxU32 /*count*/)
        {
        }
        //@}

        //************************************************************
        //! @name Configuration Datas
        //************************************************************
        //@{
        glm::GlmString _name;
        glm::Vector3 _gravity;
        float _approximateObjectSize;
        //glm::Vector3 _worldMin;
        //glm::Vector3 _worldMax;

        int _nbThreadToUse;
        int _physicsSubsteps;
        int _ccdMaxPasses;
        bool _useGPU_Broadphase;
        bool _useGPU_GRB;
        bool _useApex;
        bool _useTriggerShapes; //trigger shapes are mandatory for physics collision triggers

        glm::GlmString _physicsWorldFilepath;
        //@}

        //************************************************************
        //! @name Physics Visual Debugger configuration
        //************************************************************
        //@{
        bool _usePVD;
        bool _pvd_transmitConstraints;
        bool _pvd_transmitContacts;
        bool _pvd_transmitScenequeries;

        physx::PxPvdTransport* _pvdTransport = NULL;
        GlmString _pvd_host_ip;    // IP of the PC which is running PVD
        int _pvd_port;             // TCP port to connect to, where PVD is listening
        unsigned int _pvd_timeout; // timeout in milliseconds to wait for PVD to respond,
                                   //@}

    private:
        struct PlaneToAdd
        {
            Vector3 _planeNormal;
            float _planeConstant;
            float _staticFriction;
            float _dynamicFriction;
            float _restitution;
        };

        void doAddPlane(const PlaneToAdd& plane);

        void applyRagdollConstantForces();
        void applyClothingActorsTransforms();

        physx::PxFilterFlags pairFound(physx::PxU32 pairID, physx::PxFilterObjectAttributes attributes0, physx::PxFilterData filterData0, const physx::PxActor* a0, const physx::PxShape* s0, physx::PxFilterObjectAttributes attributes1, physx::PxFilterData filterData1, const physx::PxActor* a1, const physx::PxShape* s1, physx::PxPairFlags& pairFlags);
        void pairLost(physx::PxU32 pairID, physx::PxFilterObjectAttributes attributes0, physx::PxFilterData filterData0, physx::PxFilterObjectAttributes attributes1, physx::PxFilterData filterData1, bool objectRemoved);
        bool statusChange(physx::PxU32& pairID, physx::PxPairFlags& pairFlags, physx::PxFilterFlags& filterFlags);
        void updateCollisionPairs();

        // update physX or Apex depending on the template argument
        // return true if the current frame was new and the physX scene was updated, false if the current frame was already updated sooner
        template <typename T>
        bool updateForCurrentFrame(T* scene, const Time dt, const int currentFrame);
        template <typename T>
        void simulateScene(T* scene, const Time dt);

        //************************************************************
        //! @name Physic world data
        //************************************************************
        //@{
        physx::PxDefaultCpuDispatcher* _mCpuDispatcher;
        physx::PxScene* _mScene;
        nvidia::apex::Scene* _mApexScene;
        physx::PxControllerManager* _pxControllerManager;

        int _currentPhysicsActorCount;
        //@}

        glm::Array<PlaneToAdd> _planesToAdd;
        glm::Array<PhysicsRigidBody*> _physicsObjects;

        int _currentFrame;

        glm::PODArray<PhysicsContext_PhysX*> _contextsUsingThisWorld;

        //scratch memory to avoid PhysX to not dynamic allocate memory during simulate calls
        //cf http://coconutlizard.co.uk/blog/ue4/physx-churn-reduction
        unsigned int _configurationScratchMemorySizeInKilobytes;
        unsigned int _GSimulateScratchMemorySize;
        unsigned char* _GSimulateScratchMemory;

        glm::SpinLock _clothingActorCreationLock; //"The API in the Clothing Module is not thread safe. It is not allowed at any time to call two methods on the same or two different APEX objects."
        glm::GlmMap<glm::GlmString, ClothingAssetWithCacheData> _clothingAssetsCache;
        glm::Array<ApexEntityClothes*> _activeEntitiesWithCloth; //contains pointers to all ragdollEntityComponent's ApexEntityClothes that currently have at least one active ClothingActor

        /**
		 * \brief 
		 When a collision between a dynamic and a kinematic is detected, it is flagged to NOT solve contact, and the call to the callback is triggered.
		 When the physX callbacks triggers a new collision pair, it is added in the _physXBuffer.
		 When engine updates, the buffer is copied in _alreadySeenByGolaem buffer (wich means that the collision flag should now be changed to eSOLVE_CONTACT)
		 When physX triggers the statusChange callback, collisions in the _alreadySeenByGolaem buffer are changed flags, and removed from buffer
		 When physX lose a collision pair, it removed from the buffer it is currently in if still there
		 */
        glm::GlmMap<unsigned int, CollisionData> _disabledPairs; //pairs are put in disabled when the AABB phase found the dynamic pair
        glm::GlmMap<unsigned int, CollisionData> _collidedPairs; //pairs are put in collided when the fine collision algo found the trigger shapes
        glm::GlmMap<unsigned int, CollisionData> _pairsToEnable; //pairs should be enabled once they collided, and have been seen by the Golaem Engine and triggers system

        glm::Array<AbstractPhysicsRigidBodyInfo> _rigidBodyInfo; // add physicsRigidBody, controller and ragdollComponent to this
        glm::SpinLock _rigidBodyInfoLock;
    };

} // namespace glm

#ifdef _MSC_VER
#pragma warning(pop)
#endif
