#ifndef CONNECTOR_INCLUDED
#define CONNECTOR_INCLUDED

#include <common/node.hpp>
#include <common/objectbase.hpp>
#include <utils/assumptions.hpp>

#include <memory>
#include <utility>

namespace rofi
{
class Connector;
class Voxel;
using voxel_ptr = std::shared_ptr<Voxel>;
using voxel_weak_ptr = std::weak_ptr<Voxel>;
using connector_ptr = std::shared_ptr<rofi::Connector>;
using connector_weak_ptr = std::weak_ptr<rofi::Connector>;

    enum class side { x_pos, x_neg, y_pos, y_neg, z_pos, z_neg, undefined };
    enum class connector_type {fixed, open, rotation, shared_rotation, empty};
    enum cardinal { North, East, South, West, Invalid };

class Connector : public ObjectBase
{
    node_ptr node;

    // NOTE: friends do not get inherited
    friend class VoxelGraph;
protected:
    std::pair<voxel_weak_ptr, voxel_weak_ptr> connection = std::pair(voxel_weak_ptr(), voxel_weak_ptr());
    std::pair<side, side> voxel_side = std::pair(side::undefined, side::undefined);

    virtual void connectVoxel(side s, voxel_weak_ptr voxel);
    virtual void disconnectVoxel(voxel_weak_ptr voxel);
    virtual void disconnectVoxel(side s);

    Connector(const Connector &other);
    Connector(const Connector &&other);

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

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

    static connector_ptr copy(connector_ptr other_connector);
    static bool shares_module_link(connector_ptr fst_connector, connector_ptr snd_connector);
    static cardinal get_mutual_orientation(connector_ptr source_connector, connector_ptr destination_connector);

    bool isFull() const { return !connection.first.expired() && !connection.second.expired(); }
    bool isEmpty() const { return connection.first.expired() && connection.second.expired(); }
    virtual bool isStranded() const { return isEmpty(); }

    bool hasModuleLink() const;
    bool isOnPad() const;

    node_ptr getNode() { return node; }
    voxel_weak_ptr getOppositeConnection();
    voxel_weak_ptr getOppositeConnection(side s);
    const std::pair<voxel_weak_ptr, voxel_weak_ptr> &getConnection() { return connection; }
    const std::pair<side, side> &getVoxelSide() { return voxel_side; }
    voxel_weak_ptr getModuleLink() const;

    virtual const connector_type getType(voxel_weak_ptr) const = 0;
    virtual const connector_type getType(bool from_fst_voxel) const = 0;

    virtual ~Connector() = default;
};

class FixedConnector : public Connector
{
    FixedConnector(const FixedConnector &other);
    FixedConnector(const FixedConnector &&other);

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

public:
    using Connector::Connector;

    static std::shared_ptr<FixedConnector> copy(std::shared_ptr<FixedConnector> other_fixed_connector);

    const connector_type getType(voxel_weak_ptr) const override { return connector_type::fixed; }
    const connector_type getType(bool from_fst_voxel) const override { return connector_type::fixed; }
    bool isStranded() const override { return !isFull(); }
};

class OpenConnector : public Connector
{
    OpenConnector(const OpenConnector &other);
    OpenConnector(const OpenConnector &&other);

    OpenConnector& operator=(const OpenConnector &other);
    OpenConnector& operator=(const OpenConnector &&other);
    
public:
    using Connector::Connector;

    static std::shared_ptr<OpenConnector> copy(std::shared_ptr<OpenConnector> other_open_connector);

    const connector_type getType(voxel_weak_ptr) const override { return connector_type::open; }
    const connector_type getType(bool from_fst_voxel) const override { return connector_type::open; }
    bool isStranded() const override { return false; }
};

class RotationConnector : public Connector
{
    glm::vec3 rotation_axis;

    bool is_head;
    std::pair<connector_weak_ptr, connector_weak_ptr> bound_connectors = std::pair(connector_weak_ptr(), connector_weak_ptr());

    connector_type semantic_type = connector_type::empty;
    float neg_rotation_limit = -90.0f;
    float pos_rotation_limit = 90.0f;
    float current_rotation = 0.0f;

