#include "intersector.h"

#include "geom_io.h"
#include "util.h"
#include "validation.h"

#include <maps/libs/geolib/include/closest_point.h>
#include <maps/libs/geolib/include/intersection.h>
#include <maps/libs/geolib/include/segment.h>

#include <algorithm>


namespace maps {
namespace wiki {
namespace topo {
namespace geom {


using namespace geolib3;


Intersector::Intersector(const Graph& graph, const TopologyRestrictions& restrictions,
    DeleteNodeChecker deleteNodeChecker)
    : graph_(graph)
    , restrictions_(restrictions)
    , deleteNodeChecker_(deleteNodeChecker)
{}


Intersector::Events Intersector::operator()(const SourceEdgeID& sourceEdgeId,
    const Polyline2& polyline, const PointsVector& splitRequests) const
{
    REQUIRE(
        polyline.pointsNumber() >= 2,
        "Edited polyline mush have 2 points at least"
    );

    const SnapPolyline alignedPolyline = align(sourceEdgeId, polyline, splitRequests);
    checkPolyline(alignedPolyline.geom());
    Events events = makeEvents(sourceEdgeId, alignedPolyline, splitRequests);

    checkResult(events);
    return events;
}


NodeIDSet Intersector::getIgnoredNodesToSnapEndPoint(const SourceEdgeID& edgeId,
    const Point2& newStart, const Point2& newEnd) const
{
    NodeIDSet ignoredNodeIds;

    if (!edgeId.exists() || !graph_.edgeExists(edgeId.id())) {
        return ignoredNodeIds;
    }

    const Edge& edge = getEdge(edgeId.id());

    const Node& start = getNode(edge.startNode());
    if (start.degree() == 1 && !near(start.pos(), newStart, tolerance())) {
        ignoredNodeIds.insert(start.id());
    }

    const Node& end = getNode(edge.endNode());
    if (end.degree() == 1 && !near(end.pos(), newEnd, tolerance())) {
        ignoredNodeIds.insert(end.id());
    }

    return ignoredNodeIds;
}

NodeIDSet Intersector::getIgnoredNodesToSnapInnerPoint(const SourceEdgeID& sourceEdgeId,
    const PointsVector& splitRequests) const
{
    NodeIDSet ignoredNodeIds;

    if (!sourceEdgeId.exists() || !graph_.edgeExists(sourceEdgeId.id())) {
        return ignoredNodeIds;
    }

    // Inner points must not snap to edge nodes.
    const Edge& edge = getEdge(sourceEdgeId.id());

    // Special case of split points at edge end point.
    // We must split edited polyline at this point
    // and keep node id for this split.
    auto checkSplitRequests = [&splitRequests, this](const Node& node) -> bool
    {
        return std::none_of(
            splitRequests.begin(), splitRequests.end(),
            [&node, this](const Point2& point) -> bool
            {
                return near(point, node.pos(), junctionGravity());
            }
        );
    };

    const Node& start = getNode(edge.startNode());
    if (start.degree() == 1 && checkSplitRequests(start)) {
        ignoredNodeIds.insert(start.id());
    }

    const Node& end = getNode(edge.endNode());
    if (end.degree() == 1 && checkSplitRequests(end)) {
        ignoredNodeIds.insert(end.id());
    }

    return ignoredNodeIds;
}

NodeIDSet Intersector::getIgnoredCloseNodes(const SourceEdgeID& sourceEdgeId,
    const SnapPolyline& partiallyAlignedPolyline) const
{
    NodeIDSet ignoredNodeIds;

    if (!sourceEdgeId.exists() || !graph_.edgeExists(sourceEdgeId.id())) {
        return ignoredNodeIds;
    }

    const Edge& edge = getEdge(sourceEdgeId.id());
    const Node& start = getNode(edge.startNode());
    if (start.degree() == 1) {
        ignoredNodeIds.insert(start.id());
    }
    const Node& end = getNode(edge.endNode());
    if (end.degree() == 1) {
        ignoredNodeIds.insert(end.id());
    }

    for (const auto& point: partiallyAlignedPolyline.points()) {
        if (point.node()) {
            ignoredNodeIds.insert(*point.node());
        }
    }

    return ignoredNodeIds;
}


SnapPoint Intersector::snapEdgePoint(const SourceEdgeID& sourceEdgeId,
    const Point2& point, const NodeIDSet& ignoredNodeIds, double gravity) const
{
    const OptionalNodeID nodeId = getNodeIdNear(point, gravity, ignoredNodeIds);
    if (nodeId) {
        return SnapPoint{getNode(*nodeId)};
    }

    const OptionalEdgeID edgeId = getEdgeIdNear(point, gravity, {sourceEdgeId.id()});
    if (edgeId) {
        return snapPointToEdge(point, getEdge(*edgeId), gravity);
    }

    return SnapPoint{point};
}

SnapPoint Intersector::snapPointToEdge(const Point2& point, const Edge& edge,
    double gravity) const
{
    const Polyline2& polyline = edge.geom();
    const Point2& projection = closestPoint(polyline, point);
    const size_t index = polyline.closestNodeIndex(projection);
    const Point2& vertex = polyline.pointAt(index);
    if (near(vertex, projection, gravity)) { // try snap point to vertex
        if (index == 0) { // it's start node
            return SnapPoint{getNode(edge.startNode())};
        }
        if (index + 1 == polyline.pointsNumber()) {    // it's end node
            return SnapPoint{getNode(edge.endNode())};
        }
        return SnapPoint{edge, index};
    } // snap to segments
    return SnapPoint{
        edge,
        polyline.closestPointSegmentIndex(point), projection
    };
}

SnapPointVector Intersector::findClosePoints(const SourceEdgeID& sourceEdgeId,
    const Polyline2& polyline, const NodeIDSet& ignoredNodeIds) const
{
    SnapPointVector snaps;
    for (const NodeID& nodeId: findNodeIdsNear(polyline, junctionGravity(), ignoredNodeIds)) {
        const Node& node = getNode(nodeId);
        if (node.degree() == 1 || near(polyline, node.pos(), tolerance()))
        {
            snaps.emplace_back(node);
        }
    }

    for (const EdgeID& edgeId: findEdgeIdsNear(polyline, tolerance(), {sourceEdgeId.id()})) {
        const Edge& edge = getEdge(edgeId);
        const PointsVector& points = edge.geom().points();
        // See only inner points, because end vertces are processed as nodes.
        for (size_t i = 1; i + 1 < points.size(); ++i) {
            const Point2& point = points[i];
            if (near(point, polyline, tolerance())) {
                snaps.emplace_back(edge, i);
            }
        }
    }

    return snaps;
}

SnapPolyline Intersector::align(const SourceEdgeID& sourceEdgeId, const Polyline2& polyline,
    const PointsVector& splitRequests) const
{
    const PointsVector& points = polyline.points();
    Point2 start = points.front();
    Point2 end = points.back();

    if (isClosed(polyline, junctionGravity())) { // If polyline is closed then align begin and end.
        end = start;
    }

    // Snap endpoints:
    const NodeIDSet ignoredNodesToSnapEndPoint =
        getIgnoredNodesToSnapEndPoint(sourceEdgeId, start, end);

    SnapPoint startSnap = snapEdgePoint(
        sourceEdgeId,
        start,
        ignoredNodesToSnapEndPoint,
        junctionGravity()
    );
    SnapPoint endSnap = snapEdgePoint(
        sourceEdgeId,
        end,
        ignoredNodesToSnapEndPoint,
        junctionGravity()
    );

    // Check after snapping:
    if (near(startSnap.geom(), endSnap.geom()) && polyline.pointsNumber() >= 3) {
        endSnap = startSnap;
    }

    SnapPointVector snaps;
    snaps.reserve(polyline.pointsNumber());
    snaps.push_back(startSnap);


    // Snap internal points
    const NodeIDSet ignoredNodesToSnapInnerPoint =
        getIgnoredNodesToSnapInnerPoint(sourceEdgeId, splitRequests);

    for (size_t i = 1; i + 1 < points.size(); ++i) { // Snap all internal vertices.
        const SnapPoint snap = snapEdgePoint(
            sourceEdgeId,
            points[i],
            ignoredNodesToSnapInnerPoint,
            vertexGravity()
        );
        snaps.push_back(snap);
    }

    snaps.push_back(endSnap);

    // Optimized search of closed nodes.
    SnapPolyline partiallyAlignedPolyline(snaps);

    const SnapPointVector closeSnaps = findClosePoints(
        sourceEdgeId,
        partiallyAlignedPolyline.geom(),
        getIgnoredCloseNodes(sourceEdgeId, partiallyAlignedPolyline)
    );

    // suplement polyline with close points and return result
    return supplementPolylineWithPoints(partiallyAlignedPolyline, closeSnaps, tolerance());
}


SnapPointVector Intersector::intersect(const SnapPoint& start, const SnapPoint& end,
    const Edge& intersectedEdge, size_t segmentIndex) const
{
    SnapPointVector points;
    const Segment2& segment = intersectedEdge.geom().segmentAt(segmentIndex);
    const Segment2 snapSegment{start.geom(), end.geom()};

    if (start.onSegment(intersectedEdge, segmentIndex)) {
        points.push_back(start);
    }

    if (end.onSegment(intersectedEdge, segmentIndex)) {
        points.push_back(end);
    }

    if (points.empty()) {
        const PointsVector common = intersection(segment, snapSegment);
        if (!common.empty()) {
            points.push_back(snapPointToEdge(common[0], intersectedEdge, EPS));
        }
    }

    order( // order along segment
        points,
        [&segment](const SnapPoint& point) -> double
        {
            return distance(segment.start(), point.geom());
        }
    );

    return points;
}

// It is assumed that self-intersection of polyline possible only at the end points.
SnapPolylineVector Intersector::intersect(const SnapPolyline& editedPolyline,
    const Edge& intersectedEdge, size_t segmentIndex) const
{
    if (editedPolyline.isPoint() && editedPolyline.start().onEdge(intersectedEdge)) {
        return {editedPolyline};
    }

    std::vector<SnapPointVector> intersections;
    for (size_t i = 0; i + 1 < editedPolyline.size(); ++i) {
        SnapPointVector points = intersect(
            editedPolyline[i], editedPolyline[i + 1], intersectedEdge, segmentIndex
        );

        if (!points.empty()) {
            intersections.emplace_back(std::move(points));
        }
    }

    order( // order along segment
        intersections,
        [&](const SnapPointVector& points) -> double
        {
            return distance(
                intersectedEdge.geom().segmentAt(segmentIndex).start(),
                points.front().geom()
            );
        }
    );

    std::vector<SnapPolyline> splits;
    for (auto& points: intersections) {
        const SnapPolyline split{points};
        if (!splits.empty()) {
            SnapPolyline& lastSplit = splits.back();
            if (points.size() == 1 && lastSplit.start() == points.front()) {
                continue;
            }
            if (lastSplit.end() == points.front()) {
                if (points.size() == 1) {
                    continue;
                }

                if (lastSplit.size() == 1 || points.front() != editedPolyline.end()) {
                    lastSplit.extend(SnapPolyline{std::move(points)});
                    continue;
                }
            }
        }
        splits.emplace_back(std::move(points));
    }

    return splits;
}


SnapPolylineVector Intersector::intersect(const SnapPolyline& editedPolyline,
    const Edge& intersectedEdge) const
{
    if (editedPolyline.isPoint() && editedPolyline.start().onEdge(intersectedEdge)) {
        return {editedPolyline};
    }

    SnapPolylineVector splits;

    for (size_t i = 0; i < intersectedEdge.geom().segmentsNumber(); ++i) {
        const SnapPolylineVector polylines = intersect(editedPolyline, intersectedEdge, i);

        if (polylines.empty()) {
            continue;
        }

        auto begin = polylines.begin();
        if (splits.size()) {
            SnapPolyline& lastSplit = splits.back();

            // join two polylines with shares point
            if (lastSplit.end() == polylines.front().start()
                    && lastSplit.end() != editedPolyline.end())
            {
                lastSplit.extend(polylines.front());
                ++begin;
            }
        }

        splits.insert(splits.end(), begin, polylines.end());
    }

    return splits;
}


namespace {

// Common parts of edited edge and intersected edges.
// Also end points of each edge are included.
// All splits parts are ordered along respective edge.
struct CommonSplitParts {
    SnapPolylineVector editedEdgeSplitParts;
    EdgeIdToSplitParts intersectedEdgesSplitParts;
    SnapPolyline polylineWithSplitRequest;
};

bool needAddStartSplitFor(const SnapPolyline& polyline, const SnapPolylineVector& splits)
{
    if (splits.empty()) {
        return true;
    }

    const SnapPolyline& split = splits.front();
    const SnapPoint& start = polyline.start();

    const SnapPoint& point = (split.isPoint() || polyline.hasSameOrder(split))
        ? split.start()
        : split.end();

    return start != point;
}

bool needAddEndSplitFor(const SnapPolyline& polyline, const SnapPolylineVector& splits)
{
    REQUIRE(
        !polyline.empty(),
        "Start point must be added already, there is some mistake."
    );

    if (splits.size() < 2 && splits.back().isPoint()) {
        return true;
    }

    const SnapPoint& end = polyline.end();
    const SnapPolyline& lastSplit = splits.back();
    const SnapPoint& point = (lastSplit.isPoint() || polyline.hasSameOrder(lastSplit))
        ? lastSplit.end()
        : lastSplit.start();

    return end != point;
}

void removeDuplicateSplits(SnapPolylineVector& splits)
{
    if (splits.size() < 2) {
        return;
    }
    auto last = splits.begin();
    for (auto it = last + 1; it != splits.end(); ++it) {
        if (it->isPoint() &&
            (last->start() == it->start() || last->end() == it->start()))
        {
            continue;
        }
        if (last->isPoint() &&
            (last->start() == it->start() || last->start() == it->end()))
        {
            *last = std::move(*it);
            continue;
        }
        ++last;
        *last = std::move(*it);
    }
    splits.erase(last + 1, splits.end());
}

} // end of anonymous namespace

OptionalSnapPoint Intersector::tryAddSplitPointForRequest(SnapPolylineVector& editedEdgeSplitParts,
    EdgeIdToSplitParts& intersectedEdgesSplitParts, const SnapPolyline& polyline,
    const Point2& splitRequest) const
{
    auto trySnapToVertex = [this](const Polyline2& polyline, const Point2& point)
    {
        const size_t index = polyline.closestNodeIndex(point);
        return near(polyline.pointAt(index), point, junctionGravity())
            ? OptionalIndex(index)
            : boost::none;
    };

    auto it = std::find_if(
        editedEdgeSplitParts.begin(), editedEdgeSplitParts.end(),
        [&](const SnapPolyline& polyline) -> bool
        {
            return near(polyline.geom(), splitRequest, junctionGravity());
        }
    );

    if (it == editedEdgeSplitParts.end()) {
        const OptionalIndex vertexIndex = trySnapToVertex(polyline.geom(), splitRequest);
        const SnapPoint snapPoint = vertexIndex
            ? polyline[*vertexIndex]
            : SnapPoint{splitRequest};
        editedEdgeSplitParts.push_back(SnapPolyline{snapPoint});
        return OptionalSnapPoint{snapPoint};
    }

    if(it->isPoint()) {
        return boost::none;
    }

    const OptionalIndex vertexIndex = trySnapToVertex(it->geom(), splitRequest);
    if (vertexIndex && (*vertexIndex == 0 || *vertexIndex == it->size() - 1))  {
        return boost::none;
    }

    EdgeIDSet edgeIds;
    for (const auto& point: it->points()) {
        if (point.edge()) {
            edgeIds.insert(*point.edge());
            break;
        }
    }
    if (edgeIds.empty()) {
        if (it->start().node()) {
            const Node& start = getNode(*it->start().node());
            for (const auto& id: start.incidentEdgeIds()) {
                edgeIds.insert(id);
            }
        }
    }

    for (const auto edgeId: edgeIds) {
        SnapPolylineVector& intersectedEdgesSplit = intersectedEdgesSplitParts[edgeId];
        auto theSameAsIt = std::find(
            intersectedEdgesSplit.begin(), intersectedEdgesSplit.end(), *it
        );
        if (theSameAsIt == intersectedEdgesSplit.end()) {
            continue;
        }

        SnapPointVector first, second;
        if (vertexIndex) {
            first = {it->points().begin(), it->points().begin() + *vertexIndex + 1};
            second = {it->points().begin() + *vertexIndex, it->points().end()};
        } else {
            const Edge& edge = getEdge(edgeId);
            const SnapPoint middle {
                edge,
                edge.geom().closestPointSegmentIndex(splitRequest),
                splitRequest
            };
            const size_t segmentIndex = it->geom().closestPointSegmentIndex(splitRequest);
            first = {it->points().begin(), it->points().begin() + segmentIndex + 1};
            first.push_back(middle);
            second = {middle};
            second.insert(
                second.end(),
                it->points().begin() + segmentIndex + 1,
                it->points().end()
            );
        }

        *it = SnapPolyline{first};
        *theSameAsIt = *it;
        editedEdgeSplitParts.emplace_back(second);
        intersectedEdgesSplit.emplace_back(editedEdgeSplitParts.back());

        return OptionalSnapPoint{first.back()};
    }

    throw RuntimeError() << "Can't find common polyline of intersected edge";
}

CommonSplitParts Intersector::findCommonSplitParts(const SourceEdgeID& sourceEdgeId,
    const SnapPolyline& polyline, const PointsVector& splitRequests) const
{
    SnapPolylineVector editedEdgeSplitParts;
    EdgeIdToSplitParts intersectedEdgesSplitParts;

    // Find all intersections with other edges:
    for (const auto& edge: graph_.edges()) {
        if (edge.id() == sourceEdgeId.id()) {
            continue;
        }

        const SnapPolylineVector& intersections = intersect(polyline, edge);
        for (const auto& intersection: intersections) {
            if (!restrictions_.isMergeOfOverlappedEdgesAllowed() && intersection.size() > 1) {
                throw GeomProcessingErrorWithLocation(
                    ErrorCode::MergeOfOverlappedEdgesForbidden,
                    center(intersection.geom())
                ) << "overlapping with edge id " << edge.id()
                  << ", but merge of overlapped edges is forbbiden" ;
            }

            editedEdgeSplitParts.push_back(intersection);
            intersectedEdgesSplitParts[edge.id()].push_back(intersection);
        }
    }

    // Find intersections with nodes which degree = 0:
    for (const auto& point: polyline.points()) {
        if (point.node() && getNode(*point.node()).degree() == 0) {
            editedEdgeSplitParts.push_back(SnapPolyline{point});
        }
    }

    // Add split requests
    SnapPointVector alignedSplitRequests;
    for (const auto& splitRequest: splitRequests) {
        if (!near(splitRequest, polyline.geom(), junctionGravity())) {
            throw GeomProcessingErrorWithLocation(ErrorCode::Unsupported, splitRequest)
                << "Split request point is too distant from polyline";
        }
        const OptionalSnapPoint alignedSplitRequest = tryAddSplitPointForRequest(
            editedEdgeSplitParts, intersectedEdgesSplitParts,
            polyline, closestPoint(polyline.geom(), splitRequest)
        );

        if (alignedSplitRequest) {
            alignedSplitRequests.push_back(*alignedSplitRequest);
        }
    }

    SnapPolyline polylineWithSplitRequest = supplementPolylineWithPoints(
        polyline, alignedSplitRequests
    );

    order( // order splits aliong edited polyline
        editedEdgeSplitParts,
        [&](const SnapPolyline& sp) -> double
        {
            return (sp.isPoint() || polyline.hasSameOrder(sp))
                ? distanceAlongFromStart(polyline.geom(), sp.start().geom())
                : distanceAlongFromStart(polyline.geom(), sp.end().geom());
        }
    );

    removeDuplicateSplits(editedEdgeSplitParts);

    // check start point of edited polyline
    if (needAddStartSplitFor(polyline, editedEdgeSplitParts)) {
        editedEdgeSplitParts.insert(
            editedEdgeSplitParts.begin(), SnapPolyline{polyline.start()}
        );
    }

    // check end point of edited polyline (need for closed polyline only)
    if (needAddEndSplitFor(polyline, editedEdgeSplitParts)) {
         editedEdgeSplitParts.push_back(SnapPolyline{polyline.end()});
    }

    // check points of intersected edges
    for (auto& pair: intersectedEdgesSplitParts) {
        const Edge& edge = getEdge(pair.first);
        SnapPolylineVector& polylines = pair.second;

        order( // order splits aliong edited polyline
            polylines,
            [&](const SnapPolyline& sp) -> double
            {
                return distanceAlongFromStart(edge.geom(), sp.start().geom());
            }
        );
        removeDuplicateSplits(polylines);

        // check start point of edited polyline
        if (edge.startNode() != polylines.front().start().node()) {
            polylines.insert(
                polylines.begin(),
                SnapPolyline{ SnapPoint{getNode(edge.startNode())} }
            );
        }
        if ((polylines.size() == 1 && polylines.front().isPoint()) ||
                edge.endNode() != polylines.back().end().node())
        {
            if (isClosed(edge.geom())) {
                polylines.push_back(
                    SnapPolyline{polylines.front().start()}
                );
            } else {
                polylines.push_back(
                    SnapPolyline{SnapPoint{getNode(edge.endNode())}}
                );
            }
        }
    }

    return CommonSplitParts {
        editedEdgeSplitParts,
        intersectedEdgesSplitParts,
        polylineWithSplitRequest
    };
}


Intersector::Events Intersector::makeEvents(const SourceEdgeID& sourceEdgeId,
    const SnapPolyline& polyline, const PointsVector& splitRequests) const
{
    const CommonSplitParts commotSplitParts = findCommonSplitParts(
        sourceEdgeId, polyline, splitRequests
    );

    SplitEdgeEventMaker maker;
    std::map<EdgeID, SplitEdgeEventDataPtr> intersectedEdgesEvents;
    for (const auto& pair: commotSplitParts.intersectedEdgesSplitParts) {
        const EdgeID& edgeId = pair.first;
        const SnapPolylineVector& split = pair.second;
        // make event data if intersection is not only in end points.
        if (split.size() != 2 || !split.front().isPoint() || !split.back().isPoint()) {
            intersectedEdgesEvents[edgeId] = maker.makeForIntersectedEdge(
                getEdge(edgeId), split
            );
        }
    }

    return {
        maker.makeForEditedEdge(
            sourceEdgeId,
            commotSplitParts.polylineWithSplitRequest,
            commotSplitParts.editedEdgeSplitParts
        ),
        std::move(intersectedEdgesEvents),
        getUnusedNodeIds(sourceEdgeId, polyline)
    };
}


NodeIDSet Intersector::getUnusedNodeIds(const SourceEdgeID& sourceEdgeId,
    const SnapPolyline& polyline) const
{
    NodeIDSet potentialUnusedNodeIds;
    if (sourceEdgeId.exists() && graph_.edgeExists(sourceEdgeId.id())) {
        const Edge& edge = getEdge(sourceEdgeId.id());
        const Node& start = getNode(edge.startNode());
        const Node& end = getNode(edge.endNode());
        const OptionalNodeID& newStartId = polyline.start().node();
        const OptionalNodeID& newEndId = polyline.end().node();
        if ((start.degree() == 1) && deleteNodeChecker_(start.id())) {
            if (newStartId != start.id() && newEndId != start.id() && newStartId) {
                potentialUnusedNodeIds.insert(start.id());
            }
        }
        if ((end.degree() == 1) && deleteNodeChecker_(end.id())) {
            if (newStartId != end.id() && newEndId != end.id() && newEndId) {
                potentialUnusedNodeIds.insert(end.id());
            }
        }
    }
    return potentialUnusedNodeIds;
}

void Intersector::checkPolyline(const Polyline2& polyline) const
{
    checkSegmentsLimits(polyline, restrictions_.edgeSegmentLengthLimits());
    checkNoSelfInteraction(polyline, tolerance());
}

void Intersector::checkResult(const Intersector::Events& events) const
{
    if (restrictions_.maxIntersectedEdges()) {
        checkMaxIntersectedEdges(events, *restrictions_.maxIntersectedEdges());
    }

    if (restrictions_.maxIntersectionsWithEdge()) {
        checkMaxIntersectionsWithEdge(events, *restrictions_.maxIntersectionsWithEdge());
    }

    std::vector<Node> unusedNodes;
    for (const auto& id: events.unusedNodeIds) {
        unusedNodes.push_back(getNode(id));
    }

    checkUnusedNodes(unusedNodes, deleteNodeChecker_);

    auto checkAllParts = [this](const SplitEdgeEventData& event)
    {
        for (const auto& polyline: event.splitPolylines) {
            checkSegmentsLimits(polyline->geom, restrictions_.edgeSegmentLengthLimits());
            checkPolylineLimits(polyline->geom, restrictions_.edgeLengthLimits());
        }
    };

    checkAllParts(*events.editedEdgeEvent);
    for (const auto& pair: events.intersectedEdgesEvents) {
        checkAllParts(*pair.second);
    }

    checkNoInteraction(graph_, events, tolerance());
}


} // namespace maps::wiki::topo::geom
} // namespace maps::wiki::topo
} // namespace maps::wiki
} // namespace maps
