#pragma once
#include "util.h"
#include "event.h"

enum class kAttribute : uint16_t
{
    id = const_hash("id"),
    Width = const_hash("width"),
    MinWidth = const_hash("min-width"),
    MaxWidth = const_hash("max-width"),
    Height = const_hash("height"),
    MinHeight = const_hash("min-height"),
    MaxHeight = const_hash("max-height"),
    Divisions = const_hash("divisions"),
    InnerRadius = const_hash("inner-radius"),
    OuterRadius = const_hash("outer-radius"),
    Grow = const_hash("grow"),
    Shrink = const_hash("shrink"),
    FlexDir = const_hash("dir"),
    FlexWrap = const_hash("wrap"),
    Padding = const_hash("pad"),
    Margin = const_hash("margin"),
    Color = const_hash("color"),
    Thickness = const_hash("thickness"),
    BorderColor = const_hash("border-color"),
    Type = const_hash("type"),
    Text = const_hash("text"),
    TextWrapWidth = const_hash("text-wrap-width"),
    FontFace = const_hash("font-face"),
    FontSize = const_hash("font-size"),
    Justify = const_hash("justify"),
    Align = const_hash("align"),
    Path = const_hash("path"),
    Url = const_hash("url"),
    Region = const_hash("region"),
    Position = const_hash("position"),
    Positioning = const_hash("positioning"),
    FloodEvents = const_hash("flood-events"),
    Icon = const_hash("icon"),
    Selected = const_hash("selected"),
    Template = const_hash("template"),
    Value = const_hash("value"),
    Range = const_hash("range"),
    AspectRatio = const_hash("aspect-ratio"),
    ComboList = const_hash("combo-list"),
    Mips = const_hash("mips"),
    Default = const_hash("default"),
    RTL = const_hash("rtl"),
    AutoSize = const_hash("autosize"),
    MouseCapture = const_hash("mouse-capture"),
    ScrollColor = const_hash("scroll-color"),
    ScrollDir   = const_hash("scroll-dir"),
    Multiline = const_hash("multiline"),
    TextAlign = const_hash("text-align"),
    TextVerticalAlign = const_hash("text-vertical-align"),
};

class Node : public std::enable_shared_from_this<Node>
{
    friend class LayoutManager;
public:
    Node* m_parent{ nullptr };
    YGNodeRef y_node{ nullptr };
    class LayoutManager* m_manager = nullptr;
    uint16_t m_nodeID = 0;
    std::string m_nodeID_s;
    std::vector<std::shared_ptr<Node>> m_children;
    std::shared_ptr<Node> current_mouse_capture = nullptr;
    std::shared_ptr<Node> current_key_capture = nullptr;
    std::shared_ptr<Node> child_mouse_focus = nullptr;
    bool m_mouse_captured = false;
    bool m_key_captured = false;

    glm::mat4 m_proj;
    glm::mat4 m_mvp;
    glm::mat4 m_mvp_visible;
    bool m_mouse_focus = false;
    bool m_mouse_inside = false;
    bool m_flood_events = false;
    bool m_force_mouse_capture = false;
    bool m_capture_children = true; // wether to capture children events when xx_capture() is used
    bool m_destroyed = false;
    std::vector<std::shared_ptr<Node>> m_capture_stack;

    bool m_mouse_ignore = true;
    float m_zoom = 1.f;
    glm::vec2 m_scale{ 1, 1 };
    glm::vec2 m_pos{ 0, 0 };
    glm::vec2 m_pos_origin{ 0, 0 }; // original layout position without offset
    glm::vec2 m_pos_offset{ 0, 0 }; // artificial position offset for scrolling
    glm::vec2 m_pos_offset_childred{ 0, 0 }; // artificial position offset for scrolling
    glm::vec2 m_size{ 0, 0 };
    // rect: {origin}{size}
    glm::vec4 m_clip{ 0, 0, 0, 0 };
    // rect: {origin}{size}
    glm::vec4 m_clip_uncut{ 0, 0, 0, 0 };
    std::string m_name;
    bool m_display = true;
    // it's actually rendering
    bool m_on_screen = false;

    bool auto_width = true;
    bool auto_height = true;

    Node(const Node&) = delete;
    Node& operator=(const Node&) = delete;
    Node&& operator=(Node&& o);
    Node(Node&& o);
    Node();
    virtual ~Node();

    void SetWidth(float value);
    void SetWidthP(float value);
    void SetHeight(float value);
    void SetHeightP(float value);
    void SetSize(glm::vec2 value);
    void SetSize(float w, float h);
    void SetMinSize(float w, float h);
    void SetMaxSize(float w, float h);
    void SetMinSize(glm::vec2 value);
    void SetMaxSize(glm::vec2 value);
    void SetMaxWidth(float value);
    void SetMaxWidthP(float value);
    void SetMaxHeight(float value);
    void SetMaxHeightP(float value);
    void SetMinWidth(float value);
    void SetMinWidthP(float value);
    void SetMinHeight(float value);
    void SetMinHeightP(float value);

    void SetPadding(float t, float r, float b, float l);
    glm::vec4 GetPadding() const;
    void SetMargin(float t, float r, float b, float l);
    glm::vec4 GetMargin() const;
    void SetPosition(float l, float t, float r, float b);
    void SetPosition(float l, float t);
    void SetPosition(const glm::vec2 pos);

