/********************************************************************************
* ReactPhysics3D physics library, http://www.reactphysics3d.com                 *
* Copyright (c) 2010-2020 Daniel Chappuis                                       *
*********************************************************************************
*                                                                               *
* This software is provided 'as-is', without any express or implied warranty.   *
* In no event will the authors be held liable for any damages arising from the  *
* use of this software.                                                         *
*                                                                               *
* Permission is granted to anyone to use this software for any purpose,         *
* including commercial applications, and to alter it and redistribute it        *
* freely, subject to the following restrictions:                                *
*                                                                               *
* 1. The origin of this software must not be misrepresented; you must not claim *
*    that you wrote the original software. If you use this software in a        *
*    product, an acknowledgment in the product documentation would be           *
*    appreciated but is not required.                                           *
*                                                                               *
* 2. Altered source versions must be plainly marked as such, and must not be    *
*    misrepresented as being the original software.                             *
*                                                                               *
* 3. This notice may not be removed or altered from any source distribution.    *
*                                                                               *
********************************************************************************/

// Libraries
#include <reactphysics3d/engine/PhysicsCommon.h>
#include <reactphysics3d/engine/PhysicsWorld.h>
#include <reactphysics3d/constraint/BallAndSocketJoint.h>
#include <reactphysics3d/constraint/SliderJoint.h>
#include <reactphysics3d/constraint/HingeJoint.h>
#include <reactphysics3d/constraint/FixedJoint.h>
#include <reactphysics3d/utils/Profiler.h>
#include <reactphysics3d/engine/EventListener.h>
#include <reactphysics3d/engine/Island.h>
#include <reactphysics3d/collision/ContactManifold.h>
#include <reactphysics3d/containers/Stack.h>

// Namespaces
using namespace reactphysics3d;
using namespace std;

// Static initializations

uint PhysicsWorld::mNbWorlds = 0;

// Constructor
/**
 * @param gravity Gravity vector in the world (in meters per second squared)
 * @param worldSettings The settings of the world
 * @param profiler Pointer to the profiler
 */
PhysicsWorld::PhysicsWorld(MemoryManager& memoryManager, const WorldSettings& worldSettings, Profiler* /*profiler*/)
              : mMemoryManager(memoryManager), mConfig(worldSettings), mEntityManager(mMemoryManager.getHeapAllocator()), mDebugRenderer(mMemoryManager.getHeapAllocator()),
                mCollisionBodyComponents(mMemoryManager.getHeapAllocator()), mRigidBodyComponents(mMemoryManager.getHeapAllocator()),
                mTransformComponents(mMemoryManager.getHeapAllocator()), mCollidersComponents(mMemoryManager.getHeapAllocator()),
                mJointsComponents(mMemoryManager.getHeapAllocator()), mBallAndSocketJointsComponents(mMemoryManager.getHeapAllocator()),
                mFixedJointsComponents(mMemoryManager.getHeapAllocator()), mHingeJointsComponents(mMemoryManager.getHeapAllocator()),
                mSliderJointsComponents(mMemoryManager.getHeapAllocator()), mCollisionDetection(this, mCollidersComponents, mTransformComponents, mCollisionBodyComponents, mRigidBodyComponents,
                                        mMemoryManager),
                mCollisionBodies(mMemoryManager.getHeapAllocator()), mEventListener(nullptr),
                mName(worldSettings.worldName),  mIslands(mMemoryManager.getSingleFrameAllocator()),
                mContactSolverSystem(mMemoryManager, *this, mIslands, mCollisionBodyComponents, mRigidBodyComponents,
                               mCollidersComponents, mConfig.restitutionVelocityThreshold),
                mConstraintSolverSystem(*this, mIslands, mRigidBodyComponents, mTransformComponents, mJointsComponents,
                                        mBallAndSocketJointsComponents, mFixedJointsComponents, mHingeJointsComponents,
                                        mSliderJointsComponents),
                mDynamicsSystem(*this, mCollisionBodyComponents, mRigidBodyComponents, mTransformComponents, mCollidersComponents, mIsGravityEnabled, mConfig.gravity),
                mNbVelocitySolverIterations(mConfig.defaultVelocitySolverNbIterations),
                mNbPositionSolverIterations(mConfig.defaultPositionSolverNbIterations), 
                mIsSleepingEnabled(mConfig.isSleepingEnabled), mRigidBodies(mMemoryManager.getPoolAllocator()),
                mIsGravityEnabled(true), mSleepLinearVelocity(mConfig.defaultSleepLinearVelocity),
                mSleepAngularVelocity(mConfig.defaultSleepAngularVelocity), mTimeBeforeSleep(mConfig.defaultTimeBeforeSleep), mCurrentJointId(0) {

    // Automatically generate a name for the world
    if (mName == "") {

        std::stringstream ss;
        ss << "world";

        if (mNbWorlds > 0) {
            ss << mNbWorlds;
        }

        mName = ss.str();
    }

#ifdef IS_RP3D_PROFILING_ENABLED


    assert(profiler != nullptr);
    mProfiler = profiler;

    // Set the profiler
    mConstraintSolverSystem.setProfiler(mProfiler);
    mContactSolverSystem.setProfiler(mProfiler);
    mDynamicsSystem.setProfiler(mProfiler);
    mCollisionDetection.setProfiler(mProfiler);

#endif

    mNbWorlds++;

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: Physics world " + mName + " has been created",  __FILE__, __LINE__);
    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: Initial world settings: " + worldSettings.to_string(),  __FILE__, __LINE__);

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: Physics world " + mName + " has been created",  __FILE__, __LINE__);

}

