#ifndef SCENE_H
#define SCENE_H

#define INVALID_ID -1

#include <sys/types.h>
#include <set>
#include <list>
#include <vector>
#include <map>

#include "UpdateCallback.h"
#include "GlmToolkit.h"

// Forward declare classes referenced
class Shader;
class Visitor;
class Group;

/**
 * @brief The Node class is the base virtual class for all Node types
 *
 * Every Node is given a unique id at instanciation
 *
 * Every Node has geometric operations for translation,
 * scale and rotation. The update() function computes the
 * transform_ matrix from these components.
 *
 * draw() shall be defined by the subclass.
 * The visible flag can be used to show/hide a Node.
 * To apply geometrical transformation, draw() can multiplying the
 * modelview by the transform_ matrix.
 *
 * init() shall be called on the first call of draw():
 *     if ( !initialized() )
 *        init();
 *
 * accept() allows visitors to parse the graph.
 */
class Node {

    uint64_t  id_;
    bool      initialized_;

public:
    Node ();
    virtual ~Node ();

    // unique identifyer generated at instanciation
    inline uint64_t id () const { return id_; }

    // must initialize the node before draw
    virtual void init () { initialized_ = true; }
    virtual bool initialized () { return initialized_; }

    // pure virtual draw : to be instanciated to define node behavior
    virtual void draw (glm::mat4 modelview, glm::mat4 projection) = 0;

    // update every frame
    virtual void update (float);

    // accept all kind of visitors
    virtual void accept (Visitor& v);

    void copyTransform (const Node *other);

    // public members, to manipulate with care
    bool      visible_;
    uint      refcount_;
    glm::mat4 transform_;
    glm::vec3 scale_, rotation_, translation_, crop_;

    // animation update callbacks
    // list of callbacks to call at each update
    std::list<UpdateCallback *> update_callbacks_;
    void clearCallbacks();
};



/**
 * @brief The Primitive class is a leaf Node that can be rendered
 *
 * Primitive generates a STATIC vertex array object from
 * a list of points, colors and texture coordinates.
 * The vertex array is deleted in the Primitive destructor.
 *
 * Primitive class itself does not define any geometry: subclasses
 * should fill the points, colors and texture coordinates
 * in their constructor.
 *
 * Primitive can be given a shader th0at is used during draw.
 *
 */
class Primitive : public Node {

public:
    Primitive(Shader *s = nullptr) : Node(), shader_(s), vao_(0), drawMode_(0), drawCount_(0) {}
    virtual ~Primitive();

    virtual void init () override;
    virtual void accept (Visitor& v) override;
    virtual void draw (glm::mat4 modelview, glm::mat4 projection) override;

    inline Shader *shader () const { return shader_; }
    void replaceShader (Shader* newshader);

    GlmToolkit::AxisAlignedBoundingBox bbox() const { return bbox_; }

protected:
    Shader*   shader_;
    uint vao_, drawMode_, drawCount_;
    std::vector<glm::vec3>     points_;
    std::vector<glm::vec4>     colors_;
    std::vector<glm::vec2>     texCoords_;
    std::vector<uint>          indices_;
    GlmToolkit::AxisAlignedBoundingBox     bbox_;
};

//
// Other Nodes establish hierarchy with a set of nodes
//
struct z_comparator
{
    inline bool operator () (const Node *a, const Node *b) const
    {
        //Sort Furthest to Closest
        return (a && b && a->translation_.z < b->translation_.z);
    }
};
typedef std::multiset<Node*, z_comparator> NodeSet;

//typedef std::list<Node*> NodeSet;

struct hasId: public std::unary_function<Node*, bool>
{
    inline bool operator()(const Node* e) const
    {
       return (e && e->id() == _id);
    }
    hasId(uint64_t id) : _id(id) { }
private:
    uint64_t _id;
};


/**
 * @brief The Group class contains a list of pointers to Nodes.
 *
 * A Group defines the hierarchy in the scene graph.
 *
 * The list of Nodes* is a NodeSet, a depth-sorted set
 * accepting multiple nodes at the same depth (multiset)
 *
 * update() will update all children
 * draw() will draw all children
 *
 * When a group is deleted, the children are NOT deleted.
 */
class Group : public Node {

public:
    Group() : Node() {}
    virtual ~Group();

    // Node interface
    virtual void update (float dt) override;
    virtual void accept (Visitor& v) override;
    virtual void draw (glm::mat4 modelview, glm::mat4 projection) override;

    // container
    void clear();
    void attach  (Node *child);
    void detach (Node *child);
    inline uint numChildren ()  const { return children_.size(); }

    // Group specific access to its Nodes
    void sort();
    NodeSet::iterator begin();
    NodeSet::iterator end();
    Node *front();
    Node *back();

protected:
    NodeSet children_;

};

/**
 * @brief The Switch class selectively updates & draws ONE selected child
 *
 * update() will update only the active child
 * draw() will draw only the active child
 *
 */
class Switch : public Node {

public:
    Switch() : Node(), active_(0) {}
    virtual ~Switch();

    // Node interface
    virtual void update (float dt) override;
    virtual void accept (Visitor& v) override;
    virtual void draw (glm::mat4 modelview, glm::mat4 projection) override;

    // container
    void clear();
    uint attach  (Node *child);
    void detatch (Node *child);
    inline uint numChildren ()  const { return children_.size(); }

    // Switch specific access to its Nodes
    void setActive (uint index);
    uint active () const { return active_; }
    Node *activeChild () const { return child(active_); }
    Node *child (uint index) const;

protected:
    uint active_;
    std::vector<Node *> children_;
};


/**
 * @brief A Scene holds a root node with 3 children; a background, a workspace and a foreground
 *
 * The update() is called on the root
 *
 */
class Scene  {

    Group *root_;
    Group *background_;
    Group *workspace_;
    Group *foreground_;

public:
    Scene();
    // non assignable class
    Scene(Scene const&) = delete;
    Scene& operator=(Scene const&) = delete;
    ~Scene();

    void accept (Visitor& v);
    void update(float dt);

    void clear();
    void clearBackground();
    void clearWorkspace();
    void clearForeground();

    Group *root() { return root_; }
    Group *bg() { return background_; }
    Group *ws() { return workspace_; }
    Group *fg() { return foreground_; }

};



#endif // SCENE_H
