#include "pch.h"
#include "app.h"
#include "log.h"
#include "node.h"
#include "layout.h"
#include "util.h"
#include "asset.h"
#include "node_border.h"
#include "node_image.h"
#include "node_image_texture.h"
#include "node_icon.h"
#include "node_text.h"
#include "node_text_input.h"
#include "node_button.h"
#include "node_button_custom.h"
#include "node_slider.h"
#include "node_popup_menu.h"
#include "node_viewport.h"
#include "node_checkbox.h"
#include "node_panel_layer.h"
#include "node_panel_brush.h"
#include "node_panel_color.h"
#include "node_panel_stroke.h"
#include "node_color_quad.h"
#include "node_stroke_preview.h"
#include "node_canvas.h"
#include "node_scroll.h"
#include "node_dialog_browse.h"
#include "node_dialog_cloud.h"
#include "node_combobox.h"
#include "node_colorwheel.h"
#include "node_dialog_picker.h"
#include "node_panel_grid.h"
#include "node_about.h"
#include "node_changelog.h"
#include "node_usermanual.h"
#include "node_panel_quick.h"
#include "node_tool_bucket.h"
#include "node_panel_animation.h"
#include "node_metadata.h"

void Node::app_redraw()
{
    App::I->redraw = true;
    App::I->render_cv.notify_all();
}

void Node::watch(std::function<bool(Node*)> observer)
{
    bool cont = observer(this);
    if (cont)
    {
        for (auto& c : m_children)
        {
            //if (!glm::any(glm::lessThanEqual(zw(c->m_clip), { 0, 0 })))
                c->watch(observer);
        }
    }
}

void Node::destroy()
{
    //m_destroyed = true;
    //mouse_release();
    //key_release();
    //for (auto& c : m_children)
    //    c->destroy();
    //for (auto p = m_parent; p; p = p->m_parent)
    //    if (p->child_mouse_focus.get() == this)
    //        p->child_mouse_focus = nullptr;
    App::I->ui_task([&]
    {
        mouse_release();
        key_release();
        auto cp = m_children;
        for (auto& c : cp)
            c->destroy();
        for (auto p = m_parent; p; p = p->m_parent)
            if (p->child_mouse_focus.get() == this)
                p->child_mouse_focus = nullptr;
        remove_all_children();
        remove_from_parent();
    });
}

Node* Node::root()
{
    Node* ret = this;
    while (ret->m_parent)
        ret = ret->m_parent;
    return ret;
}

void Node::set_manager(LayoutManager* manager)
{
    m_manager = manager;
    for (auto& c : m_children)
        c->set_manager(manager);
}

bool Node::added_to_root()
{
    auto r = root();
    return r == m_manager->get(App::I->main_id);
}

kEventResult Node::on_event(Event* e)
{
    kEventResult ret = kEventResult::Available;

    if (e->m_cat == kEventCategory::KeyEvent && current_key_capture)
        return current_key_capture->on_event(e);
    
    if (current_mouse_capture && current_mouse_capture.get() != this)
    {
        if (e->m_cat == kEventCategory::MouseEvent && 
            child_mouse_focus != current_mouse_capture &&
            is_child(current_mouse_capture.get()))
        {
            MouseEvent* me = static_cast<MouseEvent*>(e);
            if (child_mouse_focus)
            {
                MouseEvent e2 = *me;
                e2.m_type = kEventType::MouseUnfocus;
                child_mouse_focus->handle_event(&e2);
                child_mouse_focus->m_mouse_focus = false;
            }
            MouseEvent e2 = *me;
            e2.m_type = kEventType::MouseFocus;
            current_mouse_capture->handle_event(&e2);
            child_mouse_focus = current_mouse_capture;
            child_mouse_focus->m_mouse_focus = true;
        }
        return current_mouse_capture->on_event(e);
    }

    bool skip_children = false;

    // skip mouse events if outside
    if (e->m_cat == kEventCategory::MouseEvent)
    {
        MouseEvent* me = static_cast<MouseEvent*>(e);
        skip_children |= !m_mouse_inside && !point_in_rect(me->m_pos, m_clip);
    }

    skip_children |= (e->m_cat == kEventCategory::MouseEvent || e->m_cat == kEventCategory::GestureEvent) &&
        (m_mouse_captured) && (root()->current_mouse_capture.get() == this) && m_capture_children; // <-- THIS IS WRONG "!m_capture_children" is correct, but it breaks everything if changed
    
    if (!m_display || glm::any(glm::lessThanEqual(zw(m_clip), { 0, 0 })))
        return kEventResult::Available;

    if (!skip_children)
    {
        // make a copy because any handler can change the children vector
        auto children_copy = m_children;
        for (auto it = children_copy.rbegin(); it != children_copy.rend(); ++it)
        {
            if ((*it)->on_event(e) == kEventResult::Consumed)
            {
                if (m_flood_events)
                {
                    ret = kEventResult::Consumed;
                }
                else
                {
                    if (e->m_cat == kEventCategory::MouseEvent && child_mouse_focus.get() != it->get())
                    {
                        MouseEvent* me = static_cast<MouseEvent*>(e);
                        if (child_mouse_focus && !child_mouse_focus->m_destroyed)
                        {
                            MouseEvent e2 = *me;
                            e2.m_type = kEventType::MouseUnfocus;
                            child_mouse_focus->handle_event(&e2);
                            child_mouse_focus->m_mouse_focus = false;
                        }
                        MouseEvent e2 = *me;
                        e2.m_type = kEventType::MouseFocus;
                        (*it)->handle_event(&e2);
                        if (!(*it)->m_destroyed)
                        {
                            child_mouse_focus = *it;
                            child_mouse_focus->m_mouse_focus = true;
                        }
                        //else
                        //{
                        //    child_mouse_focus = nullptr;
                        //}
                    }
                    ret = kEventResult::Consumed;
                    break;
                }
            }
        }
        if (ret == kEventResult::Consumed)
        {
            if (e->m_cat == kEventCategory::MouseEvent)
            {
                MouseEvent* me = static_cast<MouseEvent*>(e);
                bool old_inside = m_mouse_inside;
                m_mouse_inside = point_in_rect(me->m_pos, m_clip);
                if (old_inside == false && m_mouse_inside == true)
                {
                    MouseEvent e2 = *me;
                    e2.m_type = kEventType::MouseEnter;
                    handle_event(&e2);
                }
                if (old_inside == true && m_mouse_inside == false)
                {
                    MouseEvent e2 = *me;
                    e2.m_type = kEventType::MouseLeave;
                    handle_event(&e2);
                }
            }
            return kEventResult::Consumed;
        }
    }

    switch (e->m_cat)
    {
    case kEventCategory::MouseEvent:
    {
        if (m_mouse_ignore)
            break;
        MouseEvent* me = static_cast<MouseEvent*>(e);
        bool inside = point_in_rect(me->m_pos, m_clip);
        bool inside_old = m_mouse_inside;
        m_mouse_inside = inside;
        switch (e->m_type)
        {
        case kEventType::MouseScroll:
        case kEventType::MouseDownL:
        case kEventType::MouseDownR:
        case kEventType::MouseUpL:
        case kEventType::MouseUpR:
            if ((inside || m_mouse_captured) && ((handle_event(e) == kEventResult::Consumed) || m_force_mouse_capture))
                return kEventResult::Consumed;
            break;
        case kEventType::MouseMove:
            if (inside_old == false && inside == true)
            {
                MouseEvent e2 = *me;
                e2.m_type = kEventType::MouseEnter;
                handle_event(&e2);
            }
            if (inside || m_mouse_captured)
            {
                ret = handle_event(e);
                if (m_force_mouse_capture)
                    ret = kEventResult::Consumed;
            }
            if (inside_old == true && inside == false)
            {
                MouseEvent e2 = *me;
                e2.m_type = kEventType::MouseLeave;
                handle_event(&e2);
            }
            break;
        default:
            if (handle_event(e) == kEventResult::Consumed)
                return kEventResult::Consumed;
            break;
        }
        break;
    }
    case kEventCategory::GestureEvent:
    {
        if (m_mouse_ignore)
            break;
        GestureEvent* ge = static_cast<GestureEvent*>(e);
        bool inside = point_in_rect(ge->m_pos, m_clip);
        //bool inside_old = m_mouse_inside;
        m_mouse_inside = inside;
        if ((inside || m_mouse_captured) && handle_event(e) == kEventResult::Consumed)
            return kEventResult::Consumed;
        break;
    }
    default:
        if (handle_event(e) == kEventResult::Consumed)
            return kEventResult::Consumed;
        break;
    }
    return ret;
}

