#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"

void Node::app_redraw()
{
    App::I.redraw = true;
}

void Node::async_start()
{
    App::I.async_start();
}

void Node::async_update()
{
    App::I.async_update();
}

void Node::async_end()
{
    App::I.async_end();
}

void Node::watch(std::function<bool(Node*)> observer)
{
    bool cont = observer(this);
    if (cont)
    {
        for (auto& c : m_children)
            c->watch(observer);
    }
}

void Node::destroy()
{
    m_destroyed = true;
    mouse_release();
    key_release();
}

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

Node* Node::root()
{

    Node* ret = this;
    while (ret->parent)
        ret = ret->parent;
    return ret;
}

kEventResult Node::on_event(Event* e)
{
    kEventResult ret = kEventResult::Available;
    
    if (current_mouse_capture)
        return current_mouse_capture->on_event(e);

    bool skip_children = false;
    skip_children |= (e->m_cat == kEventCategory::MouseEvent || e->m_cat == kEventCategory::GestureEvent) &&
        (m_mouse_captured) && (root()->current_mouse_capture == this) && m_capture_children; // <-- THIS IS WRONG "!m_capture_children" is correct, but it breaks everything if changed

    if (!m_display)
        return kEventResult::Available;

    if (!skip_children)
    {
        for (auto it = m_children.rbegin(); it != m_children.rend(); ++it)
        {
            if ((*it)->on_event(e) == kEventResult::Consumed)
            {
                if (m_flood_events)
                {
                    ret = kEventResult::Consumed;
                }
                else
                {
                    return kEventResult::Consumed;
                }
            }
        }
        if (ret == kEventResult::Consumed)
            return ret;
    }

    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)
{

}

void Node::create()
{

}

void Node::init()
{

}

void Node::loaded()
{

}

void Node::added(Node* parent)
{

}

void Node::removed(Node* parent)
{

}

const Node* Node::init_template(const char* id)
{
    auto hid = const_hash(id);
    Node* top = m_manager->get(hid);
    Node* m_template = static_cast<Node*>(top->m_children[0].get());
    for (auto& c : m_template->m_children)
    {
        auto node = c->clone();
        add_child(node);
        node->init();
        node->create();
        node->loaded();
    }
    YGNodeCopyStyle(y_node, m_template->y_node);
    return m_template;
}

void Node::add_child(Node* n)
{
    m_children.emplace_back(n);
    n->parent = this;
    n->m_manager = m_manager;
    YGNodeInsertChild(y_node, n->y_node, YGNodeGetChildCount(y_node));
    n->added(this);
    on_child_added(n);
}

void Node::add_child(Node* n, int index)
{
    m_children.emplace_back(n);
    n->parent = this;
    n->m_manager = m_manager;
    YGNodeInsertChild(y_node, n->y_node, index);
    n->added(this);
    on_child_added(n);
}

void Node::add_child(std::shared_ptr<Node> n)
{
    m_children.push_back(n);
    n->parent = this;
    n->m_manager = m_manager;
    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)
{
    m_children.insert(m_children.begin() + index, n);
    n->parent = this;
    n->m_manager = m_manager;
    YGNodeInsertChild(y_node, n->y_node, index);
    n->added(this);
    on_child_added(n.get());
}

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())
    {
        n->removed(this);
        YGNodeRemoveChild(y_node, n->y_node);
        on_child_removed(n);
        m_children.erase(i);
    }
}

void Node::remove_all_children()
{
    for (auto& n : m_children)
    {
        n->removed(this);
        YGNodeRemoveChild(y_node, n->y_node);
        on_child_removed(n.get());
    }
    m_children.clear();
}

void Node::move_child(Node* n, int index)
{
    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_offset(Node* n, int offset)
{
    int count = YGNodeGetChildCount(y_node);
    for (int i = 0; i < count; i++)
    {
        if (YGNodeGetChild(y_node, i) == n->y_node)
        {
            int new_index = glm::clamp<int>(i + offset, 0, count - 1);
            YGNodeRemoveChild(y_node, n->y_node);
            YGNodeInsertChild(y_node, n->y_node, new_index);
            break;
        }
    }
}

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)
        ret = rect_union(ret, c->m_clip_uncut);
    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;
}

void Node::mouse_capture()
{
    auto& c = root()->current_mouse_capture;
    auto& s = root()->m_capture_stack;

    // already owner of capture
    if (c == this || std::find(s.begin(), s.end(), 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 = this;
    m_mouse_captured = true;
}

void Node::mouse_release()
{
    if (!parent)
        return;

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

    s.erase(std::remove(s.begin(), s.end(), this), s.end());
    if (c == this)
    {
        if (s.empty())
        {
            c = nullptr;
        }
        else
        {
            c = s.back();
            s.pop_back();
        }
    }

    m_mouse_captured = false;
}

void Node::key_capture()
{
    if (!parent)
        return;

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

void Node::key_release()
{
    root()->current_key_capture = nullptr;
    m_key_captured = false;
}

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

Node::Node()
{
    y_node = YGNodeNew();
    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);
    for (auto& c : m_children)
        c->parent = this;
    m_nodeID = o.m_nodeID;
    m_display = o.m_display;
    parent = o.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.parent = nullptr;

    m_manager = o.m_manager;
    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;

}

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;
}

void Node::SetWidthP(float value)
{
    YGNodeStyleSetWidthPercent(y_node, value);
}

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

void Node::SetHeightP(float value)
{
    YGNodeStyleSetHeightPercent(y_node, value);
}

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

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

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);
}

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);
}

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;
}

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

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);
}

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

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

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

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

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

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

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

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

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

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

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);
}

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