// Destructor
PhysicsWorld::~PhysicsWorld() {

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: Physics world " + mName + " has been destroyed",  __FILE__, __LINE__);

    // Destroy all the collision bodies that have not been removed
	// Remark: consider using reverse iterator; don't know exactly why reverse iteration is necessary.
    //for (int i=mCollisionBodies.size() - 1 ; i >= 0; i--) {
	for (size_t i = 1, cachedSize = mCollisionBodies.size(); i <= cachedSize; ++i) {
        destroyCollisionBody(mCollisionBodies[cachedSize - i]);
    }

#ifdef IS_RP3D_PROFILING_ENABLED


    // Print the profiling report into the destinations
    mProfiler->printReport();

#endif

    // Destroy all the joints that have not been removed
    for (uint32 i=0; i < mJointsComponents.getNbComponents(); i++) {
        destroyJoint(mJointsComponents.mJoints[i]);
    }

    // Destroy all the rigid bodies that have not been removed
	// Remark: consider using reverse iterator; don't know exactly why reverse iteration is necessary.
    //for (int i=mRigidBodies.size() - 1; i >= 0; i--) {
	for (size_t i = 1, cachedSize = mRigidBodies.size(); i <= cachedSize; ++i) {
        destroyRigidBody(mRigidBodies[cachedSize - i]);
    }

    assert(mJointsComponents.getNbComponents() == 0);
    assert(mRigidBodies.size() == 0);
    assert(mCollisionBodies.size() == 0);
    assert(mCollisionBodyComponents.getNbComponents() == 0);
    assert(mTransformComponents.getNbComponents() == 0);
    assert(mCollidersComponents.getNbComponents() == 0);

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: Physics world " + mName + " has been destroyed",  __FILE__, __LINE__);
}

// Create a collision body and add it to the world
/**
 * @param transform Transformation mapping the local-space of the body to world-space
 * @return A pointer to the body that has been created in the world
 */
CollisionBody* PhysicsWorld::createCollisionBody(const Transform& transform) {

    // Create a new entity for the body
    Entity entity = mEntityManager.createEntity();

    // Check that the transform is valid
    if (!transform.isValid()) {
        RP3D_LOG(mConfig.worldName, Logger::Level::Error, Logger::Category::Body,
                 "Error when creating a collision body: the init transform is not valid",  __FILE__, __LINE__);
    }
    assert(transform.isValid());

    mTransformComponents.addComponent(entity, false, TransformComponents::TransformComponent(transform));

    // Create the collision body
    CollisionBody* collisionBody = new (mMemoryManager.allocate(MemoryManager::AllocationType::Pool,
                                        sizeof(CollisionBody)))
                                        CollisionBody(*this, entity);

    assert(collisionBody != nullptr);

    // Add the components
    CollisionBodyComponents::CollisionBodyComponent bodyComponent(collisionBody);
    mCollisionBodyComponents.addComponent(entity, false, bodyComponent);

    // Add the collision body to the world
    mCollisionBodies.add(collisionBody);

#ifdef IS_RP3D_PROFILING_ENABLED


    collisionBody->setProfiler(mProfiler);

#endif

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::Body,
             "Body " + std::to_string(entity.id) + ": New collision body created",  __FILE__, __LINE__);

    // Return the pointer to the rigid body
    return collisionBody;
}

// Destroy a collision body
/**
 * @param collisionBody Pointer to the body to destroy
 */
void PhysicsWorld::destroyCollisionBody(CollisionBody* collisionBody) {

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::Body,
             "Body " + std::to_string(collisionBody->getEntity().id) + ": collision body destroyed",  __FILE__, __LINE__);

    // Remove all the collision shapes of the body
    collisionBody->removeAllColliders();

    mCollisionBodyComponents.removeComponent(collisionBody->getEntity());
    mTransformComponents.removeComponent(collisionBody->getEntity());
    mEntityManager.destroyEntity(collisionBody->getEntity());

    // Call the destructor of the collision body
    collisionBody->~CollisionBody();

    // Remove the collision body from the list of bodies
    mCollisionBodies.remove(collisionBody);

    // Free the object from the memory allocator
    mMemoryManager.release(MemoryManager::AllocationType::Pool, collisionBody, sizeof(CollisionBody));
}

// Notify the world if a body is disabled (sleeping) or not
void PhysicsWorld::setBodyDisabled(Entity bodyEntity, bool isDisabled) {

    if (isDisabled == mCollisionBodyComponents.getIsEntityDisabled(bodyEntity)) return;

    // Notify all the components
    mCollisionBodyComponents.setIsEntityDisabled(bodyEntity, isDisabled);
    mTransformComponents.setIsEntityDisabled(bodyEntity, isDisabled);

    if (mRigidBodyComponents.hasComponent(bodyEntity)) {
        mRigidBodyComponents.setIsEntityDisabled(bodyEntity, isDisabled);
    }

    // For each collider of the body
    const List<Entity>& collidersEntities = mCollisionBodyComponents.getColliders(bodyEntity);
    for (uint i=0; i < collidersEntities.size(); i++) {

        mCollidersComponents.setIsEntityDisabled(collidersEntities[i], isDisabled);
    }

    // Disable the joints of the body if necessary
    if (mRigidBodyComponents.hasComponent(bodyEntity)) {

        // For each joint of the body
        const List<Entity>& joints = mRigidBodyComponents.getJoints(bodyEntity);
        for(uint32 i=0; i < joints.size(); i++) {

            const Entity body1Entity = mJointsComponents.getBody1Entity(joints[i]);
            const Entity body2Entity = mJointsComponents.getBody2Entity(joints[i]);

            // If both bodies of the joint are disabled
            if (mRigidBodyComponents.getIsEntityDisabled(body1Entity) &&
                mRigidBodyComponents.getIsEntityDisabled(body2Entity)) {

                // We disable the joint
                setJointDisabled(joints[i], true);
            }
            else {

                // Enable the joint
                setJointDisabled(joints[i], false);
            }
        }
    }
}