kEventResult Node::handle_event(Event* e)
{
    return kEventResult::Available;
}

void Node::handle_resize(glm::vec2 old_size, glm::vec2 new_size, float zoom)
{

}

void Node::handle_on_screen(bool old_visibility, bool new_visibility)
{
    if (new_visibility == false)
    {
        for (auto& c : m_children)
        {
            if (c->m_on_screen)
            {
                c->handle_on_screen(true, false);
                c->m_on_screen = false;
            }
        }
    }
}

void Node::handle_parent_resize(glm::vec2 old_size, glm::vec2 new_size)
{

}

void Node::create()
{

}

void Node::init()
{

}

void Node::loaded()
{

}

void Node::added(Node* parent)
{
    for (auto& c : m_children)
        c->added(this);
}

void Node::removed(Node* parent)
{
    for (auto p = m_parent; p; p = p->m_parent)
        if (p->child_mouse_focus.get() == this)
            p->child_mouse_focus = nullptr;
}

const Node* Node::init_template(const char* id)
{
    Node* t = nullptr;
    App::I->ui_task([&]
    {
        auto hid = const_hash(id);
        Node* top = m_manager->get(hid);
        t = static_cast<Node*>(top->m_children[0].get());
        t->set_manager(m_manager);
        for (auto& c : t->m_children)
        {
            auto node = c->clone();
            add_child(node);
        }
        YGNodeCopyStyle(y_node, t->y_node);
        t->clone_copy(this);
    });
    return t;
}

bool Node::init_template_file(const std::string& path, const std::string& id)
{
    bool ret = false;
    App::I->ui_task([&]
    {
        LayoutManager m;
        if (m.load(path.c_str()))
        {
            auto t = m.get_ref(id.c_str())->m_children[0];
            t->set_manager(m_manager);
            for (auto& c : t->m_children)
                add_child(c->clone());
            YGNodeCopyStyle(y_node, t->y_node);
            t->clone_copy(this);
            ret = true;
        }
    });
    return ret;
}

std::shared_ptr<Node> Node::load_template(const std::string& filename, const std::string& name) const
{
    LayoutManager m;
    std::shared_ptr<Node> ret;
    if (m.load(filename.c_str()))
        ret = m.get_ref(name.c_str())->m_children[0]->clone();
    m.unload();
    return ret;
}

std::shared_ptr<Node> Node::parse_template(const std::string& xml_string, const std::string& name) const
{
    LayoutManager m;
    std::shared_ptr<Node> ret;
    if (m.parse(xml_string))
        ret = m.get_ref(name.c_str())->m_children[0]->clone();
    m.unload();
    return ret;
}

void Node::add_child(Node* n)
{
    App::I->ui_task([&]
    {
        if (n->m_parent)
            n->m_parent->remove_child(n);
        m_children.emplace_back(n);
        n->m_parent = this;
        n->set_manager(m_manager);
        n->m_destroyed = false;
        YGNodeInsertChild(y_node, n->y_node, YGNodeGetChildCount(y_node));
        n->added(this);
        on_child_added(n);
    });
}

