#pragma once

#include <maps/libs/common/include/exception.h>

#include <boost/optional.hpp>

#include <vector>
#include <unordered_set>
#include <cstdint>
#include <unordered_map>
#include <memory>

namespace maps {
namespace wiki {
namespace topo {

class Node;
class Edge;

typedef uint64_t NodeID;
typedef uint64_t EdgeID;
typedef uint64_t FaceID;

typedef boost::optional<NodeID> OptionalNodeID;
typedef boost::optional<EdgeID> OptionalEdgeID;
typedef boost::optional<size_t> OptionalIndex;

typedef std::vector<NodeID> NodeIDVector;
typedef std::vector<EdgeID> EdgeIDVector;

typedef std::vector<const Node*> NodeConstPtrVector;
typedef std::vector<const Edge*> EdgeConstPtrVector;
typedef std::vector<Edge*> EdgePtrVector;

typedef std::unordered_set<NodeID> NodeIDSet;
typedef std::unordered_set<EdgeID> EdgeIDSet;
typedef std::unordered_set<FaceID> FaceIDSet;

class SourceEdgeID
{
public:
    SourceEdgeID(EdgeID id, bool exists)
        : id_(id)
        , exists_(exists)
    {}

    bool operator == (const SourceEdgeID& oth) const
    {
        return id_ == oth.id_ && exists_ == oth.exists_;
    }

    bool operator != (const SourceEdgeID& oth) const
    {
        return !(*this == oth);
    }

    EdgeID id() const { return id_; }
    bool exists() const { return exists_; }

private:
    EdgeID id_;
    bool exists_;
};

template <class T>
struct Limits
{
    Limits(const T& min)
        : min(min)
    {}

    Limits(const T& min, const T& max)
        : min(min)
        , max(max)
    {
        REQUIRE(min <= max, "Incorrect limits: min " << min << ", max " << max);
    }

    T min;
    boost::optional<T> max;
};

enum class MergeOfOverlappedEdgesPolicy {
    Allowed,
    Forbidden
};

class TopologyRestrictions
{
public:
    TopologyRestrictions(
        double tolerance,
        double junctionGravity,
        double vertexGravity,
        double groupJunctionGravity,
        double groupJunctionSnapVertexGravity,
        const Limits<double>& edgeSegmentLengthLimits,
        const Limits<double>& edgeLengthLimits,
        boost::optional<size_t> maxIntersectionsWithEdge,
        boost::optional<size_t> maxIntersectedEdges,
        MergeOfOverlappedEdgesPolicy mergeOfOverlappedEdgesPolicy
    );

    TopologyRestrictions(const TopologyRestrictions&);
    TopologyRestrictions(TopologyRestrictions&&);
    TopologyRestrictions& operator = (const TopologyRestrictions&);
    TopologyRestrictions& operator = (TopologyRestrictions&&);

    ~TopologyRestrictions();

    double tolerance() const;
    double junctionGravity() const;
    double vertexGravity() const;
    double groupJunctionGravity() const;
    double groupJunctionSnapVertexGravity() const;
    const Limits<double>& edgeSegmentLengthLimits() const;
    const Limits<double>& edgeLengthLimits() const;
    boost::optional<size_t> maxIntersectionsWithEdge() const;
    boost::optional<size_t> maxIntersectedEdges() const;
    bool isMergeOfOverlappedEdgesAllowed() const;

    void setMaxIntersectionsWithEdge(boost::optional<size_t> count);
    void setMaxIntersectedEdges(boost::optional<size_t> count);

private:
    class TopologyRestrictionsImpl;

    std::unique_ptr<TopologyRestrictionsImpl> impl_;
};

enum class IncidenceType { Start, End };

struct Incidence {
    Incidence(EdgeID edge, NodeID node, IncidenceType type)
        : edge(edge)
        , node(node)
        , type(type)
    {}

    EdgeID edge;
    NodeID node;
    IncidenceType type;
};

typedef std::vector<std::pair<EdgeID, IncidenceType>> IncidentEdges;

std::ostream& operator << (std::ostream& out, const IncidenceType& incidenceType);

struct IncidentNodes {
    explicit IncidentNodes(NodeID start = 0, NodeID end = 0)
        : start(start)
        , end(end)
    {}

    NodeID start;
    NodeID end;
};

bool operator==(const IncidentNodes& lhs, const IncidentNodes& rhs);

typedef std::vector<Incidence> IncidencesVector;
typedef std::unordered_map<NodeID, IncidentEdges> IncidencesByNodeMap;
typedef std::unordered_map<EdgeID, IncidentNodes> IncidencesByEdgeMap;

typedef std::unordered_map<EdgeID, FaceIDSet> FacesByEdgeMap;

struct FaceDiff {
    EdgeIDSet added;
    EdgeIDSet changed;
    EdgeIDSet removed;
};

/**
 * Represents circuits in graph, where
 *   edges = [e_1, ... , e_n],
 *   nodes = [v_1, ... , v_n+1] (optional),
 *   circuit = (v_1, e_1, v_2, ... , v_n, e_n, v_n+1).
 */
struct Circuit
{
    EdgeIDVector edges;
    boost::optional<NodeIDVector> nodes;
};

} // namespace topo
} // namespace wiki
} // namespace maps