// Notify the world whether a joint is disabled or not
void PhysicsWorld::setJointDisabled(Entity jointEntity, bool isDisabled) {

    if (isDisabled == mJointsComponents.getIsEntityDisabled(jointEntity)) return;

    mJointsComponents.setIsEntityDisabled(jointEntity, isDisabled);
    if (mBallAndSocketJointsComponents.hasComponent(jointEntity)) {
        mBallAndSocketJointsComponents.setIsEntityDisabled(jointEntity, isDisabled);
    }
    if (mFixedJointsComponents.hasComponent(jointEntity)) {
        mFixedJointsComponents.setIsEntityDisabled(jointEntity, isDisabled);
    }
    if (mHingeJointsComponents.hasComponent(jointEntity)) {
        mHingeJointsComponents.setIsEntityDisabled(jointEntity, isDisabled);
    }
    if (mSliderJointsComponents.hasComponent(jointEntity)) {
        mSliderJointsComponents.setIsEntityDisabled(jointEntity, isDisabled);
    }
}

// Return true if two bodies overlap
/// Use this method if you are not interested in contacts but if you simply want to know
/// if the two bodies overlap. If you want to get the contacts, you need to use the
/// testCollision() method instead.
/**
 * @param body1 Pointer to the first body
 * @param body2 Pointer to a second body
 * @return True if the two bodies overlap
 */
bool PhysicsWorld::testOverlap(CollisionBody* body1, CollisionBody* body2) {
    return mCollisionDetection.testOverlap(body1, body2);
}

// Return the current world-space AABB of given collider
/**
 * @param collider Pointer to a collider
 * @return The AAABB of the collider in world-space
 */
AABB PhysicsWorld::getWorldAABB(const Collider* collider) const {

    if (collider->getBroadPhaseId() == -1) {
        return AABB();
    }

   return mCollisionDetection.getWorldAABB(collider);
}

// Update the physics simulation
/**
 * @param timeStep The amount of time to step the simulation by (in seconds)
 */
void PhysicsWorld::update(decimal timeStep) {

#ifdef IS_RP3D_PROFILING_ENABLED

    // Increment the frame counter of the profiler
    mProfiler->incrementFrameCounter();
#endif

    RP3D_PROFILE("PhysicsWorld::update()", mProfiler);

    // Reset the debug renderer
    if (mIsDebugRenderingEnabled) {
        mDebugRenderer.reset();
    }

    // Compute the collision detection
    mCollisionDetection.computeCollisionDetection();

    // Create the islands
    createIslands();

    // Report the contacts to the user
    mCollisionDetection.reportContactsAndTriggers();

    // Disable the joints for pair of sleeping bodies
    disableJointsOfSleepingBodies();

    // Integrate the velocities
    mDynamicsSystem.integrateRigidBodiesVelocities(timeStep);

    // Solve the contacts and constraints
    solveContactsAndConstraints(timeStep);

    // Integrate the position and orientation of each body
    mDynamicsSystem.integrateRigidBodiesPositions(timeStep, mContactSolverSystem.isSplitImpulseActive());

    // Solve the position correction for constraints
    solvePositionCorrection();

    // Update the state (positions and velocities) of the bodies
    mDynamicsSystem.updateBodiesState();

    // Update the colliders components
    mCollisionDetection.updateColliders(timeStep);

    if (mIsSleepingEnabled) updateSleepingBodies(timeStep);

    // Reset the external force and torque applied to the bodies
    mDynamicsSystem.resetBodiesForceAndTorque();

    // Reset the islands
    mIslands.clear();

    // Generate debug rendering primitives (if enabled)
    if (mIsDebugRenderingEnabled) {
        mDebugRenderer.computeDebugRenderingPrimitives(*this);
    }

    // Reset the single frame memory allocator
    mMemoryManager.resetFrameAllocator();
}


// Solve the contacts and constraints
void PhysicsWorld::solveContactsAndConstraints(decimal timeStep) {

    RP3D_PROFILE("PhysicsWorld::solveContactsAndConstraints()", mProfiler);

    // ---------- Solve velocity constraints for joints and contacts ---------- //

    // Initialize the contact solver
    mContactSolverSystem.init(mCollisionDetection.mCurrentContactManifolds, mCollisionDetection.mCurrentContactPoints, timeStep);

    // Initialize the constraint solver
    mConstraintSolverSystem.initialize(timeStep);

    // For each iteration of the velocity solver
    for (uint i=0; i<mNbVelocitySolverIterations; i++) {

        mConstraintSolverSystem.solveVelocityConstraints();

        mContactSolverSystem.solve();
    }

    mContactSolverSystem.storeImpulses();

    // Reset the contact solver
    mContactSolverSystem.reset();
}

// Solve the position error correction of the constraints
void PhysicsWorld::solvePositionCorrection() {

    RP3D_PROFILE("PhysicsWorld::solvePositionCorrection()", mProfiler);

    // ---------- Solve the position error correction for the constraints ---------- //

    // For each iteration of the position (error correction) solver
    for (uint i=0; i<mNbPositionSolverIterations; i++) {

        // Solve the position constraints
        mConstraintSolverSystem.solvePositionConstraints();
    }
}