void Node::add_child(Node* n, int index)
{
    App::I->ui_task([&]
    {
        if (n->m_parent)
            n->m_parent->remove_child(n);
        m_children.emplace_back(n);
        n->m_parent = this;
        n->set_manager(m_manager);
        n->m_destroyed = false;
        YGNodeInsertChild(y_node, n->y_node, index);
        n->added(this);
        on_child_added(n);
    });
}

void Node::add_child(std::shared_ptr<Node> n)
{
    App::I->ui_task([this,n]
    {
        if (n->m_parent)
            n->m_parent->remove_child(n.get());
        m_children.push_back(n);
        n->m_parent = this;
        n->set_manager(m_manager);
        n->m_destroyed = false;
        YGNodeInsertChild(y_node, n->y_node, YGNodeGetChildCount(y_node));
        n->added(this);
        on_child_added(n.get());
    });
}

void Node::add_child(std::shared_ptr<Node> n, int index)
{
    App::I->ui_task([&]
    {
        if (n->m_parent)
            n->m_parent->remove_child(n.get());
        m_children.insert(m_children.begin() + index, n);
        n->m_parent = this;
        n->set_manager(m_manager);
        n->m_destroyed = false;
        YGNodeInsertChild(y_node, n->y_node, index);
        n->added(this);
        on_child_added(n.get());
    });
}

void Node::remove_from_parent()
{
    if (m_parent)
        m_parent->remove_child(this);
}

void Node::remove_child(Node* n)
{
    auto i = std::find_if(m_children.begin(), m_children.end(), [=](auto& ptr) { return ptr.get() == n; });
    if (i != m_children.end())
    {
        App::I->ui_task([&]
        {
            n->removed(this);
            n->m_parent = nullptr;
            YGNodeRemoveChild(y_node, n->y_node);
            on_child_removed(n);
            m_children.erase(i);
            if (child_mouse_focus.get() == n)
                child_mouse_focus = nullptr;
        });
    }
}

void Node::remove_all_children()
{
    App::I->ui_task([&]
    {
        for (auto& n : m_children)
        {
            n->removed(this);
            n->m_parent = nullptr;
            YGNodeRemoveChild(y_node, n->y_node);
            on_child_removed(n.get());
        }
        m_children.clear();
        child_mouse_focus = nullptr;
    });
}

void Node::move_child(Node* n, int index)
{
    App::I->ui_task([&]
    {
        int count = YGNodeGetChildCount(y_node);
        index = glm::clamp<int>(index, 0, count - 1);
        YGNodeRemoveChild(y_node, n->y_node);
        YGNodeInsertChild(y_node, n->y_node, index);
        auto it = std::find_if(m_children.begin(), m_children.end(),
            [n](const std::shared_ptr<Node>& o) { return o.get() == n; });
        auto tmp = *it; // copy the ptr before removing it
        m_children.erase(it);
        m_children.insert(m_children.begin() + index, tmp);
    });
}

void Node::move_child_front(Node* n)
{
    int count = YGNodeGetChildCount(y_node);
    move_child(n, count - 1);
}

void Node::move_child_offset(Node* n, int offset)
{
    int idx = get_child_index(n);
    move_child(n, idx + offset);
}

int Node::get_child_index(Node* n)
{
    int count = YGNodeGetChildCount(y_node);
    for (int i = 0; i < count; i++)
    {
        if (YGNodeGetChild(y_node, i) == n->y_node)
        {
            return i;
        }
    }
    return -1;
}

Node* Node::get_child_at(int index)
{
    auto n = YGNodeGetChild(y_node, index);
    for (auto& c : m_children)
    {
        if (c->y_node == n)
            return c.get();
    }
    return nullptr;
}

glm::vec4 Node::get_children_rect() const
{
    if (m_children.empty())
        return glm::vec4(0);
    glm::vec4 ret = m_children[0]->m_clip_uncut;
    for (auto& c : m_children)
    {
        if (c->m_display)
        {
            ret = rect_union(ret, c->m_clip_uncut);
            auto r = c->get_children_rect();
            if (r.w > 0 && r.z > 0)
                ret = rect_union(ret, r);
        }
    }
    return ret;
}

std::vector<std::shared_ptr<Node>> Node::get_children_at_point(glm::vec2 point) const
{
    std::vector<std::shared_ptr<Node>> ret;
    for (const auto& c : m_children)
    {
        if (point_in_rect(point, c->m_clip))
        {
            ret.push_back(c);
            auto c_ret = c->get_children_at_point(point);
            ret.insert(ret.end(), c_ret.begin(), c_ret.end());
        }
    }
    return ret;
}

bool Node::is_child_recursive(Node* o) const
{
    for (const auto& c : m_children)
    {
        if (c.get() == o || c->is_child_recursive(o))
            return true;
    }
    return false;
}

bool Node::is_child(Node* o) const
{
    for (const auto& c : m_children)
    {
        if (c.get() == o)
            return true;
    }
    return false;
}

void Node::mouse_capture()
{
    if (!m_parent || !m_manager)
        return;

    auto root = m_manager->get_ref("main");
    if (!root) return;
    
    auto& c = root->current_mouse_capture;
    auto& s = root->m_capture_stack;

    // already owner of capture
    if (c.get() == this || std::find_if(s.begin(), s.end(),
        [this](const auto& a) { return a.get() == this; }) != s.end())
        return;

    if (c)
    {
        if (c->is_child_recursive(this))
        {
            // save on stack
            s.push_back(c);
        }
        else
        {
            // cancel previous owner
            MouseEvent e;
            e.m_type = kEventType::MouseCancel;
            c->handle_event(&e);

            // TODO: only delete nodes on a different tree, 
            // so preserve direct parents of this

            // also clear the whole stack
            //s.clear();

            s.push_back(c);
        }
    }
    
    // make current
    c = shared_from_this();
    m_mouse_captured = true;
}

