#include "Base.h"
#include "Node.h"
#include "AudioSource.h"
#include "Scene.h"
#include "Joint.h"
#include "PhysicsRigidBody.h"
#include "PhysicsVehicle.h"
#include "PhysicsVehicleWheel.h"
#include "PhysicsGhostObject.h"
#include "PhysicsCharacter.h"
#include "Game.h"
#include "Terrain.h"

// Node dirty flags
#define NODE_DIRTY_WORLD 1
#define NODE_DIRTY_BOUNDS 2
#define NODE_DIRTY_ALL (NODE_DIRTY_WORLD | NODE_DIRTY_BOUNDS)

namespace gameplay
{

Node::Node(const char* id)
    : _scene(NULL), _firstChild(NULL), _nextSibling(NULL), _prevSibling(NULL), _parent(NULL), _childCount(0),
    _tags(NULL), _camera(NULL), _light(NULL), _model(NULL), _terrain(NULL), _form(NULL), _audioSource(NULL), _particleEmitter(NULL),
    _collisionObject(NULL), _agent(NULL), _dirtyBits(NODE_DIRTY_ALL), _notifyHierarchyChanged(true), _userData(NULL)
{
    if (id)
    {
        _id = id;
    }
}

Node::~Node()
{
    removeAllChildren();

    if (_model)
        _model->setNode(NULL);
//    if (_audioSource)
//        _audioSource->setNode(NULL);
    if (_particleEmitter)
        _particleEmitter->setNode(NULL);
    if (_form)
        _form->setNode(NULL);

    SAFE_RELEASE(_camera);
    SAFE_RELEASE(_light);
    SAFE_RELEASE(_model);
    SAFE_RELEASE(_terrain);
    SAFE_RELEASE(_audioSource);
    SAFE_RELEASE(_particleEmitter);
    SAFE_RELEASE(_form);
    SAFE_DELETE(_collisionObject);
    SAFE_DELETE(_tags);

    setAgent(NULL);

    // Cleanup user data
    if (_userData)
    {
        // Call custom cleanup callback if specified
        if (_userData->cleanupCallback)
            _userData->cleanupCallback(_userData->pointer);
        SAFE_DELETE(_userData);
    }
}

Node* Node::create(const char* id)
{
    return new Node(id);
}

const char* Node::getId() const
{
    return _id.c_str();
}

void Node::setId(const char* id)
{
    if (id)
    {
        _id = id;
    }
}

Node::Type Node::getType() const
{
    return Node::NODE;
}

void Node::addChild(Node* child)
{
    GP_ASSERT(child);

    if (child->_parent == this)
    {
        // This node is already present in our hierarchy
        return;
    }

    child->addRef();

    // If the item belongs to another hierarchy, remove it first.
    if (child->_parent)
    {
        child->_parent->removeChild(child);
    }
    else if (child->_scene)
    {
        child->_scene->removeNode(child);
    }

    // Order is irrelevant, so add to the beginning of the list.
    if (_firstChild)
    {
        _firstChild->_prevSibling = child;
        child->_nextSibling = _firstChild;
        _firstChild = child;
    }
    else
    {
        _firstChild = child;
    }

    child->_parent = this;

    ++_childCount;

    setBoundsDirty();

    if (_notifyHierarchyChanged)
    {
        hierarchyChanged();
    }
}

void Node::removeChild(Node* child)
{
    if (child == NULL || child->_parent != this)
    {
        // The child is not in our hierarchy.
        return;
    }

    // Call remove on the child.
    child->remove();

    SAFE_RELEASE(child);
}

void Node::removeAllChildren()
{
    _notifyHierarchyChanged = false;

    while (_firstChild)
    {
        removeChild(_firstChild);
    }

    _notifyHierarchyChanged = true;
    hierarchyChanged();
}

void Node::remove()
{
    // Re-link our neighbours.
    if (_prevSibling)
    {
        _prevSibling->_nextSibling = _nextSibling;
    }
    if (_nextSibling)
    {
        _nextSibling->_prevSibling = _prevSibling;
    }

    // Update our parent.
    Node* parent = _parent;
    if (parent)
    {
        if (this == parent->_firstChild)
        {
            parent->_firstChild = _nextSibling;
        }

        --parent->_childCount;
    }

    _nextSibling = NULL;
    _prevSibling = NULL;
    _parent = NULL;

    if (parent && parent->_notifyHierarchyChanged)
    {
        parent->hierarchyChanged();
    }
}

Node* Node::getFirstChild() const
{
    return _firstChild;
}

Node* Node::getNextSibling() const
{
    return _nextSibling;
}

Node* Node::getPreviousSibling() const
{
    return _prevSibling;
}

Node* Node::getParent() const
{
    return _parent;
}

bool Node::hasTag(const char* name) const
{
    GP_ASSERT(name);

    return (_tags ? _tags->find(name) != _tags->end() : false);
}

const char* Node::getTag(const char* name) const
{
    GP_ASSERT(name);

    if (!_tags)
        return NULL;

    std::map<std::string, std::string>::const_iterator itr = _tags->find(name);
    return (itr == _tags->end() ? NULL : itr->second.c_str());
}

void Node::setTag(const char* name, const char* value)
{
    GP_ASSERT(name);

    if (value == NULL)
    {
        // Removing tag
        if (_tags)
        {
            _tags->erase(name);
            if (_tags->size() == 0)
                SAFE_DELETE(_tags);
        }
    }
    else
    {
        // Setting tag
        if (_tags == NULL)
            _tags = new std::map<std::string, std::string>();

        (*_tags)[name] = value;
    }
}

void* Node::getUserPointer() const
{
    return (_userData ? _userData->pointer : NULL);
}

void Node::setUserPointer(void* pointer, void (*cleanupCallback)(void*))
{
    // If existing user pointer is being changed, call cleanup function to free previous pointer
    if (_userData && _userData->pointer && _userData->cleanupCallback && pointer != _userData->pointer)
    {
        _userData->cleanupCallback(_userData->pointer);
    }

    if (pointer)
    {
        // Assign user pointer
        if (_userData == NULL)
            _userData = new UserData();

        _userData->pointer = pointer;
        _userData->cleanupCallback = cleanupCallback;
    }
    else
    {
        // Clear user pointer
        SAFE_DELETE(_userData);
    }
}

unsigned int Node::getChildCount() const
{
    return _childCount;
}

Node* Node::findNode(const char* id, bool recursive, bool exactMatch) const
{
    GP_ASSERT(id);

    // If the node has a model with a mesh skin, search the skin's hierarchy as well.
    Node* rootNode = NULL;
    if (_model != NULL && _model->getSkin() != NULL && (rootNode = _model->getSkin()->_rootNode) != NULL)
    {
        if ((exactMatch && rootNode->_id == id) || (!exactMatch && rootNode->_id.find(id) == 0))
            return rootNode;
        
        Node* match = rootNode->findNode(id, true, exactMatch);
        if (match)
        {
            return match;
        }
    }
    
    // Search immediate children first.
    for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
    {
        // Does this child's ID match?
        if ((exactMatch && child->_id == id) || (!exactMatch && child->_id.find(id) == 0))
        {
            return child;
        }
    }

    // Recurse.
    if (recursive)
    {
        for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
        {
            Node* match = child->findNode(id, true, exactMatch);
            if (match)
            {
                return match;
            }
        }
    }

    return NULL;
}   

unsigned int Node::findNodes(const char* id, std::vector<Node*>& nodes, bool recursive, bool exactMatch) const
{
    GP_ASSERT(id);
    
    unsigned int count = 0;

    // If the node has a model with a mesh skin, search the skin's hierarchy as well.
    Node* rootNode = NULL;
    if (_model != NULL && _model->getSkin() != NULL && (rootNode = _model->getSkin()->_rootNode) != NULL)
    {
        if ((exactMatch && rootNode->_id == id) || (!exactMatch && rootNode->_id.find(id) == 0))
        {
            nodes.push_back(rootNode);
            ++count;
        }
        count += rootNode->findNodes(id, nodes, true, exactMatch);
    }

    // Search immediate children first.
    for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
    {
        // Does this child's ID match?
        if ((exactMatch && child->_id == id) || (!exactMatch && child->_id.find(id) == 0))
        {
            nodes.push_back(child);
            ++count;
        }
    }

    // Recurse.
    if (recursive)
    {
        for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
        {
            count += child->findNodes(id, nodes, true, exactMatch);
        }
    }

    return count;
}

Scene* Node::getScene() const
{
    if (_scene)
        return _scene;

    // Search our parent for the scene
    if (_parent)
    {
        Scene* scene = _parent->getScene();
        if (scene)
            return scene;
    }

    return NULL;
}

Node* Node::getRootNode() const
{
    Node* n = const_cast<Node*>(this);
    while (n->getParent())
    {
        n = n->getParent();
    }
    return n;
}

bool Node::isStatic() const
{
    return (_collisionObject && _collisionObject->isStatic());
}

const Matrix& Node::getWorldMatrix() const
{
    if (_dirtyBits & NODE_DIRTY_WORLD)
    {
        // Clear our dirty flag immediately to prevent this block from being entered if our
        // parent calls our getWorldMatrix() method as a result of the following calculations.
        _dirtyBits &= ~NODE_DIRTY_WORLD;

        if (!isStatic())
        {
            // If we have a parent, multiply our parent world transform by our local
            // transform to obtain our final resolved world transform.
            Node* parent = getParent();
            if (parent && (!_collisionObject || _collisionObject->isKinematic()))
            {
                Matrix::multiply(parent->getWorldMatrix(), getMatrix(), &_world);
            }
            else
            {
                _world = getMatrix();
            }

            // Our world matrix was just updated, so call getWorldMatrix() on all child nodes
            // to force their resolved world matrices to be updated.
            for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
            {
                child->getWorldMatrix();
            }
        }
    }

    return _world;
}

const Matrix& Node::getWorldViewMatrix() const
{
    static Matrix worldView;

    Matrix::multiply(getViewMatrix(), getWorldMatrix(), &worldView);

    return worldView;
}

const Matrix& Node::getInverseTransposeWorldViewMatrix() const
{
    static Matrix invTransWorldView;
    Matrix::multiply(getViewMatrix(), getWorldMatrix(), &invTransWorldView);
    invTransWorldView.invert();
    invTransWorldView.transpose();
    return invTransWorldView;
}

const Matrix& Node::getInverseTransposeWorldMatrix() const
{
    static Matrix invTransWorld;
    invTransWorld = getWorldMatrix();
    invTransWorld.invert();
    invTransWorld.transpose();
    return invTransWorld;
}

const Matrix& Node::getViewMatrix() const
{
    Scene* scene = getScene();
    Camera* camera = scene ? scene->getActiveCamera() : NULL;
    if (camera)
    {
        return camera->getViewMatrix();
    }
    else
    {
        return Matrix::identity();
    }
}

const Matrix& Node::getInverseViewMatrix() const
{
    Scene* scene = getScene();
    Camera* camera = scene ? scene->getActiveCamera() : NULL;
    if (camera)
    {
        return camera->getInverseViewMatrix();
    }
    else
    {
        return Matrix::identity();
    }
}

const Matrix& Node::getProjectionMatrix() const
{
    Scene* scene = getScene();
    Camera* camera = scene ? scene->getActiveCamera() : NULL;
    if (camera)
    {
        return camera->getProjectionMatrix();
    }
    else
    {
        return Matrix::identity();
    }
}

const Matrix& Node::getViewProjectionMatrix() const
{
    Scene* scene = getScene();
    Camera* camera = scene ? scene->getActiveCamera() : NULL;
    if (camera)
    {
        return camera->getViewProjectionMatrix();
    }
    else
    {
        return Matrix::identity();
    }
}

const Matrix& Node::getInverseViewProjectionMatrix() const
{
    Scene* scene = getScene();
    Camera* camera = scene ? scene->getActiveCamera() : NULL;
    if (camera)
    {
        return camera->getInverseViewProjectionMatrix();
    }

    return Matrix::identity();
}

const Matrix& Node::getWorldViewProjectionMatrix() const
{
    static Matrix worldViewProj;

    // Always re-calculate worldViewProjection matrix since it's extremely difficult
    // to track whether the camera has changed (it may frequently change every frame).
    Matrix::multiply(getViewProjectionMatrix(), getWorldMatrix(), &worldViewProj);

    return worldViewProj;
}

Vector3 Node::getTranslationWorld() const
{
    Vector3 translation;
    getWorldMatrix().getTranslation(&translation);
    return translation;
}

Vector3 Node::getTranslationView() const
{
    Vector3 translation;
    getWorldMatrix().getTranslation(&translation);
    getViewMatrix().transformPoint(&translation);
    return translation;
}

Vector3 Node::getForwardVectorWorld() const
{
    Vector3 vector;
    getWorldMatrix().getForwardVector(&vector);
    return vector;
}

Vector3 Node::getForwardVectorView() const
{
    Vector3 vector;
    getWorldMatrix().getForwardVector(&vector);
    getViewMatrix().transformVector(&vector);
    return vector;
}

Vector3 Node::getRightVectorWorld() const
{
    Vector3 vector;
    getWorldMatrix().getRightVector(&vector);
    return vector;
}

Vector3 Node::getUpVectorWorld() const
{
    Vector3 vector;
    getWorldMatrix().getUpVector(&vector);
    return vector;
}

Vector3 Node::getActiveCameraTranslationWorld() const
{
    Scene* scene = getScene();
    if (scene)
    {
        Camera* camera = scene->getActiveCamera();
        if (camera)
        {
            Node* cameraNode = camera->getNode();
            if (cameraNode)
            {
                return cameraNode->getTranslationWorld();
            }
        }
    }

    return Vector3::zero();
}

Vector3 Node::getActiveCameraTranslationView() const
{
    Scene* scene = getScene();
    if (scene)
    {
        Camera* camera = scene->getActiveCamera();
        if (camera)
        {
            Node* cameraNode = camera->getNode();
            if (cameraNode)
            {
                return cameraNode->getTranslationView();
            }
        }
    }

    return Vector3::zero();
}

void Node::hierarchyChanged()
{
    // When our hierarchy changes our world transform is affected, so we must dirty it.
    transformChanged();
}

void Node::transformChanged()
{
    // Our local transform was changed, so mark our world matrices dirty.
    _dirtyBits |= NODE_DIRTY_WORLD | NODE_DIRTY_BOUNDS;

    // Notify our children that their transform has also changed (since transforms are inherited).
    for (Node* n = getFirstChild(); n != NULL; n = n->getNextSibling())
    {
        if (Transform::isTransformChangedSuspended())
        {
            // If the DIRTY_NOTIFY bit is not set
            if (!n->isDirty(Transform::DIRTY_NOTIFY))
            {
                n->transformChanged();
                suspendTransformChange(n);
            }
        }
        else
        {
            n->transformChanged();
        }
    }

    Transform::transformChanged();
}

void Node::setBoundsDirty()
{
    // Mark ourself and our parent nodes as dirty
    _dirtyBits |= NODE_DIRTY_BOUNDS;

    // Mark our parent bounds as dirty as well
    if (_parent)
        _parent->setBoundsDirty();
}

Animation* Node::getAnimation(const char* id) const
{
    Animation* animation = ((AnimationTarget*)this)->getAnimation(id);
    if (animation)
        return animation;
    
    // See if this node has a model, then drill down.
    Model* model = this->getModel();
    if (model)
    {
        // Check to see if there's any animations with the ID on the joints.
        MeshSkin* skin = model->getSkin();
        if (skin)
        {
            Node* rootNode = skin->_rootNode;
            if (rootNode)
            {
                animation = rootNode->getAnimation(id);
                if (animation)
                    return animation;
            }
        }

        // Check to see if any of the model's material parameter's has an animation
        // with the given ID.
        Material* material = model->getMaterial();
        if (material)
        {
            // How to access material parameters? hidden on the Material::RenderState.
            std::vector<MaterialParameter*>::iterator itr = material->_parameters.begin();
            for (; itr != material->_parameters.end(); itr++)
            {
                GP_ASSERT(*itr);
                animation = ((MaterialParameter*)(*itr))->getAnimation(id);
                if (animation)
                    return animation;
            }
        }
    }

    // look through form for animations.
    Form* form = this->getForm();
    if (form)
    {
        animation = form->getAnimation(id);
        if (animation)
            return animation;
    }

    // Look through this node's children for an animation with the specified ID.
    for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
    {
        animation = child->getAnimation(id);
        if (animation)
            return animation;
    }
    
    return NULL;
}

Camera* Node::getCamera() const
{
    return _camera;
}

void Node::setCamera(Camera* camera)
{
    if (_camera != camera)
    {
        if (_camera)
        {
            _camera->setNode(NULL);
            SAFE_RELEASE(_camera);
        }

        _camera = camera;

        if (_camera)
        {
            _camera->addRef();
            _camera->setNode(this);
        }
    }
}

Light* Node::getLight() const
{
    return _light;
}

void Node::setLight(Light* light)
{
    if (_light != light)
    {
        if (_light)
        {
            _light->setNode(NULL);
            SAFE_RELEASE(_light);
        }

        _light = light;

        if (_light)
        {
            _light->addRef();
            _light->setNode(this);
        }

        setBoundsDirty();
    }
}

Model* Node::getModel() const
{
    return _model;
}

void Node::setModel(Model* model)
{
    if (_model != model)
    {
        if (_model)
        {
            _model->setNode(NULL);
            SAFE_RELEASE(_model);
        }

        _model = model;

        if (_model)
        {
            _model->addRef();
            _model->setNode(this);
        }
    }
}

Terrain* Node::getTerrain() const
{
    return _terrain;
}

void Node::setTerrain(Terrain* terrain)
{
    if (_terrain != terrain)
    {
        if (_terrain)
        {
            _terrain->setNode(NULL);
            SAFE_RELEASE(_terrain);
        }

        _terrain = terrain;

        if (_terrain)
        {
            _terrain->addRef();
            _terrain->setNode(this);
        }

        setBoundsDirty();
    }
}

Form* Node::getForm() const
{
    return _form;
}

void Node::setForm(Form* form)
{
    if (_form != form)
    {
        if (_form)
        {
            _form->setNode(NULL);
            SAFE_RELEASE(_form);
        }

        _form = form;

        if (_form)
        {
            _form->addRef();
            _form->setNode(this);
        }
    }
}

const BoundingSphere& Node::getBoundingSphere() const
{
    if (_dirtyBits & NODE_DIRTY_BOUNDS)
    {
        _dirtyBits &= ~NODE_DIRTY_BOUNDS;

        const Matrix& worldMatrix = getWorldMatrix();

        // Start with our local bounding sphere
        // TODO: Incorporate bounds from entities other than mesh (i.e. emitters, audiosource, etc)
        bool empty = true;
        if (_terrain)
        {
            _bounds.set(_terrain->getBoundingBox());
            empty = false;
        }
        if (_model && _model->getMesh())
        {
            if (empty)
            {
                _bounds.set(_model->getMesh()->getBoundingSphere());
                empty = false;
            }
            else
            {
                _bounds.merge(_model->getMesh()->getBoundingSphere());
            }
        }
        if (_light)
        {
            switch (_light->getLightType())
            {
            case Light::POINT:
                if (empty)
                {
                    _bounds.set(Vector3::zero(), _light->getRange());
                    empty = false;
                }
                else
                {
                    _bounds.merge(BoundingSphere(Vector3::zero(), _light->getRange()));
                }
                break;
            case Light::SPOT:
                // TODO: Implement spot light bounds
                break;
            }
        }
        if (empty)
        {
            // Empty bounding sphere, set the world translation with zero radius
            worldMatrix.getTranslation(&_bounds.center);
            _bounds.radius = 0;
        }

        // Transform the sphere (if not empty) into world space.
        if (!empty)
        {
            bool applyWorldTransform = true;
            if (_model && _model->getSkin())
            {
                // Special case: If the root joint of our mesh skin is parented by any nodes, 
                // multiply the world matrix of the root joint's parent by this node's
                // world matrix. This computes a final world matrix used for transforming this
                // node's bounding volume. This allows us to store a much smaller bounding
                // volume approximation than would otherwise be possible for skinned meshes,
                // since joint parent nodes that are not in the matrix palette do not need to
                // be considered as directly transforming vertices on the GPU (they can instead
                // be applied directly to the bounding volume transformation below).
                GP_ASSERT(_model->getSkin()->getRootJoint());
                Node* jointParent = _model->getSkin()->getRootJoint()->getParent();
                if (jointParent)
                {
                    // TODO: Should we protect against the case where joints are nested directly
                    // in the node hierachy of the model (this is normally not the case)?
                    Matrix boundsMatrix;
                    Matrix::multiply(getWorldMatrix(), jointParent->getWorldMatrix(), &boundsMatrix);
                    _bounds.transform(boundsMatrix);
                    applyWorldTransform = false;
                }
            }
            if (applyWorldTransform)
            {
                _bounds.transform(getWorldMatrix());
            }
        }

        // Merge this world-space bounding sphere with our childrens' bounding volumes.
        for (Node* n = getFirstChild(); n != NULL; n = n->getNextSibling())
        {
            const BoundingSphere& childSphere = n->getBoundingSphere();
            if (!childSphere.isEmpty())
            {
                if (empty)
                {
                    _bounds.set(childSphere);
                    empty = false;
                }
                else
                {
                    _bounds.merge(childSphere);
                }
            }
        }
    }

    return _bounds;
}

Node* Node::clone() const
{
    NodeCloneContext context;
    return cloneRecursive(context);
}

Node* Node::cloneSingleNode(NodeCloneContext &context) const
{
    Node* copy = Node::create(getId());
    context.registerClonedNode(this, copy);
    cloneInto(copy, context);
    return copy;
}

Node* Node::cloneRecursive(NodeCloneContext &context) const
{
    Node* copy = cloneSingleNode(context);
    GP_ASSERT(copy);

    // Find our current last child
    Node* lastChild = NULL;
    for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
    {
        lastChild = child;
    }

    // Loop through the nodes backwards because addChild adds the node to the front.
    for (Node* child = lastChild; child != NULL; child = child->getPreviousSibling())
    {
        Node* childCopy = child->cloneRecursive(context);
        GP_ASSERT(childCopy);
        copy->addChild(childCopy);
        childCopy->release();
    }

    return copy;
}

void Node::cloneInto(Node* node, NodeCloneContext &context) const
{
    GP_ASSERT(node);
    Transform::cloneInto(node, context);

    // TODO: Clone the rest of the node data.

    if (Camera* camera = getCamera())
    {
        Camera* cameraClone = camera->clone(context);
        node->setCamera(cameraClone);
        cameraClone->release();
    }
    if (Light* light = getLight())
    {
        Light* lightClone = light->clone(context);
        node->setLight(lightClone);
        lightClone->release();
    }
//    if (AudioSource* audio = getAudioSource())
//    {
//        AudioSource* audioClone = audio->clone(context);
//        node->setAudioSource(audioClone);
//        audioClone->release();
//    }
    if (Model* model = getModel())
    {
        Model* modelClone = model->clone(context);
        node->setModel(modelClone);
        modelClone->release();
    }
    node->_world = _world;
    node->_bounds = _bounds;

    // Note: Do not clone _userData - we can't make any assumptions about its content and how it's managed,
    // so it's the caller's responsibility to clone user data if needed.

    if (_tags)
    {
        node->_tags = new std::map<std::string, std::string>(_tags->begin(), _tags->end());
    }
}

AudioSource* Node::getAudioSource() const
{
    return _audioSource;
}

void Node::setAudioSource(AudioSource* audio)
{
    if (_audioSource != audio)
    {
        if (_audioSource)
        {
//            _audioSource->setNode(NULL);
            SAFE_RELEASE(_audioSource);
        }
        
        _audioSource = audio;

        if (_audioSource)
        {
//            _audioSource->addRef();
//            _audioSource->setNode(this);
        }
    }
}

ParticleEmitter* Node::getParticleEmitter() const
{
    return _particleEmitter;
}

void Node::setParticleEmitter(ParticleEmitter* emitter)
{
    if (_particleEmitter != emitter)
    {
        if (_particleEmitter)
        {
            _particleEmitter->setNode(NULL);
            SAFE_RELEASE(_particleEmitter);
        }
        
        _particleEmitter = emitter;

        if (_particleEmitter)
        {
            _particleEmitter->addRef();
            _particleEmitter->setNode(this);
        }
    }
}

PhysicsCollisionObject* Node::getCollisionObject() const
{
    return _collisionObject;
}

PhysicsCollisionObject* Node::setCollisionObject(PhysicsCollisionObject::Type type, const PhysicsCollisionShape::Definition& shape, PhysicsRigidBody::Parameters* rigidBodyParameters)
{
    SAFE_DELETE(_collisionObject);

    switch (type)
    {
    case PhysicsCollisionObject::RIGID_BODY:
        {
            _collisionObject = new PhysicsRigidBody(this, shape, rigidBodyParameters ? *rigidBodyParameters : PhysicsRigidBody::Parameters());
        }
        break;

    case PhysicsCollisionObject::GHOST_OBJECT:
        {
            _collisionObject = new PhysicsGhostObject(this, shape);
        }
        break;

    case PhysicsCollisionObject::CHARACTER:
        {
            _collisionObject = new PhysicsCharacter(this, shape, rigidBodyParameters ? rigidBodyParameters->mass : 1.0f);
        }
        break;

    case PhysicsCollisionObject::VEHICLE:
        {
            _collisionObject = new PhysicsVehicle(this, shape, rigidBodyParameters ? *rigidBodyParameters : PhysicsRigidBody::Parameters());
        }
        break;

    case PhysicsCollisionObject::VEHICLE_WHEEL:
        {
            //
            // PhysicsVehicleWheel is special because this call will traverse up the scene graph for the
            // first ancestor node that is shared with another node of collision type VEHICLE, and then
            // proceed to add itself as a wheel onto that vehicle. This is by design, and allows the
            // visual scene hierarchy to be the sole representation of the relationship between physics
            // objects rather than forcing that upon the otherwise-flat ".physics" (properties) file.
            //
            // IMPORTANT: The VEHICLE must come before the VEHICLE_WHEEL in the ".scene" (properties) file!
            //
            _collisionObject = new PhysicsVehicleWheel(this, shape, rigidBodyParameters ? *rigidBodyParameters : PhysicsRigidBody::Parameters());
        }
        break;

    case PhysicsCollisionObject::NONE:
        break;  // Already deleted, Just don't add a new collision object back.
    }

    return _collisionObject;
}

PhysicsCollisionObject* Node::setCollisionObject(const char* url)
{
    // Load the collision object properties from file.
    Properties* properties = Properties::create(url);
    if (properties == NULL)
    {
        GP_ERROR("Failed to load collision object file: %s", url);
        return NULL;
    }

    PhysicsCollisionObject* collisionObject = setCollisionObject((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace());
    SAFE_DELETE(properties);

    return collisionObject;
}

PhysicsCollisionObject* Node::setCollisionObject(Properties* properties)
{
    SAFE_DELETE(_collisionObject);

    // Check if the properties is valid.
    if (!properties || !(strcmp(properties->getNamespace(), "collisionObject") == 0))
    {
        GP_ERROR("Failed to load collision object from properties object: must be non-null object and have namespace equal to 'collisionObject'.");
        return NULL;
    }

    if (const char* type = properties->getString("type"))
    {
        if (strcmp(type, "CHARACTER") == 0)
        {
            _collisionObject = PhysicsCharacter::create(this, properties);
        }
        else if (strcmp(type, "GHOST_OBJECT") == 0)
        {
            _collisionObject = PhysicsGhostObject::create(this, properties);
        }
        else if (strcmp(type, "RIGID_BODY") == 0)
        {
            _collisionObject = PhysicsRigidBody::create(this, properties);
        }
        else if (strcmp(type, "VEHICLE") == 0)
        {
            _collisionObject = PhysicsVehicle::create(this, properties);
        }
        else if (strcmp(type, "VEHICLE_WHEEL") == 0)
        {
            //
            // PhysicsVehicleWheel is special because this call will traverse up the scene graph for the
            // first ancestor node that is shared with another node of collision type VEHICLE, and then
            // proceed to add itself as a wheel onto that vehicle. This is by design, and allows the
            // visual scene hierarchy to be the sole representation of the relationship between physics
            // objects rather than forcing that upon the otherwise-flat ".physics" (properties) file.
            //
            // IMPORTANT: The VEHICLE must come before the VEHICLE_WHEEL in the ".scene" (properties) file!
            //
            _collisionObject = PhysicsVehicleWheel::create(this, properties);
        }
        else
        {
            GP_ERROR("Unsupported collision object type '%s'.", type);
            return NULL;
        }
    }
    else
    {
        GP_ERROR("Failed to load collision object from properties object; required attribute 'type' is missing.");
        return NULL;
    }

    return _collisionObject;
}

unsigned int Node::getNumAdvertisedDescendants() const
{
    return (unsigned int)_advertisedDescendants.size();
}

Node* Node::getAdvertisedDescendant(unsigned int i) const
{
    return _advertisedDescendants.at(i);
}

void Node::addAdvertisedDescendant(Node* node)
{
    _advertisedDescendants.push_back(node);
}

AIAgent* Node::getAgent() const
{
    return _agent;
}

void Node::setAgent(AIAgent* agent)
{
    if (agent != _agent)
    {
        if (_agent)
        {
            Game::getInstance()->getAIController()->removeAgent(_agent);
            _agent->_node = NULL;
            SAFE_RELEASE(_agent);
        }

        _agent = agent;

        if (_agent)
        {
            _agent->addRef();
            _agent->_node = this;
            Game::getInstance()->getAIController()->addAgent(_agent);
        }
    }
}

NodeCloneContext::NodeCloneContext()
{
}

NodeCloneContext::~NodeCloneContext()
{
}

Animation* NodeCloneContext::findClonedAnimation(const Animation* animation)
{
    GP_ASSERT(animation);

    std::map<const Animation*, Animation*>::iterator it = _clonedAnimations.find(animation);
    return it != _clonedAnimations.end() ? it->second : NULL;
}

void NodeCloneContext::registerClonedAnimation(const Animation* original, Animation* clone)
{
    GP_ASSERT(original);
    GP_ASSERT(clone);

    _clonedAnimations[original] = clone;
}

Node* NodeCloneContext::findClonedNode(const Node* node)
{
    GP_ASSERT(node);

    std::map<const Node*, Node*>::iterator it = _clonedNodes.find(node);
    return it != _clonedNodes.end() ? it->second : NULL;
}

void NodeCloneContext::registerClonedNode(const Node* original, Node* clone)
{
    GP_ASSERT(original);
    GP_ASSERT(clone);

    _clonedNodes[original] = clone;
}

}
