#include "module.h"
#include "poi_categories.h"
#include "../utils/geom.h"

#include <yandex/maps/wiki/validator/check.h>
#include <maps/libs/geolib/include/distance.h>

#include <unordered_set>

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

using categories::BLD;
using categories::URBAN_AREAL;
using categories::POI_ENTRANCE;

namespace {

const double MAX_DISTANCE_TO_URBAN_AREAL = 2e3; // meters
const double MAX_DISTANCE_TO_ENTRANCE = 500; // meters

const TFeatureType FT_AMUSEMENT_PARK = 224;
const TFeatureType FT_BEACH = 226;
const TFeatureType FT_BIKE_RENTAL = 1901;
const TFeatureType FT_CAR_IMPOUND_LOT = 1408;
const TFeatureType FT_CAR_TRAINING_FACILITY = 1409;
const TFeatureType FT_DOGRUN = 233;
const TFeatureType FT_GARBAGE = 220;
const TFeatureType FT_MONUMENT = 1808;
const TFeatureType FT_OBSERVATION_DECK = 1807;
const TFeatureType FT_PLAYGROUND = 225;
const TFeatureType FT_SHOPPING_STALL = 1302;
const TFeatureType FT_SPORT_GROUND = 202;
const TFeatureType FT_SPORT_HIPPODROME = 1705;
const TFeatureType FT_SPORT_RACETRACK = 1706;
const TFeatureType FT_SPORT_STADIUM = 1701;
const TFeatureType FT_TICKET_TRANSPORT = 1610;
const TFeatureType FT_TOILET = 1615;
const TFeatureType FT_WASTE_DISPOSAL = 1614;

// Feature types excluded from poi-outside-building check
const std::unordered_set<TFeatureType> BLD_CHECK_FT_EXCEPTIONS = {
    FT_AMUSEMENT_PARK, FT_BEACH, FT_BIKE_RENTAL, FT_CAR_IMPOUND_LOT,
    FT_CAR_TRAINING_FACILITY, FT_DOGRUN, FT_GARBAGE, FT_MONUMENT,
    FT_OBSERVATION_DECK, FT_PLAYGROUND, FT_SHOPPING_STALL,
    FT_SPORT_GROUND, FT_SPORT_HIPPODROME, FT_SPORT_RACETRACK,
    FT_SPORT_STADIUM, FT_TICKET_TRANSPORT, FT_TOILET, FT_WASTE_DISPOSAL
};

template<class AssignedObject>
void checkPoiDistanceToAssignedObject(
    const Poi* poi,
    CheckContext* context,
    Poi::AssignedObject::Type assignedObjType,
    double maxDistance,
    const std::string& messageSuffix)
{
    for (const auto& assignedObj : poi->assignedObjects()) {
        auto id = assignedObj.id;
        if (assignedObj.type != assignedObjType
            || !context->objects<AssignedObject>().loaded(id)) {
            continue;
        }

        const auto* object = context->objects<AssignedObject>().byId(id);

        auto distanceRatio = utils::mercatorDistanceRatio(poi->geom());
        if (geolib3::distance(poi->geom(), object->geom()) * distanceRatio
                > maxDistance) {
            context->critical(
                "poi-too-far-from-" + messageSuffix, poi->geom(),
                { poi->id(), object->id() });
        }
    }
}

void checkPoiWithinBuilding(const Poi* poi, CheckContext* context)
{
    for (const auto& assignedObj : poi->assignedObjects()) {
        auto id = assignedObj.id;
        if (BLD_CHECK_FT_EXCEPTIONS.count(poi->featureType())
            || assignedObj.type != Poi::AssignedObject::Type::Building
            || !context->objects<BLD>().loaded(id)) {
            continue;
        }

        const auto* bld = context->objects<BLD>().byId(id);
        if (!utils::contains(bld->geom(), poi->geom())) {
            context->warning(
                "poi-outside-building", poi->geom(), { poi->id(), bld->id() });
        }
    }
}

template<class CategoryName>
bool performCheck(CheckContext* context)
{
    context->objects<CategoryName>().visit([&](const Poi* poi)
    {
        checkPoiDistanceToAssignedObject<URBAN_AREAL>(
            poi,
            context,
            Poi::AssignedObject::Type::UrbanAreal,
            MAX_DISTANCE_TO_URBAN_AREAL,
            "urban-areal");
        checkPoiDistanceToAssignedObject<POI_ENTRANCE>(
            poi,
            context,
            Poi::AssignedObject::Type::Entrance,
            MAX_DISTANCE_TO_ENTRANCE,
            "entrance");
        checkPoiWithinBuilding(poi, context);
    });
    return true;
}

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

} // namespace

VALIDATOR_SIMPLE_CHECK( poi_geometry, BLD, URBAN_AREAL, POI_ENTRANCE, POI_CATEGORIES )
{
    performChecks<POI_CATEGORIES>(context);
}

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