void Node::mouse_release()
{
    if (!m_parent || !m_manager)
        return;

    auto root = m_manager->get_ref("main");
    if (!root) return;

    auto& c = root->current_mouse_capture;
    auto& s = root->m_capture_stack;

    s.erase(std::remove_if(s.begin(), s.end(),
        [this](const auto& a) { return a.get() == this; }), s.end());
    if (c.get() == this)
    {
        if (s.empty())
        {
            c = nullptr;
        }
        else
        {
            c = s.back();
            s.pop_back();
        }
    }

    m_mouse_captured = false;
}

void Node::key_capture()
{
    if (!m_parent || !m_manager)
        return;

    auto root = m_manager->get_ref("main");
    if (!root) return;

    root->current_key_capture = shared_from_this();
    m_key_captured = true;
}

void Node::key_release()
{
    if (!m_parent || !m_manager)
        return;

    auto root = m_manager->get_ref("main");
    if (!root) return;

    if (root->current_key_capture.get() == this)
        root->current_key_capture = nullptr;
    m_key_captured = false;
}

Node&& Node::operator=(Node&& o)
{
    return std::forward<Node>(o);
}

Node::Node()
{
    y_node = YGNodeNew();
    YGNodeSetContext(y_node, this);
    y_placeholder = nullptr;
}

Node::Node(Node&& o)
{
    m_name = std::move(o.m_name);
    m_nodeID_s = std::move(o.m_nodeID_s);
    m_children = std::move(o.m_children);
    m_nodeID = o.m_nodeID;
    m_display = o.m_display;
    m_parent = o.m_parent;
    y_node = o.y_node;
    m_pos = o.m_pos;
    m_size = o.m_size;
    m_clip = o.m_clip;
    m_zoom = o.m_zoom;
    m_mouse_ignore = o.m_mouse_ignore;
    o.y_node = nullptr;
    o.m_parent = nullptr;

    set_manager(o.m_manager);
    for (auto& c : m_children)
        c->m_parent = this;

    current_mouse_capture = o.current_mouse_capture;
    current_key_capture = o.current_key_capture;
    m_mouse_captured = o.m_mouse_captured;
    m_key_captured = o.m_key_captured;

    m_proj = o.m_proj;
    m_mvp = o.m_mvp;
    m_mouse_inside = o.m_mouse_inside;
    m_flood_events = o.m_flood_events;
    m_force_mouse_capture = o.m_force_mouse_capture;
    m_capture_children = o.m_capture_children;
    m_destroyed = false;// o.m_destroyed;

    m_scale = o.m_scale;
    m_pos_origin = o.m_pos_origin;
    m_pos_offset = o.m_pos_offset;
    m_pos_offset_childred = o.m_pos_offset_childred;
    m_clip_uncut = o.m_clip_uncut;

    auto_width = o.auto_width;
    auto_height = o.auto_height;
}

Node::~Node()
{
    m_children.clear();
    if (y_node)
        YGNodeFree(y_node);
    if (y_placeholder)
        YGNodeFree(y_placeholder);
}

void Node::SetWidth(float value)
{
    YGNodeStyleSetWidth(y_node, value);
    m_size.x = value;
    auto_width = value == YGUndefined;
    app_redraw();
}

void Node::SetWidthP(float value)
{
    YGNodeStyleSetWidthPercent(y_node, value);
    auto_width = value == YGUndefined;
    app_redraw();
}

void Node::SetHeight(float value)
{
    YGNodeStyleSetHeight(y_node, value);
    m_size.y = value;
    auto_height = value == YGUndefined;
    app_redraw();
}

void Node::SetHeightP(float value)
{
    YGNodeStyleSetHeightPercent(y_node, value);
    auto_height = value == YGUndefined;
    app_redraw();
}

void Node::SetSize(float w, float h)
{
    SetWidth(w); SetHeight(h);
    m_size = {w, h};
    app_redraw();
}

void Node::SetSize(glm::vec2 value)
{
    SetWidth(value.x); SetHeight(value.y);
    m_size = value;
    app_redraw();
}

void Node::SetMinSize(float w, float h)
{
    SetMinWidth(w);
    SetMinHeight(h);
}

void Node::SetMinSize(glm::vec2 value)
{
    SetMinWidth(value.x);
    SetMinHeight(value.y);
}

void Node::SetMaxSize(float w, float h)
{
    SetMaxWidth(w);
    SetMaxHeight(h);
}

void Node::SetMaxSize(glm::vec2 value)
{
    SetMaxWidth(value.x);
    SetMaxHeight(value.y);
}

void Node::SetMaxWidth(float value)
{
    YGNodeStyleSetMaxWidth(y_node, value);
    app_redraw();
}

void Node::SetMaxWidthP(float value)
{
    YGNodeStyleSetMaxWidthPercent(y_node, value);
    app_redraw();
}

void Node::SetMaxHeight(float value)
{
    YGNodeStyleSetMaxHeight(y_node, value);
    app_redraw();
}

void Node::SetMaxHeightP(float value)
{
    YGNodeStyleSetMaxHeightPercent(y_node, value);
    app_redraw();
}

void Node::SetMinWidth(float value)
{
    YGNodeStyleSetMinWidth(y_node, value);
    app_redraw();
}

void Node::SetMinWidthP(float value)
{
    YGNodeStyleSetMinWidthPercent(y_node, value);
    app_redraw();
}

void Node::SetMinHeight(float value)
{
    YGNodeStyleSetMinHeight(y_node, value);
    app_redraw();
}

