#pragma once

#include <maps/libs/geolib/include/bounding_box.h>
#include <maps/libs/geolib/include/point.h>
#include <maps/libs/geolib/include/polygon.h>
#include <maps/libs/geolib/include/polyline.h>
#include <maps/libs/geolib/include/segment.h>
#include <maps/libs/geolib/include/spatial_relation.h>

#include <memory>

namespace maps {
namespace wiki {
namespace validator {

namespace gl = maps::geolib3;

namespace {

template<typename OtherGeomType>
inline bool intersects(const gl::BoundingBox& bbox, const OtherGeomType& geom)
{ return gl::spatialRelation(bbox, geom, gl::Intersects); }

} // namespace

template<typename OtherGeomType>
class BboxIntersectsOp
{
public:
    explicit BboxIntersectsOp(const gl::BoundingBox& bbox)
        : bbox_(bbox)
    { }

    bool operator()(const OtherGeomType& geom) const
    { return intersects(bbox_, geom); }

private:
    const gl::BoundingBox& bbox_;
};


template<>
class BboxIntersectsOp<gl::Polyline2>
{
public:
    explicit BboxIntersectsOp(const gl::BoundingBox& bbox)
        : bbox_(bbox)
    { }

    bool operator()(const gl::Polyline2& polyline) const
    {
        if (polyline.pointsNumber() == 0) {
            return false;
        }
        if (polyline.pointsNumber() == 1) {
            return intersects(bbox_, polyline.pointAt(0));
        }

        bool intersectsByBbox = false;
        for (const gl::Segment2& segment: polyline.segments()) {
            if (intersects(bbox_, segment.start())
                    || intersects(bbox_, segment.end())) {
                return true;
            }
            if (intersects(bbox_, segment.boundingBox())) {
                intersectsByBbox = true;
            }
        }

        if (!intersectsByBbox) {
            return false;
        }

        if (!bboxPolygonPtr_) {
            bboxPolygonPtr_.reset(new gl::Polygon2(bbox_.polygon()));
        }
        return gl::spatialRelation(*bboxPolygonPtr_, polyline, gl::Intersects);
    }

private:
    const gl::BoundingBox& bbox_;
    mutable std::unique_ptr<gl::Polygon2> bboxPolygonPtr_;
};


template<>
class BboxIntersectsOp<gl::Polygon2>
{
public:
    explicit BboxIntersectsOp(const gl::BoundingBox& bbox)
        : bboxPolygon_(bbox.polygon())
    { }

    bool operator()(const gl::Polygon2& polygon) const
    { return gl::spatialRelation(bboxPolygon_, polygon, gl::Intersects); }

private:
    const gl::Polygon2 bboxPolygon_;
};

} // namespace validator
} // namespace wiki
} // namespace maps
