/********************************************************************************
* ReactPhysics3D physics library, http://www.reactphysics3d.com                 *
* Copyright (c) 2010-2018 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(const WorldSettings& worldSettings, Logger* logger, Profiler* profiler)
               : mConfig(worldSettings), mEntityManager(mMemoryManager.getPoolAllocator()),
                 mBodyComponents(mMemoryManager.getBaseAllocator()), mTransformComponents(mMemoryManager.getBaseAllocator()),
                 mProxyShapesComponents(mMemoryManager.getBaseAllocator()), mDynamicsComponents(mMemoryManager.getBaseAllocator()),
                 mCollisionDetection(this, mProxyShapesComponents, mTransformComponents, mDynamicsComponents, mMemoryManager),
                 mBodies(mMemoryManager.getPoolAllocator()), mCurrentBodyId(0), mFreeBodiesIds(mMemoryManager.getPoolAllocator()),
                 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(mBodyComponents.getNbComponents() == 0);
    assert(mTransformComponents.getNbComponents() == 0);
    assert(mProxyShapesComponents.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();

    // Get the next available body ID
    bodyindex bodyID = computeNextAvailableBodyId();

    // Largest index cannot be used (it is used for invalid index)
    assert(bodyID < std::numeric_limits<reactphysics3d::bodyindex>::max());

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

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

    assert(collisionBody != nullptr);

    // Add the components
    BodyComponents::BodyComponent bodyComponent(collisionBody);
    mBodyComponents.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(bodyID) + ": 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->getId()) + ": collision body destroyed");

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

    // Add the body ID to the list of free IDs
    mFreeBodiesIds.add(collisionBody->getId());

    // Reset the contact manifold list of the body
    collisionBody->resetContactManifoldsList();

    mBodyComponents.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));
}

// Return the next available body ID
bodyindex CollisionWorld::computeNextAvailableBodyId() {

    // Compute the body ID
    bodyindex bodyID;
    if (mFreeBodiesIds.size() != 0) {
        bodyID = mFreeBodiesIds[mFreeBodiesIds.size() - 1];
        mFreeBodiesIds.removeAt(mFreeBodiesIds.size() - 1);
    }
    else {
        bodyID = mCurrentBodyId;
        mCurrentBodyId++;
    }

    return bodyID;
}

// Reset all the contact manifolds linked list of each body
void CollisionWorld::resetContactManifoldListsOfBodies() {

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

        // Reset the contact manifold list of the body
        (*it)->resetContactManifoldsList();
    }
}

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

    // TODO : Make sure we notify all the components here ...

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

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

    // For each proxy-shape of the body
    const List<Entity>& proxyShapesEntities = mBodyComponents.getProxyShapes(bodyEntity);
    for (uint i=0; i < proxyShapesEntities.size(); i++) {

        mProxyShapesComponents.setIsEntityDisabled(proxyShapesEntities[i], isDisabled);
    }
}

// Test if the AABBs of two bodies overlap
/**
 * @param body1 Pointer to the first body to test
 * @param body2 Pointer to the second body to test
 * @return True if the AABBs of the two bodies overlap and false otherwise
 */
bool CollisionWorld::testAABBOverlap(const CollisionBody* body1,
                                     const CollisionBody* body2) const {

    // If one of the body is not active, we return no overlap
    if (!body1->isActive() || !body2->isActive()) return false;

    // Compute the AABBs of both bodies
    AABB body1AABB = body1->getAABB();
    AABB body2AABB = body2->getAABB();

    // Return true if the two AABBs overlap
    return body1AABB.testCollision(body2AABB);
}

// Report all the bodies which have an AABB that overlaps with the AABB in parameter
/**
 * @param aabb AABB used to test for overlap
 * @param overlapCallback Pointer to the callback class to report overlap
 * @param categoryMaskBits bits mask used to filter the bodies to test overlap with
 */
void CollisionWorld::testAABBOverlap(const AABB& aabb, OverlapCallback* overlapCallback, unsigned short categoryMaskBits) {
    mCollisionDetection.testAABBOverlap(aabb, overlapCallback, categoryMaskBits);
}

// Return true if two bodies overlap
/**
 * @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 proxy shape
/**
 * @param proxyShape Pointer to a proxy shape
 * @return The AAABB of the proxy shape in world-space
 */
AABB CollisionWorld::getWorldAABB(const ProxyShape* proxyShape) const {

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

   return mCollisionDetection.getWorldAABB(proxyShape);
}