void Node::SetMinHeightP(float value)
{
    YGNodeStyleSetMinHeightPercent(y_node, value);
    app_redraw();
}

void Node::SetPadding(float t, float r, float b, float l)
{
    YGNodeStyleSetPadding(y_node, YGEdgeTop, t);
    YGNodeStyleSetPadding(y_node, YGEdgeRight, r);
    YGNodeStyleSetPadding(y_node, YGEdgeBottom, b);
    YGNodeStyleSetPadding(y_node, YGEdgeLeft, l);
    app_redraw();
}

glm::vec4 Node::GetPadding() const
{
    float t = YGNodeLayoutGetPadding(y_node, YGEdgeTop);
    float r = YGNodeLayoutGetPadding(y_node, YGEdgeRight);
    float b = YGNodeLayoutGetPadding(y_node, YGEdgeBottom);
    float l = YGNodeLayoutGetPadding(y_node, YGEdgeLeft);
    return{ t, r, b, l };
}

void Node::SetMargin(float t, float r, float b, float l)
{
    YGNodeStyleSetMargin(y_node, YGEdgeTop, t);
    YGNodeStyleSetMargin(y_node, YGEdgeRight, r);
    YGNodeStyleSetMargin(y_node, YGEdgeBottom, b);
    YGNodeStyleSetMargin(y_node, YGEdgeLeft, l);
    app_redraw();
}

glm::vec4 Node::GetMargin() const
{
    float t = YGNodeLayoutGetMargin(y_node, YGEdgeTop);
    float r = YGNodeLayoutGetMargin(y_node, YGEdgeRight);
    float b = YGNodeLayoutGetMargin(y_node, YGEdgeBottom);
    float l = YGNodeLayoutGetMargin(y_node, YGEdgeLeft);
    return{ t, r, b, l };
}

void Node::SetPosition(const glm::vec2 pos)
{
    SetPosition(pos.x, pos.y);
    m_pos = pos;
    app_redraw();
}

void Node::SetPosition(float l, float t)
{
    YGNodeStyleSetPosition(y_node, YGEdgeTop, t);
    YGNodeStyleSetPosition(y_node, YGEdgeLeft, l);
    m_pos = {l, t};
    app_redraw();
}

void Node::SetPosition(float l, float t, float r, float b)
{
    YGNodeStyleSetPosition(y_node, YGEdgeTop, t);
    YGNodeStyleSetPosition(y_node, YGEdgeRight, r);
    YGNodeStyleSetPosition(y_node, YGEdgeBottom, b);
    YGNodeStyleSetPosition(y_node, YGEdgeLeft, l);
    app_redraw();
}

void Node::SetFlexGrow(float value)
{
    YGNodeStyleSetFlexGrow(y_node, value);
    app_redraw();
}

void Node::SetFlexShrink(float value)
{
    YGNodeStyleSetFlexShrink(y_node, value);
    app_redraw();
}

void Node::SetFlexDir(YGFlexDirection value)
{
    YGNodeStyleSetFlexDirection(y_node, value);
    app_redraw();
}

void Node::SetFlexWrap(YGWrap value)
{
    YGNodeStyleSetFlexWrap(y_node, value);
    app_redraw();
}

void Node::SetJustify(YGJustify value)
{
    YGNodeStyleSetJustifyContent(y_node, value);
    app_redraw();
}

void Node::SetAlign(YGAlign value)
{
    YGNodeStyleSetAlignItems(y_node, value);
    app_redraw();
}

void Node::SetPositioning(YGPositionType value)
{
    YGNodeStyleSetPositionType(y_node, value);
    app_redraw();
}

void Node::SetAspectRatio(float ar)
{
    YGNodeStyleSetAspectRatio(y_node, ar);
    app_redraw();
}

void Node::SetRTL(YGDirection dir)
{
    YGNodeStyleSetDirection(y_node, dir);
    app_redraw();
}

bool Node::GetVisibility()
{
    return m_display;
}

void Node::SetVisibility(bool visible)
{
    App::I->ui_task([&]
    {
        if (m_display && !visible)
        {
            // hide
            int idx = m_parent->get_child_index(this);
            YGNodeRemoveChild(m_parent->y_node, y_node);
            y_placeholder = YGNodeNew();
            YGNodeInsertChild(m_parent->y_node, y_placeholder, idx);
        }
        else if (!m_display && visible)
        {
            int count = YGNodeGetChildCount(m_parent->y_node);
            for (int i = 0; i < count; i++)
            {
                if (YGNodeGetChild(m_parent->y_node, i) == y_placeholder)
                {
                    YGNodeRemoveChild(m_parent->y_node, y_placeholder);
                    YGNodeInsertChild(m_parent->y_node, y_node, i);
                    YGNodeFree(y_placeholder);
                    y_placeholder = nullptr;
                    break;
                }
            }
        }
    });
    m_display = visible;
    app_redraw();
}

void Node::ToggleVisibility()
{
    SetVisibility(!m_display);
}

glm::vec2 Node::GetPosition()
{
    return { YGNodeLayoutGetLeft(y_node), YGNodeLayoutGetTop(y_node) };
}

float Node::GetWidth()
{
    return YGNodeLayoutGetWidth(y_node);
}

float Node::GetHeight()
{
    return YGNodeLayoutGetHeight(y_node);
}

glm::vec2 Node::GetSize()
{
    return{ GetWidth(), GetHeight() };
}

YGDirection Node::GetRTL()
{
    return YGNodeStyleGetDirection(y_node);
}

void Node::restore_context()
{
    for (auto& c : m_children)
        c->restore_context();
}

void Node::clear_context()
{
    for (auto& c : m_children)
        c->clear_context();
}

