#include "cut_line.h"

#include "polygonal_adapters.h"

#include <maps/libs/geolib/include/point.h>
#include <maps/libs/geolib/include/segment.h>
#include <maps/libs/geolib/include/bounding_box.h>
#include <map>

namespace maps {
namespace wiki {
namespace geom_tools {

double
CutLine::pointCoord(const geolib3::Point2& point) const
{
    return direction_ == Direction::X ? point.x() : point.y();
}

template <>
Relation
CutLine::relation<geolib3::Point2>(const geolib3::Point2& point) const
{
    auto sign = geolib3::sign(pointCoord(point) - coord_, tolerance_);

    return sign == 0
        ? Relation::Equal
        : (sign < 0 ? Relation::Less : Relation::Greater);
}

template <>
Relation
CutLine::relation<geolib3::Segment2>(const geolib3::Segment2& segment) const
{
    auto startRelation = relation(segment.start());
    auto endRelation = relation(segment.end());
    if (startRelation == Relation::Equal && endRelation == Relation::Equal) {
        return Relation::Equal;
    }
    if ((startRelation == Relation::Less && endRelation == Relation::Greater) ||
        (endRelation == Relation::Less && startRelation == Relation::Greater))
    {
        return Relation::Intersects;
    }
    if (startRelation == Relation::Less) {
        return Relation::Less;
    }
    return Relation::Greater;
}

template <>
Relation
CutLine::relation<geolib3::BoundingBox>(const geolib3::BoundingBox& bbox) const
{
    return relation(geolib3::Segment2(bbox.lowerCorner(), bbox.upperCorner()));
}

template <>
Relation
CutLine::relation<RingAdapter>(const RingAdapter& ring) const
{
    std::map<Relation, size_t> pointsCount;
    for (size_t i = 0; i < ring.pointsCount(); ++i) {
        ++pointsCount[relation<geolib3::Point2>(ring.point(i))];
    }
    size_t less = pointsCount[Relation::Less];
    size_t equal = pointsCount[Relation::Equal];
    size_t greater = pointsCount[Relation::Greater];
    if (less > 0 && greater > 0) {
        return Relation::Intersects;
    } else if (less > 0) {
        return equal <= 1 ? Relation::Less : Relation::Less_Touches;
    } else if (greater > 0) {
        return equal <= 1 ? Relation::Greater : Relation::Greater_Touches;
    } else {
        return Relation::Equal;
    }
}

template <>
Relation
CutLine::relation<PolygonAdapter>(const PolygonAdapter& polygon) const
{
    return relation(polygon.boundingBox());
}

template <>
Relation
CutLine::relation<geolib3::LinearRing2>(const geolib3::LinearRing2& ring) const
{
    RingAdapter r(&ring);
    return relation<RingAdapter>(r);
}

template <>
Relation
CutLine::relation<geolib3::Polygon2>(const geolib3::Polygon2& polygon) const
{
    return relation(polygon.boundingBox());
}

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