// Disable the joints for pair of sleeping bodies
void PhysicsWorld::disableJointsOfSleepingBodies() {

    // For each joint
    for (uint32 i=0; i < mJointsComponents.getNbEnabledComponents(); i++) {

        Entity body1 = mJointsComponents.mBody1Entities[i];
        Entity body2 = mJointsComponents.mBody2Entities[i];

        // If both bodies of the joint are disabled
        if (mCollisionBodyComponents.getIsEntityDisabled(body1) && mCollisionBodyComponents.getIsEntityDisabled(body2)) {

            // Disable the joint
            setJointDisabled(mJointsComponents.mJointEntities[i], true);
        }
    }
}

// Create a rigid body into the physics world
/**
 * @param transform Transformation from body local-space to world-space
 * @return A pointer to the body that has been created in the world
 */
RigidBody* PhysicsWorld::createRigidBody(const Transform& transform) {

    // Create a new entity for the body
    Entity entity = mEntityManager.createEntity();

    // Check that the transform is valid
    if (!transform.isValid()) {
        RP3D_LOG(mConfig.worldName, Logger::Level::Error, Logger::Category::Body,
                 "Error when creating a rigid body: the init transform is not valid",  __FILE__, __LINE__);
    }
    assert(transform.isValid());

    mTransformComponents.addComponent(entity, false, TransformComponents::TransformComponent(transform));

    // Create the rigid body
    RigidBody* rigidBody = new (mMemoryManager.allocate(MemoryManager::AllocationType::Pool,
                                     sizeof(RigidBody))) RigidBody(*this, entity);
    assert(rigidBody != nullptr);

    CollisionBodyComponents::CollisionBodyComponent bodyComponent(rigidBody);
    mCollisionBodyComponents.addComponent(entity, false, bodyComponent);

    RigidBodyComponents::RigidBodyComponent rigidBodyComponent(rigidBody, BodyType::DYNAMIC, transform.getPosition());
    mRigidBodyComponents.addComponent(entity, false, rigidBodyComponent);

    // Compute the inverse mass
    mRigidBodyComponents.setMassInverse(entity, decimal(1.0) / mRigidBodyComponents.getMass(entity));

    // Add the rigid body to the physics world
    mRigidBodies.add(rigidBody);

#ifdef IS_RP3D_PROFILING_ENABLED

    rigidBody->setProfiler(mProfiler);
#endif

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::Body,
             "Body " + std::to_string(entity.id) + ": New collision body created",  __FILE__, __LINE__);

    // Return the pointer to the rigid body
    return rigidBody;
}

// Destroy a rigid body and all the joints which it belongs
/**
 * @param rigidBody Pointer to the body you want to destroy
 */
void PhysicsWorld::destroyRigidBody(RigidBody* rigidBody) {

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::Body,
             "Body " + std::to_string(rigidBody->getEntity().id) + ": rigid body destroyed",  __FILE__, __LINE__);

    // Remove all the collision shapes of the body
    rigidBody->removeAllColliders();

    // Destroy all the joints in which the rigid body to be destroyed is involved
    const List<Entity>& joints = mRigidBodyComponents.getJoints(rigidBody->getEntity());
    for (uint32 i=0; i < joints.size(); i++) {
        destroyJoint(mJointsComponents.getJoint(joints[i]));
    }

    // Destroy the corresponding entity and its components
    mCollisionBodyComponents.removeComponent(rigidBody->getEntity());
    mRigidBodyComponents.removeComponent(rigidBody->getEntity());
    mTransformComponents.removeComponent(rigidBody->getEntity());
    mEntityManager.destroyEntity(rigidBody->getEntity());

    // Call the destructor of the rigid body
    rigidBody->~RigidBody();

    // Remove the rigid body from the list of rigid bodies
    mRigidBodies.remove(rigidBody);

    // Free the object from the memory allocator
    mMemoryManager.release(MemoryManager::AllocationType::Pool, rigidBody, sizeof(RigidBody));
}

// Create a joint between two bodies in the world and return a pointer to the new joint
/**
 * @param jointInfo The information that is necessary to create the joint
 * @return A pointer to the joint that has been created in the world
 */
