#ifndef VOXEL_GRAPH_INCLUDED
#define VOXEL_GRAPH_INCLUDED

#include <common/node.hpp>
#include <rofi/connector.hpp>
#include <rofi/voxel.hpp>

#include <memory>
#include <vector>

namespace rofi
{
enum component { voxel, fixed_con, open_con, rotation_con, shared_rotation_con };

class VoxelGraph;

using voxel_graph_ptr = std::shared_ptr<VoxelGraph>;

class VoxelGraph
{
    std::vector<voxel_ptr> voxels;
    std::vector<connector_ptr> connections;

    void joinSingular(side s, connector_ptr connector, voxel_ptr voxel);
    void join(glm::vec3 local_direction, connector_ptr connector, voxel_ptr voxel);
    void disjoinSingular(connector_ptr connector, voxel_ptr voxel);
    void disjoinSingular(glm::vec3 direction, voxel_ptr voxel);
    void removeConnectorSingular(connector_ptr connector);

public:
    VoxelGraph() = default;
    VoxelGraph(std::vector<voxel_ptr> _voxels, std::vector<connector_ptr> _connections) : voxels{_voxels}, connections{_connections} {}

    const std::vector<voxel_ptr> &getVoxels() const { return voxels; }
    const std::vector<connector_ptr> &getConnections() const { return connections; }

    bool empty() const { return voxels.empty() && connections.empty(); }
    bool contains(voxel_ptr voxel);
    bool contains(connector_ptr connector);

    static voxel_graph_ptr copy(const voxel_graph_ptr other_voxel_graph, const std::vector<node_ptr> &new_nodes);
    static void copy_nodes(const std::vector<std::pair<voxel_ptr, voxel_ptr>> &voxel_pairs, 
                           const std::vector<std::pair<connector_ptr, connector_ptr>> &connection_pairs, 
                           const std::vector<node_ptr> &new_nodes);
    static void linkImportVoxelGraphToNodes(const voxel_graph_ptr vg);

    void extend(const voxel_graph_ptr other_voxel_graph);
    void erase(const voxel_graph_ptr other_voxel_graph);

    bool cellAvailable(node_ptr node) { return cellAvailable(node->getPositionWorld()); }
    bool cellAvailable(glm::vec3 position);

    voxel_ptr getVoxelAtPosition(node_ptr node) { return getVoxelAtPosition(node->getPositionWorld()); }
    voxel_ptr getVoxelAtPosition(glm::vec3 position);

    voxel_ptr addVoxel(node_ptr node);
    voxel_ptr addModuleLink(node_ptr node);
    voxel_ptr addPadBoard(node_ptr node);
    connector_ptr addConnector(node_ptr node, connector_type t);
    connector_ptr addConnector(node_ptr node, connector_type t, glm::vec3 rotation_axis);
    connector_ptr addConnector(node_ptr node, connector_type t, glm::vec3 rotation_axis, glm::vec3 highlight_direction);

    void removeVoxel(voxel_ptr voxel);
    void removeConnector(connector_ptr connector);

    void removeVoxels(const std::vector<voxel_ptr> &to_remove_voxels);
    void removeConnectors(const std::vector<connector_ptr> &to_remove_connectors);

    void join(connector_ptr connector, glm::vec3 connector_local_direction, voxel_ptr voxel);
    void join(connector_ptr connector, voxel_ptr voxel, glm::vec3 voxel_local_direction);
    void joinConnectors(connector_ptr fst_connector, connector_ptr snd_connector);
    // void join(connector_ptr connector, voxel_ptr fst_voxel, voxel_ptr snd_voxel);

    void disjoin(connector_ptr connector, voxel_ptr voxel);
    // void disjoin(glm::vec3 direction, voxel_ptr voxel);
    void disjoinConnectors(connector_ptr fst_connector, connector_ptr snd_connector);
    void disjoinAll(voxel_ptr voxel);
    void disjoinAll(connector_ptr connector);
    // void disjoin(connector_ptr connector, voxel_ptr fst_voxel, voxel_ptr snd_voxel);

    /* Disjoins all connectors from their voxels. When connected with ModuleLink, 
     * the ModuleLink disjoins from both its connectors */
    void disjoinAll();

    void joinImplicit();
};

// NOTE: now only by position, no rotation taken into account
side in_proximity(connector_ptr connector, voxel_ptr voxel); 

}

using voxel_graph_ptr = std::shared_ptr<rofi::VoxelGraph>;

#endif