void Node::update(float width, float height, float zoom)
{
    m_zoom = zoom;
    YGNodeStyleSetWidth(y_node, (width / zoom));
    YGNodeStyleSetHeight(y_node, (height / zoom));
    YGNodeCalculateLayout(y_node, YGUndefined, YGUndefined, YGDirectionLTR);
    m_proj = glm::ortho(0.f, (width / zoom), (height / zoom), 0.f, -1.f, 1.f);
    update_internal({ 0, 0 }, m_proj, zoom);
}

void Node::update()
{
    YGNodeCalculateLayout(y_node, YGUndefined, YGUndefined, YGDirectionLTR);
    update_internal({ 0, 0 }, m_proj, m_zoom);
}

void Node::update_internal(const glm::vec2& origin, const glm::mat4& proj, float zoom)
{
    float x = YGNodeLayoutGetLeft(y_node);
    float y = YGNodeLayoutGetTop(y_node);
    float w = YGNodeLayoutGetWidth(y_node);
    float h = YGNodeLayoutGetHeight(y_node);
    auto old_size = m_size;

    if (YGNodeStyleGetWidth(y_node).unit == YGUnit::YGUnitAuto)
        w -= m_pos_offset.x;
    //if (YGNodeStyleGetHeight(y_node).unit == YGUnit::YGUnitAuto)
    //    h -= m_pos_offset.y;
    
    m_pos = /*glm::floor*/(origin + glm::vec2(x, y) + m_pos_offset);
    m_pos_origin = /*glm::floor*/(origin + glm::vec2(x, y));
    m_size = /*glm::floor*/(glm::vec2(w, h));

    if (m_parent)
    {
        // correct the padding clip
        // should not clip the padded area
        // useful to draw decorations
        float pt = YGNodeLayoutGetPadding(m_parent->y_node, YGEdgeTop);
        float pr = YGNodeLayoutGetPadding(m_parent->y_node, YGEdgeRight);
        float pb = YGNodeLayoutGetPadding(m_parent->y_node, YGEdgeBottom);
        float pl = YGNodeLayoutGetPadding(m_parent->y_node, YGEdgeLeft);
        glm::vec2 off_p(pl, pt);
        glm::vec2 off_s(pr, pb);
        glm::vec4 pclip = { xy(m_parent->m_clip) + off_p, zw(m_parent->m_clip) - (off_s + off_p) };
        m_clip_uncut = glm::vec4(m_pos - glm::vec2(1), m_size + glm::vec2(2));
        m_clip = rect_intersection(m_clip_uncut, m_parent->m_clip);
    }
    else
    {
        m_clip_uncut = m_clip = glm::vec4(m_pos, m_size);
    }

    glm::mat4 pivot = glm::translate(glm::vec3(.5f, .5f, 0.f));
    glm::mat4 scale = glm::scale(glm::vec3(m_size, 1.f));
    glm::mat4 prescale = glm::scale(glm::vec3(m_scale, 1.f));
    glm::mat4 pos = glm::translate(glm::vec3(m_pos, 0));
    m_mvp = proj * pos * scale * pivot * prescale;
    m_proj = proj;

    if (!glm::any(glm::isnan(m_size)) && m_size != old_size || m_zoom != zoom)
    {
        m_zoom = zoom;
        handle_resize(old_size, m_size, zoom);
        for (auto& c : m_children)
            c->handle_parent_resize(old_size, m_size);
    }

    for (auto& c : m_children)
    {
        c->m_pos_offset = m_pos_offset + m_pos_offset_childred;
        c->update_internal(m_pos_origin, proj, zoom);
    }

    m_children.erase(std::remove_if(m_children.begin(), m_children.end(),
        [](const auto& n) { return n->m_destroyed; }), m_children.end());
}

void Node::tick(float dt)
{
    for (auto& c : m_children)
        c->tick(dt);
    on_tick(dt);
}