Joint* PhysicsWorld::createJoint(const JointInfo& jointInfo) {

    // Create a new entity for the joint
    Entity entity = mEntityManager.createEntity();

    Joint* newJoint = nullptr;

    const bool isJointDisabled = mRigidBodyComponents.getIsEntityDisabled(jointInfo.body1->getEntity()) &&
                                 mRigidBodyComponents.getIsEntityDisabled(jointInfo.body2->getEntity());

    // Allocate memory to create the new joint
    switch(jointInfo.type) {

        // Ball-and-Socket joint
        case JointType::BALLSOCKETJOINT:
        {
            // Create a BallAndSocketJoint component
            BallAndSocketJointComponents::BallAndSocketJointComponent ballAndSocketJointComponent;
            mBallAndSocketJointsComponents.addComponent(entity, isJointDisabled, ballAndSocketJointComponent);

            void* allocatedMemory = mMemoryManager.allocate(MemoryManager::AllocationType::Pool,
                                                            sizeof(BallAndSocketJoint));
            const BallAndSocketJointInfo& info = static_cast<const BallAndSocketJointInfo&>(jointInfo);
            BallAndSocketJoint* joint = new (allocatedMemory) BallAndSocketJoint(entity, *this, info);

            newJoint = joint;
            mBallAndSocketJointsComponents.setJoint(entity, joint);
            break;
        }

        // Slider joint
        case JointType::SLIDERJOINT:
        {
            const SliderJointInfo& info = static_cast<const SliderJointInfo&>(jointInfo);

            // Create a SliderJoint component
            SliderJointComponents::SliderJointComponent sliderJointComponent(info.isLimitEnabled, info.isMotorEnabled,
                                                                             info.minTranslationLimit, info.maxTranslationLimit,
                                                                             info.motorSpeed, info.maxMotorForce);
            mSliderJointsComponents.addComponent(entity, isJointDisabled, sliderJointComponent);

            void* allocatedMemory = mMemoryManager.allocate(MemoryManager::AllocationType::Pool, sizeof(SliderJoint));
            SliderJoint* joint = new (allocatedMemory) SliderJoint(entity, *this, info);

            newJoint = joint;
            mSliderJointsComponents.setJoint(entity, joint);

            break;
        }

        // Hinge joint
        case JointType::HINGEJOINT:
        {
            const HingeJointInfo& info = static_cast<const HingeJointInfo&>(jointInfo);

            // Create a HingeJoint component
            HingeJointComponents::HingeJointComponent hingeJointComponent(info.isLimitEnabled, info.isMotorEnabled,
                                                                          info.minAngleLimit, info.maxAngleLimit,
                                                                          info.motorSpeed, info.maxMotorTorque);
            mHingeJointsComponents.addComponent(entity, isJointDisabled, hingeJointComponent);

            void* allocatedMemory = mMemoryManager.allocate(MemoryManager::AllocationType::Pool,
                                                            sizeof(HingeJoint));
            HingeJoint* joint = new (allocatedMemory) HingeJoint(entity, *this, info);

            newJoint = joint;
            mHingeJointsComponents.setJoint(entity, joint);
            break;
        }

        // Fixed joint
        case JointType::FIXEDJOINT:
        {
            // Create a BallAndSocketJoint component
            FixedJointComponents::FixedJointComponent fixedJointComponent;
            mFixedJointsComponents.addComponent(entity, isJointDisabled, fixedJointComponent);

            void* allocatedMemory = mMemoryManager.allocate(MemoryManager::AllocationType::Pool,
                                                            sizeof(FixedJoint));
            const FixedJointInfo& info = static_cast<const FixedJointInfo&>(jointInfo);
            FixedJoint* joint = new (allocatedMemory) FixedJoint(entity, *this, info);

            newJoint = joint;

            mFixedJointsComponents.setJoint(entity, joint);

            break;
        }

        default:
        {
            assert(false);
            return nullptr;
        }
    }

    JointComponents::JointComponent jointComponent(jointInfo.body1->getEntity(), jointInfo.body2->getEntity(), newJoint, jointInfo.type,
                                                   jointInfo.positionCorrectionTechnique, jointInfo.isCollisionEnabled);
    mJointsComponents.addComponent(entity, isJointDisabled, jointComponent);

    // If the collision between the two bodies of the constraint is disabled
    if (!jointInfo.isCollisionEnabled) {

        // Add the pair of bodies in the set of body pairs that cannot collide with each other
        mCollisionDetection.addNoCollisionPair(jointInfo.body1->getEntity(), jointInfo.body2->getEntity());
    }

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::Joint,
             "Joint " + std::to_string(newJoint->getEntity().id) + ": New joint created",  __FILE__, __LINE__);
    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::Joint,
             "Joint " + std::to_string(newJoint->getEntity().id) + ": " + newJoint->to_string(),  __FILE__, __LINE__);

    // Add the joint into the joint list of the bodies involved in the joint
    addJointToBodies(jointInfo.body1->getEntity(), jointInfo.body2->getEntity(), entity);

    // Return the pointer to the created joint
    return newJoint;
}

// Destroy a joint
/**
 * @param joint Pointer to the joint you want to destroy
 */
void PhysicsWorld::destroyJoint(Joint* joint) {

    assert(joint != nullptr);

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::Joint,
             "Joint " + std::to_string(joint->getEntity().id) + ": joint destroyed",  __FILE__, __LINE__);

    // If the collision between the two bodies of the constraint was disabled
    if (!joint->isCollisionEnabled()) {

        // Remove the pair of bodies from the set of body pairs that cannot collide with each other
        mCollisionDetection.removeNoCollisionPair(joint->getBody1()->getEntity(), joint->getBody2()->getEntity());
    }

    RigidBody* body1 = joint->getBody1();
    RigidBody* body2 = joint->getBody2();

    // Wake up the two bodies of the joint
    body1->setIsSleeping(false);
    body2->setIsSleeping(false);

    // Remove the joint from the joint list of the bodies involved in the joint
    mRigidBodyComponents.removeJointFromBody(body1->getEntity(), joint->getEntity());
    mRigidBodyComponents.removeJointFromBody(body2->getEntity(), joint->getEntity());

    size_t nbBytes = joint->getSizeInBytes();

    Entity jointEntity = joint->getEntity();

    // Destroy the corresponding entity and its components
    mJointsComponents.removeComponent(jointEntity);
    if (mBallAndSocketJointsComponents.hasComponent(jointEntity)) {
        mBallAndSocketJointsComponents.removeComponent(jointEntity);
    }
    if (mFixedJointsComponents.hasComponent(jointEntity)) {
        mFixedJointsComponents.removeComponent(jointEntity);
    }
    if (mHingeJointsComponents.hasComponent(jointEntity)) {
        mHingeJointsComponents.removeComponent(jointEntity);
    }
    if (mSliderJointsComponents.hasComponent(jointEntity)) {
        mSliderJointsComponents.removeComponent(jointEntity);
    }
    mEntityManager.destroyEntity(jointEntity);

    // Call the destructor of the joint
    joint->~Joint();

    // Release the allocated memory
    mMemoryManager.release(MemoryManager::AllocationType::Pool, joint, nbBytes);
}


