#include "module.h"

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

#include "../utils/face_builder.h"
#include "../utils/geom.h"
#include "../utils/misc.h"
#include "../utils/object_elements_within_aoi.h"

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

#include <algorithm>

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

namespace {

const double NON_COUNTRY_INCLUSION_THRESHOLD = 0.8;
const size_t AD_GEOM_SEARCH_DEPTH = 1;

const size_t VISIT_BATCH_SIZE = 5000;

} // namespace

using categories::AD;
using categories::AD_FC;
using categories::AD_EL;
using categories::RD;
using categories::RD_EL;

VALIDATOR_SIMPLE_CHECK( rd_ad_inclusion, AD, AD_FC, AD_EL, RD, RD_EL )
{
    auto viewAd = context->objects<AD>();
    auto viewAdFc = context->objects<AD_FC>();
    auto viewAdEl = context->objects<AD_EL>();
    auto viewRdEl = context->objects<RD_EL>();

    context->objects<RD>().batchVisit(
        [&](const Road* road) {
            if (road->elements().empty() || !utils::objectElementsWithinAoi<RD>(context, road)) {
                return;
            }

            if (road->associatedObject().type != Road::AssociatedObject::Type::AdmUnit ||
                !viewAd.loaded(road->associatedObject().id))
            {
                return;
            }

            // Get related AD or its nearest parent with geometry
            size_t depth = 0;
            const AdmUnit* admUnit = viewAd.byId(road->associatedObject().id);
            while (admUnit->faces().empty() && depth < AD_GEOM_SEARCH_DEPTH) {
                auto parent = admUnit->parent();
                if (!parent) {
                    break;
                }
                if (!viewAd.loaded(parent)) {
                    return;
                }
                admUnit = viewAd.byId(parent);
                ++depth;
            }

            auto geomForReport = [&] {
                return utils::geomForReport(
                    viewRdEl.byId(road->elements().front()));
            };

            if (admUnit->faces().empty()) {
                context->error(
                    "ad-geom-not-found",
                    geomForReport(),
                    { road->id(), road->associatedObject().id });
                return;
            }
            if (!utils::objectFacesWithinAoi<AD>(context, admUnit)) {
                return;
            }

            // Calculate road length
            geolib3::PolylinesVector geom;
            double roadLength = 0.0;
            for (TId elementId : road->elements()) {
                geom.push_back(viewRdEl.byId(elementId)->geom());
                roadLength += geolib3::length(geom.back());
            }
            if (roadLength < utils::EPS) {
                return;
            }

            // Calculate included length
            double inclusionLength = 0.0;
            for (TId faceId : admUnit->faces()) {
                const Face* face = viewAdFc.byId(faceId);
                utils::FaceBuilder faceBuilder(face, viewAdEl);

                if (!faceBuilder.valid()) {
                    // Adm unit contains invalid faces
                    return;
                }

                double faceInclusion = utils::intersectionLength(
                    geom, geolib3::Polygon2(faceBuilder.points())
                );

                if (face->isInterior()) {
                    inclusionLength -= faceInclusion;
                } else {
                    inclusionLength += faceInclusion;
                }
            }

            if (admUnit->levelKind() == AdmUnit::LevelKind::Country) {
                if (std::abs(inclusionLength - roadLength) > utils::EPS) {
                    context->critical(
                        "rd-not-within-country",
                        geomForReport(),
                        { road->id(), admUnit->id() });
                }
            } else {
                if (inclusionLength < NON_COUNTRY_INCLUSION_THRESHOLD * roadLength) {
                    context->error(
                        "rd-not-within-ad-geom",
                        geomForReport(),
                        { road->id(), admUnit->id() });
                }
            }
        },
        VISIT_BATCH_SIZE
    );
}

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