void Node::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
    switch (ka)
    {
    case kAttribute::id:
        m_nodeID_s = attr->Value();
        m_nodeID = const_hash(attr->Value());
        break;
    case kAttribute::Width:
        if (strcmp(attr->Value(), "auto") == 0)
        {
            YGNodeStyleSetWidth(y_node, YGUndefined);
            YGNodeStyleSetWidthPercent(y_node, YGUndefined);
            auto_width = true;
        }
        else
        {
            if (strchr(attr->Value(), '%'))
                YGNodeStyleSetWidthPercent(y_node, attr->FloatValue());
            else
                YGNodeStyleSetWidth(y_node, attr->FloatValue());
            auto_width = false;
        }
        break;
    case kAttribute::MinWidth:
        YGNodeStyleSetMinWidth(y_node, attr->FloatValue());
        break;
    case kAttribute::MaxWidth:
        YGNodeStyleSetMaxWidth(y_node, attr->FloatValue());
        break;
    case kAttribute::Height:
        if (strcmp(attr->Value(), "auto") == 0)
        {
            YGNodeStyleSetHeight(y_node, YGUndefined);
            YGNodeStyleSetHeightPercent(y_node, YGUndefined);
            auto_height = true;
        }
        else
        {
            if (strchr(attr->Value(), '%'))
                YGNodeStyleSetHeightPercent(y_node, attr->FloatValue());
            else
                YGNodeStyleSetHeight(y_node, attr->FloatValue());
            auto_height = false;
        }
        break;
    case kAttribute::MinHeight:
        YGNodeStyleSetMinHeight(y_node, attr->FloatValue());
        break;
    case kAttribute::MaxHeight:
        YGNodeStyleSetMaxHeight(y_node, attr->FloatValue());
        break;
    case kAttribute::Grow:
        YGNodeStyleSetFlexGrow(y_node, attr->FloatValue());
        break;
    case kAttribute::Shrink:
        YGNodeStyleSetFlexShrink(y_node, attr->FloatValue());
        break;
    case kAttribute::FlexDir:
    {
        YGFlexDirection dir = YGFlexDirectionRow;
        if (strcmp("col", attr->Value()) == 0)
            dir = YGFlexDirectionColumn;
        else if (strcmp("col-reverse", attr->Value()) == 0)
            dir = YGFlexDirectionColumnReverse;
        else if (strcmp("row", attr->Value()) == 0)
            dir = YGFlexDirectionRow;
        else if (strcmp("row-reverse", attr->Value()) == 0)
            dir = YGFlexDirectionRowReverse;
        YGNodeStyleSetFlexDirection(y_node, dir);
        break;
    }
    case kAttribute::FlexWrap:
        YGNodeStyleSetFlexWrap(y_node, attr->IntValue() ? YGWrapWrap : YGWrapNoWrap);
        break;
    case kAttribute::Justify:
    {
        YGJustify v = YGJustifyFlexStart;
        if (strcmp("center", attr->Value()) == 0)
            v = YGJustifyCenter;
        else if (strcmp("flex-start", attr->Value()) == 0)
            v = YGJustifyFlexStart;
        else if (strcmp("flex-end", attr->Value()) == 0)
            v = YGJustifyFlexEnd;
        else if (strcmp("space-around", attr->Value()) == 0)
            v = YGJustifySpaceAround;
        else if (strcmp("space-between", attr->Value()) == 0)
            v = YGJustifySpaceBetween;
        YGNodeStyleSetJustifyContent(y_node, v);
        break;
    }
    case kAttribute::Align:
    {
        YGAlign v = YGAlignStretch;
        if (strcmp("stretch", attr->Value()) == 0)
            v = YGAlignStretch;
        else if (strcmp("flex-start", attr->Value()) == 0)
            v = YGAlignFlexStart;
        else if (strcmp("flex-end", attr->Value()) == 0)
            v = YGAlignFlexEnd;
        else if (strcmp("center", attr->Value()) == 0)
            v = YGAlignCenter;
        YGNodeStyleSetAlignItems(y_node, v);
        break;
    }
    case kAttribute::Positioning:
    {
        YGPositionType v = YGPositionTypeRelative;
        if (strcmp("relative", attr->Value()) == 0)
            v = YGPositionTypeRelative;
        else if (strcmp("absolute", attr->Value()) == 0)
            v = YGPositionTypeAbsolute;
        YGNodeStyleSetPositionType(y_node, v);
        break;
    }
    case kAttribute::Position:
    {
        glm::vec4 v;
        int n = sscanf(attr->Value(), "%f %f %f %f", &v.x, &v.y, &v.z, &v.w);
        if (n == 2)
        {
            YGNodeStyleSetPosition(y_node, YGEdgeLeft, v.x);
            YGNodeStyleSetPosition(y_node, YGEdgeTop, v.y);
        }
        else
        {
            YGNodeStyleSetPadding(y_node, YGEdgeLeft, v.x);
            YGNodeStyleSetPadding(y_node, YGEdgeTop, v.y);
            YGNodeStyleSetPadding(y_node, YGEdgeRight, v.z);
            YGNodeStyleSetPadding(y_node, YGEdgeBottom, v.w);
        }
        break;
    }
    case kAttribute::Padding:
    {
        glm::vec4 pad;
        int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
        if (n == 1)
        {
            YGNodeStyleSetPadding(y_node, YGEdgeTop, pad.x);
            YGNodeStyleSetPadding(y_node, YGEdgeRight, pad.x);
            YGNodeStyleSetPadding(y_node, YGEdgeBottom, pad.x);
            YGNodeStyleSetPadding(y_node, YGEdgeLeft, pad.x);
        }
        else
        {
            YGNodeStyleSetPadding(y_node, YGEdgeTop, pad.x);
            YGNodeStyleSetPadding(y_node, YGEdgeRight, pad.y);
            YGNodeStyleSetPadding(y_node, YGEdgeBottom, pad.z);
            YGNodeStyleSetPadding(y_node, YGEdgeLeft, pad.w);
        }
        break;
    }
    case kAttribute::Margin:
    {
        glm::vec4 pad;
        int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
        if (n == 1)
        {
            YGNodeStyleSetMargin(y_node, YGEdgeTop, pad.x);
            YGNodeStyleSetMargin(y_node, YGEdgeRight, pad.x);
            YGNodeStyleSetMargin(y_node, YGEdgeBottom, pad.x);
            YGNodeStyleSetMargin(y_node, YGEdgeLeft, pad.x);
        }
        else
        {
            YGNodeStyleSetMargin(y_node, YGEdgeTop, pad.x);
            YGNodeStyleSetMargin(y_node, YGEdgeRight, pad.y);
            YGNodeStyleSetMargin(y_node, YGEdgeBottom, pad.z);
            YGNodeStyleSetMargin(y_node, YGEdgeLeft, pad.w);
        }
        break;
    }
    case kAttribute::FloodEvents:
        m_flood_events = attr->BoolValue();
        break;
    case kAttribute::MouseCapture:
        m_force_mouse_capture = attr->BoolValue();
        break;
    case kAttribute::AspectRatio:
        YGNodeStyleSetAspectRatio(y_node, attr->FloatValue());
        break;
    case kAttribute::RTL:
        if (strcmp("rtl", attr->Value()) == 0)
            SetRTL(YGDirectionRTL);
        else if (strcmp("ltr", attr->Value()) == 0)
            SetRTL(YGDirectionLTR);
        else if (strcmp("inherit", attr->Value()) == 0)
            SetRTL(YGDirectionInherit);
        else
        {
            LOG("Attribute %s for RTL unrecognized", attr->Value());
        }
    default:
        break;
    }
}