void Node::update_internal(const glm::vec2& origin, const glm::mat4& proj)
{
    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;
    glm::vec2 parent_offset = parent ? parent->m_pos_offset_childred : glm::vec2(0.f);
    m_pos = glm::floor(origin + glm::vec2(x, y) + m_pos_offset + parent_offset);
    m_pos_origin = glm::floor(origin + glm::vec2(x, y));
    m_size = glm::floor(glm::vec2(w, h));

    if (parent)
    {
        // correct the padding clip
        // should not clip the padded area
        // useful to draw decorations
        float pt = YGNodeLayoutGetPadding(parent->y_node, YGEdgeTop);
        float pr = YGNodeLayoutGetPadding(parent->y_node, YGEdgeRight);
        float pb = YGNodeLayoutGetPadding(parent->y_node, YGEdgeBottom);
        float pl = YGNodeLayoutGetPadding(parent->y_node, YGEdgeLeft);
        glm::vec2 off_p(pl, pt);
        glm::vec2 off_s(pr, pb);
        glm::vec4 pclip = { xy(parent->m_clip) + off_p, zw(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, 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;

    for (int i = 0; i < m_children.size(); i++)
    {
        if (m_children[i]->m_destroyed)
        {
            m_children[i]->destroy_immediate();
            remove_child(m_children[i].get());
            i--;
        }
    }

    if (m_size != old_size)
        handle_resize(old_size, m_size);

    for (auto& c : m_children)
        c->update_internal(m_pos, proj);
}

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);
        }
        else
        {
            if (strchr(attr->Value(), '%'))
                YGNodeStyleSetWidthPercent(y_node, attr->FloatValue());
            else
                YGNodeStyleSetWidth(y_node, attr->FloatValue());
        }
        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);
        }
        else
        {
            if (strchr(attr->Value(), '%'))
                YGNodeStyleSetHeightPercent(y_node, attr->FloatValue());
            else
                YGNodeStyleSetHeight(y_node, attr->FloatValue());
        }
        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)
{
    m_name = x_node->Name();
    //LOG("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();

    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;
            }
        }

        kWidget child_id = (kWidget)const_hash(x_child->Name());
        switch (child_id)
        {
#define CASE(W,C) case W: { auto n = new C(); add_child(n); n->load_internal(x_child); break; }
            CASE(kWidget::Node, Node);
            CASE(kWidget::Border, NodeBorder);
            CASE(kWidget::Image, NodeImage);
            CASE(kWidget::ImageTexture, NodeImageTexture);
            CASE(kWidget::Icon, NodeIcon);
            CASE(kWidget::Text, NodeText);
            CASE(kWidget::TextInput, NodeTextInput);
            CASE(kWidget::Button, NodeButton);
            CASE(kWidget::ButtonCustom, NodeButtonCustom);
            CASE(kWidget::ComboBox, NodeComboBox);
            CASE(kWidget::SliderH, NodeSliderH);
            CASE(kWidget::SliderV, NodeSliderV);
            CASE(kWidget::SliderHue, NodeSliderHue);
            CASE(kWidget::PopupMenu, NodePopupMenu);
            CASE(kWidget::Viewport, NodeViewport);
            CASE(kWidget::CheckBox, NodeCheckBox);
            CASE(kWidget::Layer, NodeLayer);
            CASE(kWidget::PanelLayer, NodePanelLayer);
            CASE(kWidget::PanelBrush, NodePanelBrush);
            CASE(kWidget::PanelColor, NodePanelColor);
            CASE(kWidget::PanelStroke, NodePanelStroke);
            CASE(kWidget::PanelGrid, NodePanelGrid);
            CASE(kWidget::ColorQuad, NodeColorQuad);
            CASE(kWidget::StrokePreview, NodeStrokePreview);
            CASE(kWidget::Canvas, NodeCanvas);
            CASE(kWidget::Scroll, NodeScroll);
            CASE(kWidget::DialogBrowse, NodeDialogBrowse);
            CASE(kWidget::DialogBrowseItem, NodeDialogBrowseItem);
            CASE(kWidget::DialogCloud, NodeDialogCloud);
            CASE(kWidget::DialogCloudItem, NodeDialogCloudItem);
            CASE(kWidget::ColorWheel, NodeColorWheel);
            CASE(kWidget::ColorPicker, NodeColorPicker);
            CASE(kWidget::About, NodeAbout);
            CASE(kWidget::Changelog, NodeChangelog);
            CASE(kWidget::UserManual, NodeUserManual);
#undef  CASE
        case kWidget::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);
            break;
        }
        default:
        {
            LOG("instancing UNKNOWN node: %s", x_child->Name());
            auto n = new Node();
            add_child(n);
            n->load_internal(x_child);
            break;
        }
        }
        x_child = x_child->NextSiblingElement();
    }
    loaded();
}

void Node::draw()
{

}

Node* Node::clone()
{
    Node* n = clone_instantiate();
    clone_copy(n);
    clone_children(n);
    clone_finalize(n);
    return n;
}

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

void Node::clone_copy(Node* dest) const
{
    YGNodeCopyStyle(dest->y_node, y_node);
    dest->m_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->m_manager = m_manager;
    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;


}

void Node::clone_children(Node* dest) const
{
    for (auto& c : m_children)
    {
        Node* cn = c->clone();
        dest->m_children.emplace_back(cn);
        cn->parent = dest;
        cn->m_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 */
}
