#include "module.h"
#include <yandex/maps/wiki/validator/categories.h>
#include <yandex/maps/wiki/validator/check.h>

#include <maps/libs/geolib/include/spatial_relation.h>
#include <maps/libs/geolib/include/intersection.h>
#include <maps/libs/geolib/include/bounding_box.h>
#include <maps/libs/geolib/include/polygon.h>
#include <maps/libs/geolib/include/polyline.h>
#include <maps/libs/geolib/include/linear_ring.h>

#include "../utils/misc.h"

#include <cmath>
#include <vector>

using maps::wiki::validator::categories::BLD;
using maps::wiki::validator::categories::RD_EL;
using maps::wiki::validator::categories::TRANSPORT_TRAM_EL;
using maps::wiki::validator::categories::TRANSPORT_METRO_EL;
using maps::wiki::validator::categories::TRANSPORT_RAILWAY_EL;
using maps::wiki::validator::categories::HYDRO_LN_EL;

namespace maps {
namespace wiki {
namespace validator {
namespace checks {

namespace gl = maps::geolib3;

namespace {

const size_t VISIT_BATCH_SIZE = 10000;

template<class Element>
bool intersects(const Building* b, const Element* e)
{ return gl::spatialRelation(b->geom(), e->geom(), gl::Intersects); }

template<class Element>
gl::Point2 geomForReport(const Building* building, const Element* element)
{
    const gl::LinearRing2& ring = building->geom().exteriorRing();
    for (size_t idx = 0; idx < ring.segmentsNumber(); ++idx) {
        const gl::Segment2& segment = ring.segmentAt(idx);
        const auto& polylines = gl::intersection(segment, element->geom());
        if (!polylines.empty()) {
            return polylines.at(0).pointAt(0);
        }
    }
    //building contains element
    return utils::geomForReport(element);
}

template<class Element>
void checkIntersections(CheckContext* context, const Building* building)
{
    std::string message = "bld-" + Element::id() + "-intersection";
    const auto& elems = context->objects<Element>().byBbox(
            building->geom().boundingBox());
    for (const auto& elem : elems) {
        if (intersects(building, elem)) {
            context->error(
                    message, geomForReport(building, elem),
                    {building->id(), elem->id()});
        }
    }
}

template<>
void checkIntersections<RD_EL>(
    CheckContext* context,
    const Building* building)
{
    std::string message = "bld-" + RD_EL::id() + "-intersection";
    const auto& elems = context->objects<RD_EL>().byBbox(
            building->geom().boundingBox());
    for (const auto& elem : elems) {
        const auto structType = elem->structType();
        if (structType != common::StructType::Tunnel && intersects(building, elem)) {
            const auto fc = elem->fc();
            if (1 <= fc && fc <= 7) {
                context->error(
                        message, geomForReport(building, elem),
                        {building->id(), elem->id()});
            }
            if (8 <= fc && fc <= 10) {
                context->warning(
                        message, geomForReport(building, elem),
                        {building->id(), elem->id()});
            }
        }
    }
}

template<>
void checkIntersections<TRANSPORT_METRO_EL>(
    CheckContext* context,
    const Building* building)
{
    std::string message = "bld-" + TRANSPORT_METRO_EL::id() + "-intersection";
    const auto& elems = context->objects<TRANSPORT_METRO_EL>().byBbox(
            building->geom().boundingBox());
    for (const auto& elem : elems) {
        if ((elem->fromZlevel() == 0 || elem->toZlevel() == 0)
                && intersects(building, elem)) {
            context->error(
                    message, geomForReport(building, elem),
                    {building->id(), elem->id()});
        }
    }
}

} //namespace

VALIDATOR_CHECK_PART( bld_el_intersections, road_intersections, BLD, RD_EL )
{
    context->objects<BLD>().batchVisit(
            [&](const Building* building)
    {
        checkIntersections<RD_EL>(context, building);
    }, VISIT_BATCH_SIZE);
}

VALIDATOR_CHECK_PART( bld_el_intersections, transport_intersections,
        BLD, TRANSPORT_TRAM_EL, TRANSPORT_METRO_EL, TRANSPORT_RAILWAY_EL )
{
    context->objects<BLD>().batchVisit(
            [&](const Building* building)
    {
        checkIntersections<TRANSPORT_METRO_EL>(context, building);
        checkIntersections<TRANSPORT_TRAM_EL>(context, building);
        checkIntersections<TRANSPORT_RAILWAY_EL>(context, building);
    }, VISIT_BATCH_SIZE);
}

VALIDATOR_CHECK_PART( bld_el_intersections, hydro_intersections,
        BLD, HYDRO_LN_EL )
{
    context->objects<BLD>().batchVisit(
            [&](const Building* building)
    {
        checkIntersections<HYDRO_LN_EL>(context, building);
    }, VISIT_BATCH_SIZE);
}

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