    void SetFlexGrow(float value);
    void SetFlexShrink(float value);
    void SetFlexDir(YGFlexDirection value);
    void SetFlexWrap(YGWrap value);
    void SetJustify(YGJustify value);
    void SetAlign(YGAlign value);
    void SetPositioning(YGPositionType value);
    void SetAspectRatio(float ar);
    void SetRTL(YGDirection dir);

    // used in visibility switch
    YGNodeRef y_placeholder = nullptr;
    bool GetVisibility();
    void SetVisibility(bool visible);
    void ToggleVisibility();

    glm::vec2 GetPosition();
    float GetWidth();
    float GetHeight();
    glm::vec2 GetSize();
    YGDirection GetRTL();

    virtual void restore_context();;
    virtual void clear_context();
    void update(float width, float height, float zoom);
    void update();
    void update_internal(const glm::vec2& origin, const glm::mat4& proj, float zoom);
    virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr);
    void load_internal(const tinyxml2::XMLElement* x_node, bool skip_children = false);
    virtual void draw();
    virtual Node* clone_instantiate() const;
    virtual void clone_copy(Node* dest) const;
    virtual void clone_children(Node* dest) const;
    virtual void clone_finalize(Node* dest) const;;
    void watch(std::function<bool(Node*)> observer);
    virtual void destroy();
    Node* root();
    void set_manager(LayoutManager* manager);
    bool added_to_root();

    template<class T = Node> std::shared_ptr<T> clone()
    {
        std::shared_ptr<T> n = std::shared_ptr<T>((T*)clone_instantiate());
        clone_copy(n.get());
        clone_children(n.get());
        clone_finalize(n.get());
        return n;
    }

    template<class T = Node> T* find(const char* ids)
    {
        uint16_t id = const_hash(ids);
        if (id == m_nodeID)
            return static_cast<T*>(this);
        for (auto& c : m_children)
            if (auto found = c->find(ids))
                return static_cast<T*>(found);
        return nullptr;
    }
    template<class T = Node> std::shared_ptr<T> find_ref(const char* ids)
    {
        uint16_t id = const_hash(ids);
        for (auto& c : m_children)
        {
            if (c->m_nodeID == id)
                return std::static_pointer_cast<T>(c);
            else if (auto found = c->find_ref(ids))
                return std::static_pointer_cast<T>(found);
        }
        return nullptr;
    }
    template<class T> T* add_child()
    {
        auto* n = new T;
        n->set_manager(m_manager);
        n->m_parent = m_parent;
        n->init();
        n->create();
        n->loaded();
        add_child(n);
        return n;
    }
    template<class T> std::shared_ptr<T> add_child_ref()
    {
        auto n = std::make_shared<T>();
        n->set_manager(m_manager);
        n->m_parent = m_parent;
        n->init();
        n->create();
        n->loaded();
        add_child(n);
        return n;
    }
    template<class T = Node> std::shared_ptr<T> add_child_file(const std::string& filename, const std::string& name)
    {
        if (auto t = load_template(filename, name))
        {
            add_child(t);
            return std::dynamic_pointer_cast<T>(t);
        }
        return nullptr;
    }
    template<class T = Node> std::shared_ptr<T> add_child_xml(const std::string& xml_string, const std::string& name)
    {
        if (auto t = parse_template(xml_string, name))
        {
            add_child(t);
            return std::dynamic_pointer_cast<T>(t);
        }
        return nullptr;
    }

    virtual void on_tick(float dt) { };
    virtual kEventResult on_event(Event* e);
    virtual kEventResult handle_event(Event* e);
    virtual void handle_resize(glm::vec2 old_size, glm::vec2 new_size, float zoom);
    virtual void handle_on_screen(bool old_visibility, bool new_visibility);
    virtual void handle_parent_resize(glm::vec2 old_size, glm::vec2 new_size);
    virtual void create();
    virtual void init();
    virtual void loaded();
    virtual void added(Node* parent);
    virtual void removed(Node* parent);
    virtual void on_child_added(Node* child) { };
    virtual void on_child_removed(Node* child) { };
    const Node* init_template(const char* id);
    bool init_template_file(const std::string& path, const std::string& id);
    std::shared_ptr<Node> load_template(const std::string& filename, const std::string& name) const;
    std::shared_ptr<Node> parse_template(const std::string& xml_stirng, const std::string& name) const;
    void tick(float dt);
    void app_redraw();
    void add_child(Node* n);
    void add_child(Node* n, int index);
    void add_child(std::shared_ptr<Node> n);
    void add_child(std::shared_ptr<Node> n, int index);
    void remove_from_parent();
    void remove_child(Node* n);
    void remove_all_children();
    void move_child(Node* n, int index);
    void move_child_front(Node* n);
    void move_child_offset(Node* n, int offset);
    int get_child_index(Node* n);
    Node* get_child_at(int index);
    // returns {origin, size} form
    glm::vec4 get_children_rect() const;
    std::vector<std::shared_ptr<Node>> get_children_at_point(glm::vec2 point) const;
    bool is_child_recursive(Node* o) const;
    bool is_child(Node* o) const;
    void mouse_capture();
    void mouse_release();
    void key_capture();
    void key_release();
};