void Node::load_internal(const tinyxml2::XMLElement* x_node, bool skip_children /*= false*/)
{
    m_name = x_node->Name();
    //LOG("load_internal node %s", m_name.c_str());

    init();

    auto attr = x_node->FirstAttribute();
    while (attr)
    {
        parse_attributes((kAttribute)const_hash(attr->Name()), attr);
        attr = attr->Next();
    }

    create();

    if (skip_children)
    {
        loaded();
        return;
    }

    auto x_child = x_node->FirstChildElement();
    while (x_child)
    {
        if (auto os = x_child->Attribute("os"))
        {
            auto osv = split(os, ',');
            if (std::find(osv.begin(), osv.end(), PP_OS) == osv.end())
            {
                LOG("Element %s not for this os(%s), skipping", x_child->Name(), PP_OS)
                x_child = x_child->NextSiblingElement();
                continue;
            }
        }

        std::string node_name = x_child->Name();
        if (node_name == "ref")
        {
            auto ids = x_child->Attribute("id");
            auto id = const_hash(ids);
            auto& ref = (*m_manager)[id]->m_children[0];
            auto n = ref->clone();
            n->m_nodeID_s = ids;
            n->m_nodeID = id;
            add_child(n);
        }
        else if (node_name == "text")
        {
            auto n = new NodeText();
            add_child(n);
            n->load_internal(x_child, true);
            std::string text;
            auto node = x_child->FirstChild();
            while (node)
            {
                if (auto e = node->ToElement())
                {
                    if (strcmp(e->Name(), "br") == 0)
                        text.append("\n");
                }
                else if (auto t = node->ToText())
                {
                    text.append(t->Value());
                }
                node = node->NextSibling();
            }
            if (!text.empty())
                n->set_text(text);
        }
#define CASE(W,C) else if (node_name == W) { auto n = new C(); add_child(n); n->load_internal(x_child); }
        CASE("node", Node)
        CASE("border", NodeBorder)
        CASE("image", NodeImage)
        CASE("image-texture", NodeImageTexture)
        CASE("icon", NodeIcon)
        CASE("text-input", NodeTextInput)
        CASE("button", NodeButton)
        CASE("button-custom", NodeButtonCustom)
        CASE("combobox", NodeComboBox)
        CASE("slider-h", NodeSliderH)
        CASE("slider-v", NodeSliderV)
        CASE("slider-hue", NodeSliderHue)
        CASE("popup-menu", NodePopupMenu)
        CASE("viewport", NodeViewport)
        CASE("checkbox", NodeCheckBox)
        CASE("layer", NodeLayer)
        CASE("panel-layer", NodePanelLayer)
        CASE("panel-brush", NodePanelBrush)
        CASE("panel-color", NodePanelColor)
        CASE("panel-stroke", NodePanelStroke)
        CASE("panel-grid", NodePanelGrid)
        CASE("panel-quick", NodePanelQuick)
        CASE("dialog-browse", NodeDialogBrowse)
        CASE("dialog-browse-item", NodeDialogBrowseItem)
        CASE("dialog-cloud", NodeDialogCloud)
        CASE("dialog-cloud-item", NodeDialogCloudItem)
        CASE("color-picker", NodeColorPicker)
        CASE("about", NodeAbout)
        CASE("changelog", NodeChangelog)
        CASE("usermanual", NodeUserManual)
        CASE("tool-bucket", NodeToolBucket)
        CASE("timeline", NodeAnimationTimeline)
        CASE("stroke-preview", NodeStrokePreview)
        CASE("canvas", NodeCanvas)
        CASE("scroll", NodeScroll)
        CASE("metadata", NodeMetadata)
        CASE("panel-quick", NodePanelQuick)
        CASE("colorwheel", NodeColorWheel)
        CASE("color-quad", NodeColorQuad)
#undef  CASE
        else
        {
            LOG("instancing UNKNOWN node: %s", x_child->Name());
            auto n = new Node();
            add_child(n);
            n->load_internal(x_child);
        }
        x_child = x_child->NextSiblingElement();
    }
    loaded();
}

void Node::draw()
{

}

Node* Node::clone_instantiate() const
{
    return new Node();
}

void Node::clone_copy(Node* dest) const
{
    YGNodeCopyStyle(dest->y_node, y_node);
    dest->set_manager(m_manager);

    dest->m_name = m_name;
    dest->m_nodeID_s = m_nodeID_s;
    dest->m_nodeID = m_nodeID;
    dest->m_display = m_display;
    dest->m_pos = m_pos;
    dest->m_size = m_size;
    dest->m_clip = m_clip;
    dest->m_flood_events = m_flood_events;
    dest->m_force_mouse_capture = m_force_mouse_capture;

    dest->current_mouse_capture = current_mouse_capture;
    dest->current_key_capture = current_key_capture;
    dest->m_mouse_captured = m_mouse_captured;
    dest->m_key_captured = m_key_captured;
    dest->m_proj = m_proj;
    dest->m_mvp = m_mvp;
    dest->m_mouse_inside = m_mouse_inside;
    dest->m_capture_children = m_capture_children;
    dest->m_destroyed = false;// m_destroyed;
    dest->m_scale = m_scale;
    dest->m_pos_origin = m_pos_origin;
    dest->m_pos_offset = m_pos_offset;
    dest->m_pos_offset_childred = m_pos_offset_childred;
    dest->m_clip_uncut = m_clip_uncut;

    dest->auto_width = auto_width;
    dest->auto_height = auto_height;
}

void Node::clone_children(Node* dest) const
{
    for (auto& c : m_children)
    {
        std::shared_ptr<Node> cn = c->clone();
        dest->m_children.push_back(cn);
        cn->m_parent = dest;
        cn->set_manager(dest->m_manager);
        cn->loaded();
        YGNodeInsertChild(dest->y_node, cn->y_node, YGNodeGetChildCount(dest->y_node));
    }
}

void Node::clone_finalize(Node* dest) const
{
    /* find controllers and stuff */
}