// Set the number of iterations for the velocity constraint solver
/**
 * @param nbIterations Number of iterations for the velocity solver
 */
void PhysicsWorld::setNbIterationsVelocitySolver(uint nbIterations) {

    mNbVelocitySolverIterations = nbIterations;

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: Set nb iterations velocity solver to " + std::to_string(nbIterations),  __FILE__, __LINE__);
}

// Add the joint to the list of joints of the two bodies involved in the joint
void PhysicsWorld::addJointToBodies(Entity body1, Entity body2, Entity joint) {

    mRigidBodyComponents.addJointToBody(body1, joint);

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::Body,
             "Body " + std::to_string(body1.id) + ": Joint " + std::to_string(joint.id) + " added to body",  __FILE__, __LINE__);

    mRigidBodyComponents.addJointToBody(body2, joint);

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::Body,
             "Body " + std::to_string(body2.id) + ": Joint " + std::to_string(joint.id) + " added to body",  __FILE__, __LINE__);
}

// Compute the islands using potential contacts and joints
/// We compute the islands before creating the actual contacts here because we want all
/// the contact manifolds and contact points of the same island
/// to be packed together into linear arrays of manifolds and contacts for better caching.
/// An island is an isolated group of rigid bodies that have constraints (joints or contacts)
/// between each other. This method computes the islands at each time step as follows: For each
/// awake rigid body, we run a Depth First Search (DFS) through the constraint graph of that body
/// (graph where nodes are the bodies and where the edges are the constraints between the bodies) to
/// find all the bodies that are connected with it (the bodies that share joints or contacts with
/// it). Then, we create an island with this group of connected bodies.
void PhysicsWorld::createIslands() {

    RP3D_PROFILE("PhysicsWorld::createIslands()", mProfiler);

    // Reset all the isAlreadyInIsland variables of bodies and joints
    for (uint b=0; b < mRigidBodyComponents.getNbComponents(); b++) {

        mRigidBodyComponents.mIsAlreadyInIsland[b] = false;
    }
    for (uint32 i=0; i < mJointsComponents.getNbComponents(); i++) {
        mJointsComponents.mIsAlreadyInIsland[i] = false;
    }

    // Create a stack for the bodies to visit during the Depth First Search
    Stack<Entity> bodyEntityIndicesToVisit(mMemoryManager.getSingleFrameAllocator());

    uint nbTotalManifolds = 0;

    // For each rigid body component
    for (uint b=0; b < mRigidBodyComponents.getNbEnabledComponents(); b++) {

        // If the body has already been added to an island, we go to the next body
        if (mRigidBodyComponents.mIsAlreadyInIsland[b]) continue;

        // If the body is static, we go to the next body
        if (mRigidBodyComponents.mBodyTypes[b] == BodyType::STATIC) continue;

        // Reset the stack of bodies to visit
        bodyEntityIndicesToVisit.clear();

        // Add the body into the stack of bodies to visit
        mRigidBodyComponents.mIsAlreadyInIsland[b] = true;
        bodyEntityIndicesToVisit.push(mRigidBodyComponents.mBodiesEntities[b]);

        // Create the new island
        uint32 islandIndex = mIslands.addIsland(nbTotalManifolds);

        // While there are still some bodies to visit in the stack
        while (bodyEntityIndicesToVisit.size() > 0) {

            // Get the next body to visit from the stack
            const Entity bodyToVisitEntity = bodyEntityIndicesToVisit.pop();

            // Add the body into the island
            mIslands.bodyEntities[islandIndex].add(bodyToVisitEntity);

            RigidBody* rigidBodyToVisit = static_cast<RigidBody*>(mCollisionBodyComponents.getBody(bodyToVisitEntity));

            // Awake the body if it is sleeping
            rigidBodyToVisit->setIsSleeping(false);

            // If the current body is static, we do not want to perform the DFS search across that body
            if (rigidBodyToVisit->getType() == BodyType::STATIC) continue;

            // If the body is involved in contacts with other bodies
            auto itBodyContactPairs = mCollisionDetection.mMapBodyToContactPairs.find(bodyToVisitEntity);
            if (itBodyContactPairs != mCollisionDetection.mMapBodyToContactPairs.end()) {

                // For each contact pair in which the current body is involded
                List<uint>& contactPairs = itBodyContactPairs->second;
                for (size_t p=0; p < contactPairs.size(); p++) {

                    ContactPair& pair = (*mCollisionDetection.mCurrentContactPairs)[contactPairs[p]];

                    // Check if the current contact pair has already been added into an island
                    if (pair.isAlreadyInIsland) continue;

                    // If the colliding body is a RigidBody (and not a CollisionBody) and is not a trigger
                    if (mRigidBodyComponents.hasComponent(pair.body1Entity) && mRigidBodyComponents.hasComponent(pair.body2Entity)
                        && !mCollidersComponents.getIsTrigger(pair.collider1Entity) && !mCollidersComponents.getIsTrigger(pair.collider2Entity)) {

                        assert(pair.potentialContactManifoldsIndices.size() > 0);
                        nbTotalManifolds += static_cast<uint>(pair.potentialContactManifoldsIndices.size());

                        // Add the contact manifold into the island
                        mIslands.nbContactManifolds[islandIndex] += static_cast<uint>(pair.potentialContactManifoldsIndices.size());
                        pair.isAlreadyInIsland = true;

                        const Entity otherBodyEntity = pair.body1Entity == bodyToVisitEntity ? pair.body2Entity : pair.body1Entity;

                        // Check if the other body has already been added to the island
                        if (mRigidBodyComponents.getIsAlreadyInIsland(otherBodyEntity)) continue;

                        // Insert the other body into the stack of bodies to visit
                        bodyEntityIndicesToVisit.push(otherBodyEntity);
                        mRigidBodyComponents.setIsAlreadyInIsland(otherBodyEntity, true);
                    }
                    else {

                        // Add the contact pair index in the list of contact pairs that won't be part of islands
                        pair.isAlreadyInIsland = true;
                    }
                }
            }

            // For each joint in which the current body is involved
            const List<Entity>& joints = mRigidBodyComponents.getJoints(rigidBodyToVisit->getEntity());
            for (uint32 i=0; i < joints.size(); i++) {

                // Check if the current joint has already been added into an island
                if (mJointsComponents.getIsAlreadyInIsland(joints[i])) continue;

                // Add the joint into the island
                mJointsComponents.setIsAlreadyInIsland(joints[i], true);

                const Entity body1Entity = mJointsComponents.getBody1Entity(joints[i]);
                const Entity body2Entity = mJointsComponents.getBody2Entity(joints[i]);
                const Entity otherBodyEntity = body1Entity == bodyToVisitEntity ? body2Entity : body1Entity;

                // Check if the other body has already been added to the island
                if (mRigidBodyComponents.getIsAlreadyInIsland(otherBodyEntity)) continue;

                // Insert the other body into the stack of bodies to visit
                bodyEntityIndicesToVisit.push(otherBodyEntity);
                mRigidBodyComponents.setIsAlreadyInIsland(otherBodyEntity, true);
            }
        }

        // Reset the isAlreadyIsland variable of the static bodies so that they
        // can also be included in the other islands
        for (uint j=0; j < mRigidBodyComponents.getNbEnabledComponents(); j++) {

            if (mRigidBodyComponents.mBodyTypes[j] == BodyType::STATIC) {
                mRigidBodyComponents.mIsAlreadyInIsland[j] = false;
            }
        }
    }

    mCollisionDetection.mMapBodyToContactPairs.clear(true);
}

