#include <algorithm>
#include <iostream>
#include <memory>
#include <spdlog/fmt/fmt.h>
#include <string>
#include <vector>

struct ArrayDescriptor
{
    std::string data_type;
    size_t size;
    size_t bits;
    double lower_limit;
    double upper_limit;
    std::vector<std::string> names;
};

namespace detail
{
template <typename T> struct Node: public std::enable_shared_from_this<Node<T>>
{
    std::string name;
    std::weak_ptr<Node<T>> parent;
    std::vector<std::shared_ptr<Node<T>>> children;
    T data;

    std::shared_ptr<Node<T>> add_child(const std::string &name)
    {
        auto child = std::make_shared<Node<T>>();
        child->name = name;
        child->parent = this->shared_from_this();
        children.push_back(child);
        return child;
    }

    std::shared_ptr<Node<T>> get(const std::string &name) const
    {
        if (auto it = std::find_if(std::begin(children), std::end(children),
                                   [&](const auto &child) { return child->name == name; });
            it != std::end(children))
        {
            return *it;
        }

        return {};
    }

    std::shared_ptr<Node<T>> get(const std::string &name)
    {
        if (auto ret = const_cast<const Node<T> *>(this)->get(name))
            return ret;
        return add_child(name);
    }
};
} // namespace detail

using Node = detail::Node<ArrayDescriptor>;

void print_preorder(const std::shared_ptr<Node> &node, int depth)
{
    for (int i = 0; i < depth; ++i)
        std::cout << "  ";
    std::cout << "name=" << node->name;
    if (node->data.size > 0)
        std::cout << ", size=" << node->data.size;
    std::cout << std::endl;

    for (const auto &child: node->children)
        print_preorder(child, depth + 1);
}

void print_bfs(const std::shared_ptr<Node> &node)
{
    std::vector<std::shared_ptr<Node>> queue;
    queue.push_back(node);

    while (!queue.empty())
    {
        auto current = queue.front();
        queue.erase(queue.begin());

        std::cout << "name=" << current->name;
        if (current->data.size > 0)
            std::cout << ", size=" << current->data.size;
        std::cout << std::endl;

        for (const auto &child: current->children)
            queue.push_back(child);
    }
}

int main(int argc, char *argv[])
{
    auto root = std::make_shared<Node>();
    root->name = "root";
    root->get("m0")->get("a00")->data.size = 16;
    root->get("m0")->get("a01")->data.size = 16;
    root->get("m1")->get("a10")->data.size = 32;
    root->get("m1")->get("a10")->add_child("a100")->data.size = 32;
    root->get("m1")->get("a10")->add_child("a101")->data.size = 4;
    root->get("m1")->get("a11")->add_child("a110")->data.size = 32;
    root->get("m1")->get("a12")->data.size = 8;
    root->get("m1")->get("a12")->add_child("a120")->data.size = 16;
    root->get("m1")->get("a12")->add_child("a121")->data.size = 16;
    root->get("m1")->get("a12")->add_child("a122")->data.size = 16;

    std::cout << "preorder:\n";
    print_preorder(root, 0);

    std::cout << "bfs:\n";
    print_bfs(root);
}
