#include "validate.h"

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

#include <set>
#include <map>

#include <iostream>

namespace maps {
namespace wiki {
namespace geom_tools {

geolib3::PointsVector
findIncorrectIntersections(const CutLine& cutLine, const RingList& rings)
{
    typedef size_t SegmentId;
    SegmentId segmentId = 0;
    geolib3::StaticGeometrySearcher<geolib3::Segment2, SegmentId> segmentsIndex;
    std::list<geolib3::Segment2> segments;
    std::map<SegmentId, const geolib3::Segment2*> segmentsPtrMap;
    boost::optional<geolib3::BoundingBox> checkedSegmentsBBox;

    std::set<SegmentId> checkedSegmentIds;

    auto collectCheckedSegments = [&] (const RingAdapter& ring)
    {
        for (size_t segIdx = 0; segIdx < ring.pointsCount(); ++segIdx) {
            const auto& seg = ring.segment(segIdx);
            if (geolib3::distance(seg.start(), cutLine.line()) < 2 * cutLine.tolerance() ||
                geolib3::distance(seg.end(), cutLine.line()) < 2 * cutLine.tolerance())
            {
                checkedSegmentsBBox = checkedSegmentsBBox
                    ? geolib3::expand(*checkedSegmentsBBox, seg)
                    : seg.boundingBox();
            }
        }
    };

    for (const auto& ring : rings) {
        collectCheckedSegments(ring);
    }

    if (checkedSegmentIds.empty()) {
        return {};
    }

    auto collectIntersectedSegments = [&] (const RingAdapter& ring)
    {
        for (size_t segIdx = 0; segIdx < ring.pointsCount(); ++segIdx) {
            const auto& seg = ring.segment(segIdx);
            if (geolib3::intersects(
                    geolib3::resizeByValue(seg.boundingBox(), 2 * cutLine.tolerance()),
                    *checkedSegmentsBBox))
            {
                segments.push_back(seg);
                segmentsPtrMap.insert({++segmentId, &segments.back()});
                segmentsIndex.insert(&segments.back(), segmentId);
                if (geolib3::distance(seg.start(), cutLine.line()) < 2 * cutLine.tolerance() ||
                    geolib3::distance(seg.end(), cutLine.line()) < 2 * cutLine.tolerance())
                {
                    checkedSegmentIds.insert(segmentId);
                }
            }
        }
    };

    for (const auto& ring : rings) {
        collectIntersectedSegments(ring);
    }

    segmentsIndex.build();

    geolib3::PointsVector result;

    for (auto id : checkedSegmentIds) {
        auto segmentPtr = segmentsPtrMap.at(id);
        auto segmentsSearchResult = segmentsIndex.find(
            geolib3::resizeByValue(
                segmentPtr->boundingBox(), cutLine.tolerance()));
        for (auto it = segmentsSearchResult.first; it != segmentsSearchResult.second; ++it)
        {
            const auto& segmentId = it->value();
            if (segmentId == id) {
                continue;
            }
            const auto& segmentGeom = it->geometry();
            const auto& intersection = geolib3::intersection(*segmentPtr, segmentGeom);
            std::set<geolib3::Point2> endpoints = {
                segmentPtr->start(), segmentPtr->end(),
                segmentGeom.start(), segmentGeom.end()
            };
            if (intersection.size() > 1) {
                result.insert(result.end(), intersection.begin(), intersection.end());
                continue;
            }
            for (const auto& point : intersection) {
                if (!endpoints.count(point)) {
                    result.push_back(point);
                }
            }
        }
    }

    return result;
}

} // namespace geom_tools
} // namespace wiki
} // namespace maps
