#include <rofi/voxel.hpp>

#include <ranges>

namespace rofi
{

void Voxel::connectConnection(side s, connector_weak_ptr connector)
{
    ASSUMPTION(!connections.contains(s));
    ASSUMPTION(connections.size() < 6);

    auto con_type = connector.lock()->getType(voxel_weak_ptr());

    // NOTE: workaround for non-head rotation connectors
    if (auto rot_con = std::dynamic_pointer_cast<RotationConnector>(connector.lock())
        ; !rot_con || rot_con->isHead())
        ASSUMPTION(slotCompatible(s, con_type));

    restrictSlots(s, connector);

    connections.emplace(s, connector);
}
void Voxel::disconnectConnection(side s)
{
    ASSUMPTION(connections.contains(s));

    releaseSlots(s, getConnectorAt(s));

    connections.erase(s);
}

void Voxel::disconnectConnection(connector_weak_ptr connector)
{
    auto locked_connector = connector.lock();

    auto prev_size = connections.size();

    for (auto it = connections.begin(); it != connections.end(); )
    {
        if (it->second.lock() == locked_connector)
        {
            releaseSlots(it->first, connector);
            it = connections.erase(it);
        }
        else
            ++it;
    }

    ASSUMPTION(connections.size() < prev_size);
}

void Voxel::restrictSlotsSharedRotation(const side s)
{
    for (auto &[side, type_map] : slots)
    {
        type_map[shared_rotation] = false;
        if (side != vec_to_enum(-enum_to_vec(s)))
            type_map[rotation] = false;
    }
}

void Voxel::restrictSlotsRotation(const side s, connector_weak_ptr connector)
{
    auto rotation_con = std::dynamic_pointer_cast<RotationConnector>(connector.lock());

    ASSUMPTION(rotation_con);

    slots[s][rotation] = false;

    if (!rotation_con->isHead())
        return;

    auto rotation_axis = rotation_con->getRotationAxis();

    ASSUMPTION(glm::length(rotation_axis) == 1.0f);
    ASSUMPTION(rotation_axis != enum_to_vec(s) || -rotation_axis != enum_to_vec(s));

    auto side_under_rotation = glm::cross(enum_to_vec(s), rotation_axis);

    ASSUMPTION(glm::length(side_under_rotation) == 1.0f);

    for (auto &[type, val] : slots[vec_to_enum(side_under_rotation)])
    {
        val = false;
    }
    for (auto &[type, val] : slots[vec_to_enum(-side_under_rotation)])
    {
        val = false;
    }

    for (auto &[side, type_map] : slots)
    {
        if (side != vec_to_enum(rotation_axis) && side != vec_to_enum(-rotation_axis))
            type_map[rotation] = false;
        if (side != vec_to_enum(-enum_to_vec(s)))
            type_map[shared_rotation] = false;
    }
}

void Voxel::releaseSlotsSharedRotation(const side s)
{
    slots[s][shared_rotation] = true;

    // if shared voxel with Rotation Connector on opposite side
    if(isConnectedAt(-enum_to_vec(s)) 
        && getConnectorAt(-enum_to_vec(s)).lock()->getType(true) == connector_type::rotation)
        return;

    for (auto &[side, type_map] : slots)
    {
        type_map[shared_rotation] = true;
        type_map[rotation] = true;    
    }
}

void Voxel::releaseSlotsRotation(const side s, connector_weak_ptr connector)
{
    auto rotation_con = std::dynamic_pointer_cast<RotationConnector>(connector.lock());

    ASSUMPTION(rotation_con);
    
    if (!rotation_con->isHead())
        return;

    auto rotation_axis = rotation_con->getRotationAxis();

    ASSUMPTION(glm::length(rotation_axis) == 1.0f);
    ASSUMPTION(rotation_axis != enum_to_vec(s) || -rotation_axis != enum_to_vec(s));

    auto side_under_rotation = glm::cross(enum_to_vec(s), rotation_axis);

    ASSUMPTION(glm::length(side_under_rotation) == 1.0f);

    slots[s][rotation] = true;
    auto side_under_pos = vec_to_enum(side_under_rotation);
    auto side_under_neg = vec_to_enum(-side_under_rotation);
    slots[side_under_pos][fixed] = true;
    slots[side_under_pos][open] = true;
    slots[side_under_neg][fixed] = true;
    slots[side_under_neg][open] = true;

    // if shared voxel with Shared Rotation Connector on opposite side
    if(isConnectedAt(-enum_to_vec(s)) 
       && getConnectorAt(-enum_to_vec(s)).lock()->getType(true) == connector_type::shared_rotation)
       return;
    
    for (auto &[side, type_map] : slots)
    {
        type_map[rotation] = true;
        type_map[shared_rotation] = true;
    }
}

Voxel::Voxel(const Voxel &other)
{
    slots = other.slots;
    node = Node::copy_deep(other.node);
}

Voxel::Voxel(const Voxel &&other)
{
    slots = std::move(other.slots);
    node = std::move(other.node);
}

Voxel& Voxel::operator=(const Voxel &other)
{
    slots = other.slots;
    node = Node::copy_deep(other.node);

    return *this;
}

Voxel& Voxel::operator=(const Voxel &&other)
{
    slots = std::move(other.slots);
    node = std::move(other.node);

    return *this;
}

/* PUBLIC */

voxel_ptr Voxel::copy(voxel_ptr other_voxel)
{
    if (auto module_link = std::dynamic_pointer_cast<ModuleLink>(other_voxel))
        return ModuleLink::copy(module_link);

    if (auto pad_board = std::dynamic_pointer_cast<PadBoard>(other_voxel))
        return PadBoard::copy(pad_board);
        
    return voxel_ptr{ new Voxel(*other_voxel) };
}

side Voxel::getSideWithConnector(connector_weak_ptr connector) const
{
    ASSUMPTION(!connector.expired());

    for (auto &[side, weak_connector] : connections)
    {
        if (weak_connector.expired())
            continue;

        if (!weak_connector.owner_before(connector) && !connector.owner_before(weak_connector))
            return side;
    }

    return side::undefined;
}

connector_weak_ptr Voxel::getConnectorAt(side s) const
{
    ASSUMPTION(isConnectedAt(s));
    return connections.find(s)->second;
}

connector_weak_ptr Voxel::getOppositeConnector(connector_weak_ptr connector) const
{
    auto side_connector = std::ranges::find_if(connections, 
                                               [&connector](const auto &pair) { return !pair.second.owner_before(connector)
                                                                                       && !connector.owner_before(pair.second); } );
    ASSUMPTION(side_connector != connections.end());

    auto opposite_side = vec_to_enum(-enum_to_vec(side_connector->first));
    ASSUMPTION(connections.contains(opposite_side));

    return connections.at(opposite_side);
}

connector_weak_ptr Voxel::getSharedConnector() const
{
    ASSUMPTION(hasSharedRotation());
    
    for (auto &[side, connector_weak] : connections)
    {
        if (connector_weak.lock()->getType(true) == connector_type::shared_rotation)
            return connector_weak;
    }

    ASSUMPTION(false);
    return std::weak_ptr<SharedRotationConnector>();
}

connector_weak_ptr Voxel::getRotationConnector() const
{
    ASSUMPTION(hasRotationConnector());

    for (auto &[side, connector_weak] : connections)
    {
        if (connector_weak.lock()->getType(true) == connector_type::rotation
            && std::dynamic_pointer_cast<RotationConnector>(connector_weak.lock())->isHead())
            return connector_weak;
    }

    ASSUMPTION(false);
    return std::weak_ptr<RotationConnector>();
}

voxel_weak_ptr Voxel::getVoxelAt(side s) const
{
    ASSUMPTION(isConnectedAt(s));
    auto con = getConnectorAt(s).lock();

    if (!con->isFull())
        return con->getOppositeConnection();
    else
        return con->getOppositeConnection(s);
}

bool Voxel::hasSharedRotation() const
{
    for (auto [key, val] : connections)
    {
        if (std::dynamic_pointer_cast<SharedRotationConnector>(val.lock()))
            return true;
    }
    return false;
}

bool Voxel::hasRotationConnector() const
{
    for (auto [key, val] : connections)
    {
        if (const auto rot_con = std::dynamic_pointer_cast<RotationConnector>(val.lock())
            ; rot_con && rot_con->getConnection().first.lock().get() == this)
            return true;
    }
    return false;
}


bool Voxel::slotCompatible(const side s, const connector_type type) const
{
    ASSUMPTION(s != side::undefined);

    return type == connector_type::empty ? false : slots.find(s)->second.find(type)->second;
}

void Voxel::restrictSlots(const side s, connector_weak_ptr connector)
{
    auto con_type = connector.lock()->getType(voxel_weak_ptr());
    if (con_type == connector_type::fixed
        || con_type == connector_type::open)
        return;

    if (con_type == connector_type::shared_rotation)
        restrictSlotsSharedRotation(s);
    else if (con_type == connector_type::rotation)
        restrictSlotsRotation(s, connector);
    else
        ASSUMPTION(false);
}

void Voxel::releaseSlots(const side s, connector_weak_ptr connector)
{
    auto con_type = connector.lock()->getType(voxel_weak_ptr());
    if (con_type == connector_type::fixed
        || con_type == connector_type::open)
        return;

    if (con_type == connector_type::shared_rotation)
        releaseSlotsSharedRotation(s);
    else if (con_type == connector_type::rotation)
        releaseSlotsRotation(s, connector);
    else
        ASSUMPTION(false);
}

/* MODULE LINK */

ModuleLink::ModuleLink(const ModuleLink &other)
: Voxel{other} {}

ModuleLink::ModuleLink(const ModuleLink &&other)
: Voxel{other} {}

ModuleLink& ModuleLink::operator=(const ModuleLink &other)
{
    Voxel::operator=(other);
    return *this;
}

ModuleLink& ModuleLink::operator=(const ModuleLink &&other)
{
    Voxel::operator=(other);
    return *this;
}

std::shared_ptr<ModuleLink> ModuleLink::copy(const std::shared_ptr<ModuleLink> other_module_link)
{
    return std::shared_ptr<ModuleLink>{ new ModuleLink(*other_module_link) };
}

/* PAD BOARD */

PadBoard::PadBoard(const PadBoard &other)
: Voxel{other} {}

PadBoard::PadBoard(const PadBoard &&other)
: Voxel{other} {}

PadBoard& PadBoard::operator=(const PadBoard &other)
{
    Voxel::operator=(other);
    return *this;
}

PadBoard& PadBoard::operator=(const PadBoard &&other)
{
    Voxel::operator=(other);
    return *this;
}

std::shared_ptr<PadBoard> PadBoard::copy(const std::shared_ptr<PadBoard> other_module_link)
{
    return std::shared_ptr<PadBoard>{ new PadBoard(*other_module_link) };
}

}