    RotationConnector(const RotationConnector &other);
    RotationConnector(const RotationConnector &&other);

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

public:
    using Connector::Connector;
    RotationConnector(node_ptr _node, glm::vec3 _rotation_axis, bool _is_head) 
    : Connector(_node), rotation_axis{_rotation_axis}, is_head{_is_head} {}
    RotationConnector(glm::vec3 position, glm::quat rotation, glm::vec3 _rotation_axis, bool _is_head)
    : Connector(position, rotation), rotation_axis{_rotation_axis}, is_head{_is_head} {}

    static std::shared_ptr<RotationConnector> copy(std::shared_ptr<RotationConnector> other_rotation_connector);

    const connector_type getType(voxel_weak_ptr from_voxel) const override
    {
        if (connection.first.expired())
        return connector_type::rotation;

        if (from_voxel.expired())
            return semantic_type;

        // This expects, that host Voxel of Rotation connector is always first in pair
        return from_voxel.lock() == connection.first.lock() ? connector_type::rotation : semantic_type;
    }
    const connector_type getType(bool from_fst_voxel) const override { return from_fst_voxel == true ? connector_type::rotation : semantic_type; }
    void setSemanticType(connector_type _semantic_type) { semantic_type = _semantic_type; }

    float &getNegRotationLimit() { return neg_rotation_limit; }
    float &getPosRotationLimit() { return pos_rotation_limit; }

    void setNegRotationLimit(float degree_angle) { neg_rotation_limit = degree_angle; }
    void setPosRotationLimit(float degree_angle) { pos_rotation_limit = degree_angle; }

    float &getCurrentRotation() { return current_rotation; }
    void setCurrentRotation(float degree_angle) { current_rotation = degree_angle; }

    bool isStranded() const override { return connection.first.expired(); }

    bool isHead() const { return is_head; }
    bool isParentVoxel(voxel_weak_ptr voxel) const;

    const glm::vec3 &getRotationAxis() const { return rotation_axis; }
    void setRotationAxis(glm::vec3 &_rotation_axis) { rotation_axis = _rotation_axis; }

    const std::pair<connector_weak_ptr, connector_weak_ptr> &getBoundConnectors() const { return bound_connectors; }
    void setBoundConnectors(connector_weak_ptr fst, connector_weak_ptr snd) { bound_connectors.first = fst, bound_connectors.second = snd; }

    const connector_weak_ptr &getHead() const { return bound_connectors.first; }
};

class SharedRotationConnector : public Connector
{
    glm::vec3 rotation_axis;
    // TO DO: make user costumizable
    float modulo = 360.0f;

    SharedRotationConnector(const SharedRotationConnector &other);
    SharedRotationConnector(const SharedRotationConnector &&other);

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

public:
    using Connector::Connector;
    SharedRotationConnector(node_ptr _node, glm::vec3 _rotation_axis) 
    : Connector(_node), rotation_axis{_rotation_axis} {}
    SharedRotationConnector(glm::vec3 position, glm::quat rotation, glm::vec3 _rotation_axis)
    : Connector(position, rotation), rotation_axis{_rotation_axis} {}

    static std::shared_ptr<SharedRotationConnector> copy(std::shared_ptr<SharedRotationConnector> other_shared_rotation_connector);

    const connector_type getType(voxel_weak_ptr) const override { return connector_type::shared_rotation; }
    const connector_type getType(bool from_fst_voxel) const override { return connector_type::shared_rotation; }
    bool isStranded() const override { return !isFull(); }

    const float &getModulo() const { return modulo; }
    void setModulo(float _modulo) { modulo = _modulo; }    

    const glm::vec3 &getRotationAxis() const { return rotation_axis; }
    void setRotationAxis(glm::vec3 _rotation_axis) { rotation_axis = _rotation_axis; }
};

side vec_to_enum(glm::vec3 local_direction);
glm::vec3 enum_to_vec(side s);
cardinal degree_to_cardinal(float degree_angle);
float cardinal_to_degree(cardinal orientation);

}

using connector_ptr = std::shared_ptr<rofi::Connector>;

#endif