#include "module.h"
#include "../poi/indoor_poi_categories.h"

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

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

#include <maps/libs/geolib/include/spatial_relation.h>

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

namespace gl = maps::geolib3;
using categories::INDOOR_LEVEL;
using categories::INDOOR_AREA;
using categories::INDOOR_RADIOMAP_CAPTURER_PATH;

namespace {

const size_t VISIT_BATCH_SIZE = 10000;
constexpr TFeatureType PUBLIC_AREA_FT_TYPE = 2401;

template<class Category>
bool performCheck(CheckContext* context)
{
    context->objects<Category>().visit([&](const Poi* poi)
    {
        auto indoorLevelId = poi->parent();
        if (context->objects<INDOOR_LEVEL>().loaded(indoorLevelId)) {
            auto indoorLevel = context->objects<INDOOR_LEVEL>().byId(indoorLevelId);
            if (!gl::spatialRelation(indoorLevel->geom(), poi->geom(), gl::Contains)) {
                context->fatal(
                    "indoor-poi-outside-level",
                    poi->geom(),
                    {poi->id(), indoorLevel->id()});
            }
        } else {
            context->fatal(
                "indoor-poi-outside-level",
                poi->geom(),
                {poi->id()});
        }
    });
    return true;
}

template<class... Categories>
void performChecks(CheckContext* context)
{
    auto performed = {performCheck<Categories>(context)...};
    (void)performed;
}

} // namespace

VALIDATOR_SIMPLE_CHECK( indoor_poi_inside_level, INDOOR_LEVEL, INDOOR_POI_CATEGORIES )
{
    performChecks<INDOOR_POI_CATEGORIES>(context);
}

VALIDATOR_SIMPLE_CHECK( indoor_area_geometry_valid, INDOOR_AREA )
{
    context->objects<INDOOR_AREA>().batchVisit([&](const auto* area) {
        utils::runPolygonGeometryCheck(area, context);
    }, VISIT_BATCH_SIZE);

}

VALIDATOR_SIMPLE_CHECK( indoor_area_radiomap_capturer_path_intersection,
    INDOOR_RADIOMAP_CAPTURER_PATH,
    INDOOR_AREA)
{
    std::map<TId, std::vector<const INDOOR_AREA::TObject*>> nonPublicAreasByLevel;
    context->objects<INDOOR_AREA>().visit([&](const auto* area) {
        if (area->featureType() != PUBLIC_AREA_FT_TYPE) {
            nonPublicAreasByLevel[area->parent()].push_back(area);
        }
    });
    context->objects<INDOOR_RADIOMAP_CAPTURER_PATH>().batchVisit([&](const auto* path) {
        const auto it = nonPublicAreasByLevel.find(path->parent());
        if (it == nonPublicAreasByLevel.end()) {
            return;
        }
        const auto& sameLevelAreas = it->second;
        std::vector<TId> intersected;
        for (const auto& area : sameLevelAreas) {
            if (gl::spatialRelation(
                area->geom(),
                path->geom(),
                gl::SpatialRelation::Intersects))
            {
                intersected.push_back(area->id());
            }
        }
        if (!intersected.empty()) {
            intersected.push_back(path->id());
            context->critical(
                    "indoor-area-radiomap-capturer-path-intersection",
                    path->geom().boundingBox().center(),
                    intersected);
        }
    }, VISIT_BATCH_SIZE);
}

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