// Put bodies to sleep if needed.
/// For each island, if all the bodies have been almost still for a long enough period of
/// time, we put all the bodies of the island to sleep.
void PhysicsWorld::updateSleepingBodies(decimal timeStep) {

    RP3D_PROFILE("PhysicsWorld::updateSleepingBodies()", mProfiler);

    const decimal sleepLinearVelocitySquare = mSleepLinearVelocity * mSleepLinearVelocity;
    const decimal sleepAngularVelocitySquare = mSleepAngularVelocity * mSleepAngularVelocity;

    // For each island of the world
    for (uint i=0; i<mIslands.getNbIslands(); i++) {

        decimal minSleepTime = DECIMAL_LARGEST;

        // For each body of the island
        for (uint b=0; b < mIslands.bodyEntities[i].size(); b++) {

            const Entity bodyEntity = mIslands.bodyEntities[i][b];

            // Skip static bodies
            if (mRigidBodyComponents.getBodyType(bodyEntity) == BodyType::STATIC) continue;

            // If the body is velocity is large enough to stay awake
            if (mRigidBodyComponents.getLinearVelocity(bodyEntity).lengthSquare() > sleepLinearVelocitySquare ||
                mRigidBodyComponents.getAngularVelocity(bodyEntity).lengthSquare() > sleepAngularVelocitySquare ||
                !mRigidBodyComponents.getIsAllowedToSleep(bodyEntity)) {

                // Reset the sleep time of the body
                mRigidBodyComponents.setSleepTime(bodyEntity, decimal(0.0));
                minSleepTime = decimal(0.0);
            }
            else {  // If the body velocity is below the sleeping velocity threshold

                // Increase the sleep time
                decimal sleepTime = mRigidBodyComponents.getSleepTime(bodyEntity);
                mRigidBodyComponents.setSleepTime(bodyEntity, sleepTime + timeStep);
                sleepTime = mRigidBodyComponents.getSleepTime(bodyEntity);
                if (sleepTime < minSleepTime) {
                    minSleepTime = sleepTime;
                }
            }
        }

        // If the velocity of all the bodies of the island is under the
        // sleeping velocity threshold for a period of time larger than
        // the time required to become a sleeping body
        if (minSleepTime >= mTimeBeforeSleep) {

            // Put all the bodies of the island to sleep
            for (uint b=0; b < mIslands.bodyEntities[i].size(); b++) {

                const Entity bodyEntity = mIslands.bodyEntities[i][b];
                RigidBody* body = mRigidBodyComponents.getRigidBody(bodyEntity);
                body->setIsSleeping(true);
            }
        }
    }
}

// Enable/Disable the sleeping technique.
/// The sleeping technique is used to put bodies that are not moving into sleep
/// to speed up the simulation.
/**
 * @param isSleepingEnabled True if you want to enable the sleeping technique
 *                          and false otherwise
 */
