/********************************************************************************
* ReactPhysics3D physics library, http://www.reactphysics3d.com                 *
* Copyright (c) 2010-2019 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 "CollisionWorld.h"
#include "utils/Profiler.h"
#include "utils/Logger.h"

// Namespaces
using namespace reactphysics3d;
using namespace std;

// Initialization of static fields
uint CollisionWorld::mNbWorlds = 0;

// Constructor
CollisionWorld::CollisionWorld(MemoryManager& memoryManager, const WorldSettings& worldSettings, Logger* logger, Profiler* profiler)
               : mMemoryManager(memoryManager), mConfig(worldSettings), mEntityManager(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),
                 mBodies(mMemoryManager.getHeapAllocator()),  mEventListener(nullptr),
                 mName(worldSettings.worldName), mIsProfilerCreatedByUser(profiler != nullptr),
                 mIsLoggerCreatedByUser(logger != nullptr) {

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

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

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

        mName = ss.str();
    }

#ifdef IS_PROFILING_ACTIVE

    mProfiler = profiler;

    // If the user has not provided its own profiler, we create one
    if (mProfiler == nullptr) {

       mProfiler = new Profiler();

        // Add a destination file for the profiling data
        mProfiler->addFileDestination("rp3d_profiling_" + mName + ".txt", Profiler::Format::Text);
    }


    // Set the profiler
    mCollisionDetection.setProfiler(mProfiler);

#endif

#ifdef IS_LOGGING_ACTIVE

    mLogger = logger;

    // If the user has not provided its own logger, we create one
    if (mLogger == nullptr) {

       mLogger = new Logger();

        // Add a log destination file
        uint logLevel = static_cast<uint>(Logger::Level::Information) | static_cast<uint>(Logger::Level::Warning) |
                static_cast<uint>(Logger::Level::Error);
        mLogger->addFileDestination("rp3d_log_" + mName + ".html", logLevel, Logger::Format::HTML);
    }

#endif

    mNbWorlds++;

    RP3D_LOG(mLogger, Logger::Level::Information, Logger::Category::World,
             "Collision World: Collision world " + mName + " has been created");
    RP3D_LOG(mLogger, Logger::Level::Information, Logger::Category::World,
             "Collision World: Initial world settings: " + worldSettings.to_string());
}

// Destructor
CollisionWorld::~CollisionWorld() {

    RP3D_LOG(mLogger, Logger::Level::Information, Logger::Category::World,
             "Collision World: Collision world " + mName + " has been destroyed");

    // Destroy all the collision bodies that have not been removed
    for (int i=mBodies.size() - 1 ; i >= 0; i--) {
        destroyCollisionBody(mBodies[i]);
    }

#ifdef IS_PROFILING_ACTIVE

    /// Delete the profiler
    if (!mIsProfilerCreatedByUser) {
        delete mProfiler;
    }

#endif

#ifdef IS_LOGGING_ACTIVE

    /// Delete the logger
    if (!mIsLoggerCreatedByUser) {
        delete mLogger;
    }

#endif

    assert(mBodies.size() == 0);
    assert(mCollisionBodyComponents.getNbComponents() == 0);
    assert(mTransformComponents.getNbComponents() == 0);
    assert(mCollidersComponents.getNbComponents() == 0);
}

// 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* CollisionWorld::createCollisionBody(const Transform& transform) {

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

    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
    mBodies.add(collisionBody);

#ifdef IS_PROFILING_ACTIVE

    collisionBody->setProfiler(mProfiler);

#endif

#ifdef IS_LOGGING_ACTIVE
   collisionBody->setLogger(mLogger);
#endif

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

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

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

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

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

    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
    mBodies.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 CollisionWorld::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 CollisionWorld::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 CollisionWorld::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 CollisionWorld::getWorldAABB(const Collider* collider) const {

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

   return mCollisionDetection.getWorldAABB(collider);
}