#ifndef NODE_INCLUDED
#define NODE_INCLUDED

#include <common/objectbase.hpp>
#include <utils/math.hpp>

#include <memory>
#include <vector>

using objectbase_ptr = std::shared_ptr<ObjectBase>;

class Node;

using node_ptr = std::shared_ptr<Node>;
using node_weak_ptr = std::weak_ptr<Node>;

class Node : public std::enable_shared_from_this<Node>
{
    glm::vec3 position_vec;
    glm::quat rotation_quat;
    glm::vec3 scale_vec;

    mutable std::unique_ptr<glm::mat4> model_world = nullptr;

    std::vector<node_ptr> children;
    node_weak_ptr parent = std::weak_ptr<Node>();
    
    std::vector<objectbase_ptr> objects;

        /* HELPER FUNCTIONS */
    static glm::vec3 get_position_from_mat(glm::mat4 mat);
    static glm::vec3 get_scale_from_mat(glm::mat4 mat);
    static glm::quat get_rotation_from_mat(glm::mat4 mat);

    static void decompose_model_mat(const glm::mat4& mat, glm::vec3& position, glm::quat& rotation, glm::vec3& scale);

    Node(const Node &other);
    Node(const Node &&other);

    Node &operator=(const Node &other);
    Node &operator=(const Node &&other);

    Node(glm::vec3 _position_vec = glm::vec3(0.0f),
          glm::vec3 rotation_vec = glm::vec3(0.0f),
          glm::vec3 _scale_vec = glm::vec3(1.0f));
    Node(glm::vec3 _position_vec,
          glm::quat _rotation_quat,
          glm::vec3 _scale_vec = glm::vec3(1.0f));

public:
    
    ~Node() = default;
    
    static node_ptr create(glm::vec3 _position_vec = glm::vec3(0.0f), 
                                         glm::vec3 rotation_vec = glm::vec3(0.0f),
                                         glm::vec3 _scale_vec = glm::vec3(1.0f));

    static node_ptr create(glm::vec3 _position_vec,
                                         glm::quat _rotation_quat,
                                         glm::vec3 _scale_vec = glm::vec3(1.0f));

    static node_ptr create(const glm::mat4 &matrix);

    static node_ptr create(const glm::mat4 &&matrix);

    /* Creates a copy of node. Only positional attributes get copied. Objects and hierarchy do not. */
    static node_ptr copy_frame(node_ptr other_node);
    /* NOTE: Can be potentially dangerous */
    static node_ptr copy_shallow(node_ptr other_node);
    static node_ptr copy_deep(node_ptr other_node);

    /* GETTERS */
        /* LOCAL */
    glm::vec3 getPositionLocal() const;
    glm::quat getRotationLocal() const;
    glm::vec3 getScaleLocal() const;

    glm::vec3 getRotationAxisXLocal() const;
    glm::vec3 getRotationAxisYLocal() const;
    glm::vec3 getRotationAxisZLocal() const;

    glm::mat4 getTranslationMatLocal() const;
    glm::mat4 getRotationMatLocal() const;
    glm::mat4 getInverseRotationMatLocal() const;
    glm::mat4 getScaleMatLocal() const;

    glm::mat4 getModelMatLocal() const;
    glm::mat4 getInverseModelMatLocal() const;

        /* WORLD */
    glm::vec3 getPositionWorld() const;
    glm::quat getRotationWorld() const;
    glm::vec3 getScaleWorld() const;

    glm::vec3 getRotationAxisXWorld() const;
    glm::vec3 getRotationAxisYWorld() const;
    glm::vec3 getRotationAxisZWorld() const;

    glm::mat4 getTranslationMatWorld() const;
    glm::mat4 getRotationMatWorld() const;
    glm::mat4 getInverseRotationMatWorld() const;
    glm::mat4 getScaleMatWorld() const;

    glm::mat4 getModelMatWorld() const;
    glm::mat4 getInverseModelMatWorld() const;

    glm::mat4 getParentModelMatWorld() const;
    glm::mat4 getParentInverseModelMatWorld() const;
    glm::mat4 getParentRotationMatWorld() const;
    glm::mat4 getParentInverseRotationMatWorld() const;

    /* SETTERS */

    void setPosVec(glm::vec3 _position_vec);
    void translatePosVec(glm::vec3 direction);
    void translatePosVec(glm::mat4 matrix);
    void setRotationQuat(glm::vec3 rotation_vec);
    void setRotationQuat(glm::quat _rotation_quat);
    void setRotationQuat(const glm::mat4 &rotation_matrix);
    void rotateRotationQuat(glm::vec3 added_rotation);
    void rotateRotationQuat(glm::quat added_rotation);
    void setScale(glm::vec3 _scale_vec);

    void setFrame(glm::vec3 _position_vec, glm::vec3 rotation_vec, glm::vec3 _scale_vec);
    void setFrame(glm::vec3 _position_vec, glm::quat _rotation_quat, glm::vec3 _scale_vec);

    void translatePosVecWorld(glm::vec3 direction);

    void normalizeQuat();

    /* WORLD MODEL MATRIX SETTERS */

    void setModelMatWorld(glm::mat4 _model_world) const;
    void invalidateModelMatWorld() const;

    /* NODE HIERARCHY METHODS */

    bool isParentOf(node_ptr other_node);
    node_weak_ptr getRootParent();
    node_weak_ptr getParent() const;

    std::vector<node_ptr> &getChildren();
    void addChild(node_ptr child, bool transform_to_parent_base = false);
    void removeChild(std::vector<node_ptr>::iterator it);
    void removeChild(node_ptr child);
    void setChildren(std::vector<node_ptr> &_children, bool transform_to_parent_base = false);
    void removeChildren(const std::vector<node_ptr> &unwanted_children);
    void clearChildren();
    void extendChildren(const std::vector<node_ptr> &extra_children, bool transform_to_parent_base = false);

    void transferChild(node_ptr child, node_weak_ptr new_parent, bool transform_to_parent_base = false);

    const std::vector<objectbase_ptr> &getObjects() const;
    std::vector<objectbase_ptr> &getObjects();
    void setObjects(const std::vector<objectbase_ptr> &_objects);

    void addObject(ObjectBase obj);
    void addObject(objectbase_ptr obj);

protected:

    void setParent(node_weak_ptr _parent, bool recalculate_basis);
    void removeParent();
};

#endif