void PhysicsWorld::enableSleeping(bool isSleepingEnabled) {
    mIsSleepingEnabled = isSleepingEnabled;

    if (!mIsSleepingEnabled) {

        // For each body of the world
        List<RigidBody*>::Iterator it;
        for (it = mRigidBodies.begin(); it != mRigidBodies.end(); ++it) {

            // Wake up the rigid body
            (*it)->setIsSleeping(false);
        }
    }

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: isSleepingEnabled=" + (isSleepingEnabled ? std::string("true") : std::string("false")) ,  __FILE__, __LINE__);
}

// Set the number of iterations for the position constraint solver
/**
 * @param nbIterations Number of iterations for the position solver
 */
void PhysicsWorld::setNbIterationsPositionSolver(uint nbIterations) {

    mNbPositionSolverIterations = nbIterations;

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: Set nb iterations position solver to " + std::to_string(nbIterations),  __FILE__, __LINE__);
}

// Set the gravity vector of the world
/**
 * @param gravity The gravity vector (in meter per seconds squared)
 */
void PhysicsWorld::setGravity(const Vector3& gravity) {

    mConfig.gravity = gravity;

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: Set gravity vector to " + gravity.to_string(),  __FILE__, __LINE__);
}

// Set the sleep linear velocity.
/// When the velocity of a body becomes smaller than the sleep linear/angular
/// velocity for a given amount of time, the body starts sleeping and does not need
/// to be simulated anymore.
/**
 * @param sleepLinearVelocity The sleep linear velocity (in meters per second)
 */
void PhysicsWorld::setSleepLinearVelocity(decimal sleepLinearVelocity) {
    assert(sleepLinearVelocity >= decimal(0.0));
    mSleepLinearVelocity = sleepLinearVelocity;

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: sleepLinearVelocity= " + std::to_string(sleepLinearVelocity),  __FILE__, __LINE__);
}

// Set the sleep angular velocity.
/// When the velocity of a body becomes smaller than the sleep linear/angular
/// velocity for a given amount of time, the body starts sleeping and does not need
/// to be simulated anymore.
/**
 * @param sleepAngularVelocity The sleep angular velocity (in radian per second)
 */
void PhysicsWorld::setSleepAngularVelocity(decimal sleepAngularVelocity) {
    assert(sleepAngularVelocity >= decimal(0.0));
    mSleepAngularVelocity = sleepAngularVelocity;

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: sleepAngularVelocity= " + std::to_string(sleepAngularVelocity),  __FILE__, __LINE__);
}

// Set the time a body is required to stay still before sleeping
/**
 * @param timeBeforeSleep Time a body is required to stay still before sleeping (in seconds)
 */
void PhysicsWorld::setTimeBeforeSleep(decimal timeBeforeSleep) {
    assert(timeBeforeSleep >= decimal(0.0));
    mTimeBeforeSleep = timeBeforeSleep;

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: timeBeforeSleep= " + std::to_string(timeBeforeSleep),  __FILE__, __LINE__);
}

// Enable/Disable the gravity
/**
 * @param isGravityEnabled True if you want to enable the gravity in the world
 *                         and false otherwise
 */
void PhysicsWorld::setIsGravityEnabled(bool isGravityEnabled) {
    mIsGravityEnabled = isGravityEnabled;

    RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::World,
             "Physics World: isGravityEnabled= " + (isGravityEnabled ? std::string("true") : std::string("false")),  __FILE__, __LINE__);
}

// Return a constant pointer to a given CollisionBody of the world
/**
 * @param index Index of a CollisionBody in the world
 * @return Constant pointer to a given CollisionBody
 */
const CollisionBody* PhysicsWorld::getCollisionBody(size_t index) const {

    if (index >= getNbCollisionBodies()) {

        RP3D_LOG(mConfig.worldName, Logger::Level::Error, Logger::Category::World,
                 "Error when getting collision body: index is out of bounds",  __FILE__, __LINE__);
    }

    assert(index < mCollisionBodies.size());

    return mCollisionBodies[index];
}

// Return a pointer to a given CollisionBody of the world
/**
 * @param index Index of a CollisionBody in the world
 * @return Pointer to a given CollisionBody
 */
CollisionBody* PhysicsWorld::getCollisionBody(size_t index) {

    if (index >= getNbCollisionBodies()) {

        RP3D_LOG(mConfig.worldName, Logger::Level::Error, Logger::Category::World,
                 "Error when getting collision body: index is out of bounds",  __FILE__, __LINE__);
    }

    assert(index < mCollisionBodies.size());

    return mCollisionBodies[index];
}

// Return a constant pointer to a given RigidBody of the world
/**
 * @param index Index of a RigidBody in the world
 * @return Constant pointer to a given RigidBody
 */
const RigidBody* PhysicsWorld::getRigidBody(size_t index) const {

    if (index >= getNbRigidBodies()) {

        RP3D_LOG(mConfig.worldName, Logger::Level::Error, Logger::Category::World,
                 "Error when getting rigid body: index is out of bounds",  __FILE__, __LINE__);
    }

    assert(index < mRigidBodies.size());

    return mRigidBodies[index];
}

// Return a pointer to a given RigidBody of the world
/**
 * @param index Index of a RigidBody in the world
 * @return Pointer to a given RigidBody
 */
RigidBody* PhysicsWorld::getRigidBody(size_t index) {

    if (index >= getNbRigidBodies()) {

        RP3D_LOG(mConfig.worldName, Logger::Level::Error, Logger::Category::World,
                 "Error when getting rigid body: index is out of bounds",  __FILE__, __LINE__);
    }

    assert(index < mRigidBodies.size());

    return mRigidBodies[index];
}

