#include "pch.h"
#include "app.h"
#include "layout.h"
#include "node.h"

namespace
{
using NodeChildren = std::vector<std::shared_ptr<Node>>;
using NodeChildIterator = NodeChildren::iterator;

NodeChildIterator find_child_iterator(Node& parent, Node* child)
{
    return std::find_if(parent.m_children.begin(), parent.m_children.end(),
        [child](const std::shared_ptr<Node>& candidate) { return candidate.get() == child; });
}

template <typename AttachChildrenFn>
void attach_child(Node& parent, Node* child, int yoga_index, AttachChildrenFn&& attach_children)
{
    if (child->m_parent)
        child->m_parent->remove_child(child);

    attach_children(child);
    child->m_parent = &parent;
    child->set_manager(parent.m_manager);
    child->m_destroyed = false;
    YGNodeInsertChild(parent.y_node, child->y_node, yoga_index);
    child->added(&parent);
    parent.on_child_added(child);
}

template <typename AttachChildrenFn>
void attach_child(Node& parent, const std::shared_ptr<Node>& child, int yoga_index, AttachChildrenFn&& attach_children)
{
    if (child->m_parent)
        child->m_parent->remove_child(child.get());

    attach_children(child);
    child->m_parent = &parent;
    child->set_manager(parent.m_manager);
    child->m_destroyed = false;
    YGNodeInsertChild(parent.y_node, child->y_node, yoga_index);
    child->added(&parent);
    parent.on_child_added(child.get());
}

void detach_child(Node& parent, NodeChildIterator child_it)
{
    Node* child = child_it->get();
    child->removed(&parent);
    child->m_parent = nullptr;
    YGNodeRemoveChild(parent.y_node, child->y_node);
    parent.on_child_removed(child);
    parent.m_children.erase(child_it);
    if (parent.child_mouse_focus.get() == child)
        parent.child_mouse_focus = nullptr;
}

void detach_all_children(Node& parent)
{
    for (auto& child : parent.m_children)
    {
        child->removed(&parent);
        child->m_parent = nullptr;
        YGNodeRemoveChild(parent.y_node, child->y_node);
        parent.on_child_removed(child.get());
    }
    parent.m_children.clear();
    parent.child_mouse_focus = nullptr;
}

void reorder_child(Node& parent, Node* child, int index)
{
    int count = YGNodeGetChildCount(parent.y_node);
    index = glm::clamp<int>(index, 0, count - 1);
    YGNodeRemoveChild(parent.y_node, child->y_node);
    YGNodeInsertChild(parent.y_node, child->y_node, index);

    auto child_it = find_child_iterator(parent, child);
    auto moved_child = *child_it;
    parent.m_children.erase(child_it);
    parent.m_children.insert(parent.m_children.begin() + index, moved_child);
}
}

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

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

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([&]
    {
        attach_child(*this, n, YGNodeGetChildCount(y_node),
            [this](Node* child)
            {
                m_children.emplace_back(child);
            });
    });
}

void Node::add_child(Node* n, int index)
{
    App::I->ui_task([&]
    {
        attach_child(*this, n, index,
            [this](Node* child)
            {
                // Preserve the current backing-vector behavior for raw-pointer inserts.
                m_children.emplace_back(child);
            });
    });
}

void Node::add_child(std::shared_ptr<Node> n)
{
    App::I->ui_task([this, n]
    {
        attach_child(*this, n, YGNodeGetChildCount(y_node),
            [this](const std::shared_ptr<Node>& child)
            {
                m_children.push_back(child);
            });
    });
}

void Node::add_child(std::shared_ptr<Node> n, int index)
{
    App::I->ui_task([&]
    {
        attach_child(*this, n, index,
            [this, index](const std::shared_ptr<Node>& child)
            {
                m_children.insert(m_children.begin() + index, child);
            });
    });
}

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

void Node::remove_child(Node* n)
{
    auto i = find_child_iterator(*this, n);
    if (i != m_children.end())
    {
        App::I->ui_task([&]
        {
            detach_child(*this, i);
        });
    }
}

void Node::remove_all_children()
{
    App::I->ui_task([&]
    {
        detach_all_children(*this);
    });
}

void Node::move_child(Node* n, int index)
{
    App::I->ui_task([&]
    {
        reorder_child(*this, n, index);
    });
}

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