#include <rofi/module.hpp>

namespace rofi
{

void Module::makeNodeTree()
{
    for (auto &voxel: parts->getVoxels())
        node->addChild(voxel->getNode());

    for (auto &connector : parts->getConnections())
        node->addChild(connector->getNode());
}

void Module::setConnectorDocks() // TO DO: change if connector placement gets changed
{
    if (type == "cube")
        setConnectorDocksCube();
    if (type == "universal")
        setConnectorDocksUniversal();
}

void Module::setConnectorDocksCube()
{
    using enum side;
    std::string dock;

    for (const auto &connector : parts->getConnections())
    {
        const auto con_side = connector->getVoxelSide().first;

        switch(con_side)
        {
            case x_neg:
                dock = "-X";
                break;
            case x_pos:
                dock = "+X";
                break;
            case z_neg:
                dock = "-Z";
                break;
            case z_pos:
                dock = "+Z";
                break;
            case y_neg:
                dock = "-Y";
                break;
            case y_pos:
                dock = "+Y";
                break;
            default:
                ASSUMPTION(false);
        }
        connector_docks.emplace(connector, dock);
    }
}

void Module::setConnectorDocksUniversal()
{
    using enum side;
    std::string dock;
    auto a_pos = glm::vec3(0.0f);

    for (const auto &connector : parts->getConnections())
    {
        if (connector->getType(false) == connector_type::shared_rotation)
            continue;

        const auto con_side = connector->getVoxelSide().first;
        const auto voxel_pos = connector->getConnection().first.lock()->getNode()->getPositionLocal();
        switch(con_side)
        {
            case x_neg:
                dock = voxel_pos == a_pos ? "A+X" : "B+X";
                break;
            case x_pos:
                dock = voxel_pos == a_pos ? "A-X" : "B-X";
                break;
            case z_neg:
                dock = voxel_pos == a_pos ? "A-Z" : "B-Z";
                break;
            default:
                ASSUMPTION(false);
        }
        connector_docks.emplace(connector, dock);
    }
}

void Module::setComponentIDs()
{
    component_ids.clear();

    if (type == "universal")
        setComponentIDsUniversal();
    else
        setComponentIDsGeneric();
}

void Module::setComponentIDsUniversal()
{
    ASSUMPTION(connector_docks.size() == 6);

    // RoFICoMs
    for (const auto &[connector, dock] : connector_docks)
    {
        if (dock == "A-X")
            component_ids.emplace(connector, 0);
        else if (dock == "A+X")
            component_ids.emplace(connector, 1);
        else if (dock == "A-Z")
            component_ids.emplace(connector, 2);
        else if (dock == "B-X")
            component_ids.emplace(connector, 3);
        else if (dock == "B+X")
            component_ids.emplace(connector, 4);
        else if (dock == "B-Z")
            component_ids.emplace(connector, 5);
    }

    // Bodies
    auto a_pos = glm::vec3(0.0f);
    voxel_ptr a_voxel;

    for (const auto &voxel : parts->getVoxels())
    {
        if (voxel->getNode()->getPositionLocal() == a_pos)
        {
            component_ids.emplace(voxel, 8);
            a_voxel = voxel;
        }
        else
            component_ids.emplace(voxel, 9);
    }

    // Shoes do not get placed into the map as the key - connector is already placed as roficom
}

void Module::setComponentIDsGeneric()
{
    for (const auto connector : parts->getConnections())
    {
        if (connector->getType(true) == connector_type::open)
            component_ids.emplace(connector, component_ids.size());
    }
    for (const auto connector : parts->getConnections())
    {
        if (connector->getType(true) == connector_type::rotation)
            component_ids.emplace(connector, component_ids.size());
    }

    for (const auto voxel : parts->getVoxels())
    {
        if (!std::dynamic_pointer_cast<ModuleLink>(voxel) && !std::dynamic_pointer_cast<PadBoard>(voxel))
            component_ids.emplace(voxel, component_ids.size());
    }
}

Module::Module(const Module &other)
{
    node = Node::copy_deep(other.node);
    parts = VoxelGraph::copy(other.parts, node->getChildren());
    name = other.name;
    type = other.type;
    setConnectorDocks();
    setComponentIDs();
}
Module::Module(const Module &&other)
{
    node = std::move(other.node);
    parts = std::move(other.parts);
    name = std::move(other.name);
    type = std::move(other.type);
    setConnectorDocks();
    setComponentIDs();
}
Module& Module::operator=(const Module &other)
{
    node = Node::copy_deep(other.node);
    parts = VoxelGraph::copy(other.parts, node->getChildren());
    name = other.name;
    type = other.type;
    component_ids = other.component_ids;
    setConnectorDocks();
    setComponentIDs();

    return *this;
}
Module& Module::operator=(const Module &&other)
{
    node = std::move(other.node);
    parts = std::move(other.parts);
    name = std::move(other.name);
    type = std::move(other.type);
    setConnectorDocks();
    setComponentIDs();

    return *this;
}

Module::Module(voxel_graph_ptr _parts, std::string &_name, std::string &_type, std::map<objectbase_ptr, uint64_t> &_component_ids)
: node{Node::create()}, parts{_parts}, name{_name}, type{_type}, component_ids{_component_ids}
{
    ASSUMPTION(!_parts->getVoxels().empty() || !_parts->getConnections().empty());
    makeNodeTree();
    setConnectorDocks();
    setComponentIDs();
}

Module::Module(voxel_graph_ptr _parts, std::string &&_name, std::string &&_type, std::map<objectbase_ptr, uint64_t> &&_component_ids)
: node{Node::create()}, parts{_parts}, name{std::move(_name)}, type{std::move(_type)}, component_ids{std::move(_component_ids)}
{
    ASSUMPTION(!_parts->getVoxels().empty() || !_parts->getConnections().empty());
    makeNodeTree();
    setConnectorDocks();
    setComponentIDs();
}

module_ptr Module::copy(const module_ptr other_module)
{
    return module_ptr{ new Module(*other_module) };
}

void Module::setModuleNodeHierarchy(voxel_graph_ptr vg, std::string type)
{
    if (type == "cube")
        setModuleNodeHierarchyCube(vg);
    else if (type == "universal")
        setModuleNodeHierarchyUniversal(vg);
}

void Module::setModuleNodeHierarchyCube(voxel_graph_ptr vg)
{
    ASSUMPTION(!vg->getVoxels().empty());
    auto body_node = vg->getVoxels().front()->getNode();

    for (auto connector : vg->getConnections())
    {
        auto con_node = connector->getNode();
        if (!con_node->getParent().expired())
            con_node->getParent().lock()->transferChild(con_node, body_node, true);
    }

    const auto rot = glm::angleAxis(glm::radians(90.0f), glm::vec3(0.0f, 0.0f, -1.0f)) 
                     * glm::angleAxis(glm::radians(90.0f), glm::vec3(0.0f, -1.0f, 0.0f));
    body_node->rotateRotationQuat(rot);
}

void Module::setModuleNodeHierarchyUniversal(voxel_graph_ptr vg)
{
    for (auto &voxel : vg->getVoxels())
    {
        if (!voxel->hasRotationConnector())
            continue;

        auto voxel_node = voxel->getNode();

        auto rot_con = std::dynamic_pointer_cast<rofi::RotationConnector>(voxel->getRotationConnector().lock());
        auto [fst, snd] = rot_con->getBoundConnectors();
        auto rot_con_node = rot_con->getNode();
        std::vector<node_ptr> connector_nodes = { rot_con_node, fst.lock()->getNode(), snd.lock()->getNode() };
        std::vector<node_ptr> parent_nodes = { voxel_node, rot_con_node, rot_con_node };

        for (int i = 0; i < 3; ++i)
        {
            if (!connector_nodes[i]->getParent().expired())
                connector_nodes[i]->getParent().lock()->transferChild(connector_nodes[i], parent_nodes[i], true);
        }
    }
}

}
