#ifndef SCENENODE_H
#define SCENENODE_H

#include <glm/ext.hpp>
#include "LinearMath/btMotionState.h"
#include <SparrowRenderer/scene.h>

class SceneTree;
class Transform;
class Engine;
class Light;
class btRigidBody;

class SceneNode
{
public:
    class SparrowMotionState : public btMotionState
    {
        SceneNode* m_node;

    public:
        SparrowMotionState(SceneNode* node) : m_node(node) {}

        /// bullet physics uses this getter to access the node's transform
        virtual void getWorldTransform(btTransform& worldTrans ) const;

        /// bullet physics calls this setter to move objects (only on active rigidbodies)
        virtual void setWorldTransform(const btTransform& worldTrans);
    };

    class Script
    {
    public:
        virtual void begin(SceneNode* node) {}
        virtual void update(SceneNode* node) = 0;
        virtual void end(SceneNode* node) {}
    };

private:
    bool m_toDestroy;

    std::vector<SceneNode*> m_nodesToRemove;
    std::vector<SceneNode*> m_nodesToAdd;

    bool m_parentVisible;
    bool m_visible;
    bool m_enabled;

    bool m_transformChanged;

    SceneNode* m_parent;
    SceneTree* m_scene;

    // contains the mesh if this node has one
    GeometryNode m_geometry;

    // contains the light source if the node has one
    Light* m_light;

    // m_transform is the relative transformation matrix of this node
    glm::mat4 m_transform;

    // m_parentTransform is the base transform for this element
    glm::mat4 m_parentTransform;

    // m_script allows to add some game logic in a generic way
    Script* m_script;

    // bullet physics rigidbody
    btRigidBody *m_rigidBody;

    // bullet physics handle for our transformation matrix
    SparrowMotionState m_motionState;

    // node's children
    std::vector<SceneNode*> m_children;

    void updateVisibility(bool visible);
    void setParent(SceneNode* parent);
    void updateLightSource();

public:
    // constructor/destructor
    SceneNode(Script *script = nullptr);
    virtual ~SceneNode();

    // game logic methods
    /// @brief update this method is called every tick by the engine
    virtual void update();

    bool isEnabled() { return m_enabled; }
    void setEnabled(bool isEnabled) { m_enabled = isEnabled; }

    Script* getScript();

    // methods called to access the graphic propeties of the node
    void setMesh(Mesh* mesh) { m_geometry.mesh = mesh; }
    Mesh* getMesh() { return m_geometry.mesh; }
    GeometryNode* getGeometryNode() { return &m_geometry; }

    void setLight(Light* light) { m_light = light; }
    Light* getLight() { return m_light; }

    // scene tree structure methods
    /// @brief setSceneTree this method can be used to change the scene of the node
    virtual void setSceneTree(SceneTree* tree);
    /// @brief getScene this method returns the scene this node is in
    SceneTree* getScene() { return m_scene; }
    /// @brief addChild this method can be used to add a child to a node
    void addChild(SceneNode* node);
    /// @brief removeChild this method can be used to remove a child from a node
    void removeChild(SceneNode* node);
    /// @brief getParent returns the parent of this node, or nullptr if this node is the scene tree root
    virtual SceneNode* getParent() { return m_parent; }
    /// @brief killWhenOrphan this method can be called if you wish to delete this object as soon as it has no parent.
    virtual void destroyWhenOrphan() { m_toDestroy = true; }
    /// @brief getChildren returns a vector containing all this node's children
    const std::vector<SceneNode*> getChildren() { return m_children; }


    // transform methods
    void setTransform(const Transform& t);
    void getTransform(Transform& t);

    void setTransform(const glm::mat4 &transform);
    const glm::mat4& getTransform() { return m_transform; }
    void setParentTransform(const glm::mat4 &transform);
    const glm::mat4& getParentTransform() { return m_parentTransform; }
    void resetTransform();

    // visibility methods
    bool isVisible() { return m_parentVisible && m_visible; }
    void toggleVisibility() { setVisible(!m_visible); }
    void setVisible(bool visible);
    void setParentVisible(bool visible);

    // transformation tools :
    void moveTo(const glm::vec3 &position);
    void translate(const glm::vec3 &vector);
    void lookAt(const glm::vec3 &target, const glm::vec3 &upVector = glm::vec3(0, 1, 0));
    void rotate(float angle, const glm::vec3 &vector);
    void scale(const glm::vec3 &scaleFactor);

    //2D tools:
    void moveTo2D(const glm::vec2 &position) { setTransform(glm::translate(m_transform,glm::vec3(position.x,position.y,0) - glm::vec3(m_transform[3]))); }
    void rotate2D(const glm::vec2 &center, float angle) { setTransform(glm::rotate(m_transform,angle,glm::vec3(0,0,1))); }
    void scale2D(const glm::vec2 scaleFactor) { setTransform(glm::scale(m_transform, glm::vec3(scaleFactor.x,scaleFactor.y,1))); }
    void resize2D(const glm::vec2 oldDimension, glm::vec2 newDimension) { scale2D(glm::vec2(newDimension / oldDimension)); }
    float getDepth();
    void setDepth(float depth);

    // physics methods
    /// @brief getMotionState this is used to synchronize a bullet rigidbody's transform with a GraphicalNode transform
    SparrowMotionState* getMotionState() { return &m_motionState; }
    /// @brief setRigidBody sets a rigidbody to the node
    void setRigidBody(btRigidBody* body);
    btRigidBody* getRigidBody() { return m_rigidBody; }

    /// the scene node clones itself, with no parent
    virtual SceneNode* clone();

    // global engine methods
    Engine& getEngine();
};

#endif // SCENENODE_H
