#include "segments_intersector.h"

#include <maps/libs/geolib/include/intersection.h>
#include <maps/libs/geolib/include/distance.h>

#include <maps/libs/log8/include/log8.h>

namespace maps {
namespace wiki {
namespace topology_fixer {
namespace utils {

namespace {

const double MIN_DIST = 0.1;

} // namespace

SegmentsProcessingResult
SegmentsPairIntersector::operator () (
    SegmentsGraph& graph, SegmentId segmentId1, SegmentId segmentId2) const
{
    if (segmentId1 == segmentId2 ||
            graph.haveCommonEndpoint(segmentId1, segmentId2)) {
        return {{segmentId1}, {segmentId2}, false};
    }

    gl::Segment2 segmentGeom1 = graph.segmentGeom(segmentId1);
    gl::Segment2 segmentGeom2 = graph.segmentGeom(segmentId2);

    auto intersectionPoints = gl::intersection(segmentGeom1, segmentGeom2);
    if (intersectionPoints.size() != 1) {
        return {{segmentId1}, {segmentId2}, false};
    }
    const auto& segment1 = graph.segment(segmentId1);
    const auto& segment2 = graph.segment(segmentId2);
    const auto startId1 = segment1.startPointId;
    const auto endId1 = segment1.endPointId;
    const auto startId2 = segment2.startPointId;
    const auto endId2 = segment2.endPointId;
    const auto& point = intersectionPoints.front();
    auto chunkLength1s = gl::distance(point, graph.point(startId1).pos);
    auto chunkLength1e = gl::distance(point, graph.point(endId1).pos);
    auto chunkLength2s = gl::distance(point, graph.point(startId2).pos);
    auto chunkLength2e = gl::distance(point, graph.point(endId2).pos);

    if (chunkLength1s < MIN_DIST) {
        graph.removeSegment(segmentId2);
        return {
            {segmentId1},
            {graph.addSegment(startId2, startId1).id, graph.addSegment(startId1, endId2).id},
            true};
    }

    if (chunkLength1e < MIN_DIST) {
        graph.removeSegment(segmentId2);
        return {
            {segmentId1},
            {graph.addSegment(startId2, endId1).id, graph.addSegment(endId1, endId2).id},
            true};
    }

    if (chunkLength2s < MIN_DIST) {
        graph.removeSegment(segmentId1);
        return {
            {graph.addSegment(startId1, startId2).id, graph.addSegment(startId2, endId1).id},
            {segmentId2},
            true};
    }

    if (chunkLength2e < MIN_DIST) {
        graph.removeSegment(segmentId1);
        return {
            {graph.addSegment(startId1, endId2).id, graph.addSegment(endId2, endId1).id},
            {segmentId2},
            true};
    }

    graph.removeSegment(segmentId1);
    graph.removeSegment(segmentId2);
    const auto newId = graph.addPoint(boost::none, point).id;

    return {
        {graph.addSegment(startId1, newId).id, graph.addSegment(newId, endId1).id},
        {graph.addSegment(startId2, newId).id, graph.addSegment(newId, endId2).id},
        true};
}

void
SegmentsIntersector::Impl::doAfterProcessing()
{
    removeSnags();
    removeDuplicatedSegments();
}

SegmentsProcessingResult
processIntersections(SegmentsGraph& graph,
    const SegmentIdsList& segmentIds, const SegmentIdsList& otherSegmentIds)
{
    SegmentsIntersector intersector;
    return intersector(graph, segmentIds, otherSegmentIds);
}

} // namespace utils
} // namespace topology_fixer
} // namespace wiki
} // namespace maps
