#ifndef VOXEL_INCLUDED
#define VOXEL_INCLUDED

#include <common/node.hpp>
#include <common/objectbase.hpp>
#include <rofi/connector.hpp>
#include <utils/math.hpp>

#include <map>
#include <memory>

namespace rofi
{
class Connector;
side vec_to_enum(glm::vec3 local_direction);

using connector_weak_ptr = std::weak_ptr<Connector>;

class Voxel;
using voxel_ptr = std::shared_ptr<Voxel>;

class Voxel : public ObjectBase
{
    using enum connector_type;
    std::map<const side, std::map<const rofi::connector_type, bool>> slots = {
    {side::x_pos, {{fixed, true}, {open, true}, {rotation, true}, {shared_rotation, true}}},
    {side::x_neg, {{fixed, true}, {open, true}, {rotation, true}, {shared_rotation, true}}},
    {side::y_pos, {{fixed, true}, {open, true}, {rotation, true}, {shared_rotation, true}}},
    {side::y_neg, {{fixed, true}, {open, true}, {rotation, true}, {shared_rotation, true}}},
    {side::z_pos, {{fixed, true}, {open, true}, {rotation, true}, {shared_rotation, true}}},
    {side::z_neg, {{fixed, true}, {open, true}, {rotation, true}, {shared_rotation, true}}}
    };

protected:
    node_ptr node;
    std::map<side, connector_weak_ptr> connections;

    friend class VoxelGraph;

    void connectConnection(side s, connector_weak_ptr connector);
    void disconnectConnection(side s);
    void disconnectConnection(connector_weak_ptr connector);

    const std::map<side, connector_weak_ptr> &getConnections() { return connections; }

    void restrictSlotsRotation(const side s, connector_weak_ptr connector);
    void restrictSlotsSharedRotation(const side s);

    void releaseSlotsRotation(const side s, connector_weak_ptr connector);
    void releaseSlotsSharedRotation(const side s);

    Voxel(const Voxel &other);
    Voxel(const Voxel &&other);

    Voxel& operator=(const Voxel &other);
    Voxel& operator=(const Voxel &&other);

public:
    Voxel(node_ptr _node) : node{_node} {}
    Voxel(glm::vec3 position, glm::quat rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) : node{Node::create(position, rotation)} {}

    static voxel_ptr copy(voxel_ptr other_voxel);
    
    virtual ~Voxel() = default;

    node_ptr getNode() const { return node; }
    bool isConnectedAt(side s) const { return connections.contains(s); }
    bool isConnectedAt(glm::vec3 side_direction) const { return isConnectedAt(vec_to_enum(side_direction)); }

    side getSideWithConnector(connector_weak_ptr connector) const;

    connector_weak_ptr getConnectorAt(side s) const;
    connector_weak_ptr getConnectorAt(glm::vec3 side_direction) const { return getConnectorAt(vec_to_enum(side_direction)); };
    connector_weak_ptr getOppositeConnector(connector_weak_ptr connector) const;
    connector_weak_ptr getSharedConnector() const;
    connector_weak_ptr getRotationConnector() const;

    voxel_weak_ptr getVoxelAt(side s) const;
    voxel_weak_ptr getVoxelAt(glm::vec3 side_direction) const { return getVoxelAt(vec_to_enum(side_direction)); }

    bool hasNoConnections() const { return connections.empty(); }
    bool hasSharedRotation() const;
    bool hasRotationConnector() const;

    bool slotCompatible(const side s, const connector_type type) const;
    bool slotCompatible(const glm::vec3 side_direction, const connector_type type) const { return slotCompatible(vec_to_enum(side_direction), type); }

    void restrictSlots(const side s, connector_weak_ptr connector);
    void releaseSlots(const side s, connector_weak_ptr connector);

};

class ModuleLink : public Voxel
{
    using enum connector_type;
    std::map<const side, std::map<const rofi::connector_type, bool>> slots = {
    {side::x_pos, {{fixed, false}, {open, false}, {rotation, false}, {shared_rotation, false}}},
    {side::x_neg, {{fixed, false}, {open, false}, {rotation, false}, {shared_rotation, false}}},
    {side::y_pos, {{fixed, false}, {open, false}, {rotation, false}, {shared_rotation, false}}},
    {side::y_neg, {{fixed, false}, {open, false}, {rotation, false}, {shared_rotation, false}}},
    {side::z_pos, {{fixed, true}, {open, true}, {rotation, false}, {shared_rotation, false}}},
    {side::z_neg, {{fixed, true}, {open, true}, {rotation, false}, {shared_rotation, false}}}
    };

    ModuleLink(const ModuleLink &other);
    ModuleLink(const ModuleLink &&other);

    ModuleLink& operator=(const ModuleLink &other);
    ModuleLink& operator=(const ModuleLink &&other);

public:
    friend class VoxelGraph;
    using Voxel::Voxel;

    static std::shared_ptr<ModuleLink> copy(const std::shared_ptr<ModuleLink> other_module_link); 

    std::pair<connector_weak_ptr, connector_weak_ptr> getInterModuleConnectors() const { return {connections.begin()->second, std::next(connections.begin())->second}; }
};

class PadBoard : public Voxel
{
    using enum connector_type;
    std::map<const side, std::map<const rofi::connector_type, bool>> slots = {
    {side::x_pos, {{fixed, false}, {open, false}, {rotation, false}, {shared_rotation, false}}},
    {side::x_neg, {{fixed, false}, {open, false}, {rotation, false}, {shared_rotation, false}}},
    {side::y_pos, {{fixed, false}, {open, true}, {rotation, false}, {shared_rotation, false}}},
    {side::y_neg, {{fixed, false}, {open, false}, {rotation, false}, {shared_rotation, false}}},
    {side::z_pos, {{fixed, false}, {open, false}, {rotation, false}, {shared_rotation, false}}},
    {side::z_neg, {{fixed, false}, {open, false}, {rotation, false}, {shared_rotation, false}}}
    };


    PadBoard(const PadBoard &other);
    PadBoard(const PadBoard &&other);

    PadBoard& operator=(const PadBoard &other);
    PadBoard& operator=(const PadBoard &&other);

public:
    friend class VoxelGraph;
    using Voxel::Voxel;

    static std::shared_ptr<PadBoard> copy(const std::shared_ptr<PadBoard> other_module_link); 
};

}

using voxel_ptr = std::shared_ptr<rofi::Voxel>;

#endif