#include "validator.h"
#include "check_outsource_consistency.h"
#include "check_permissions.h"

#include "configs/config.h"
#include "objects/attr_object.h"
#include "objects/object.h"
#include "objects/areal_object.h"
#include "objects/junction.h"
#include "objects/linear_element.h"
#include "objects/line_object.h"
#include "objects/point_object.h"
#include "objects/complex_object.h"
#include "objects/relation_object.h"
#include "objects/category_traits.h"
#include "views/objects_query.h"
#include "view_utils.h"
#include "topologycal.h"
#include "objects_cache.h"
#include "revisions_facade.h"
#include "relations_manager.h"
#include "topo_storage.h"

#include <yandex/maps/wiki/topo/face_validator.h>
#include <yandex/maps/wiki/topo/cache.h>
#include <yandex/maps/wiki/common/string_utils.h>
#include <yandex/maps/wiki/configs/editor/category_template.h>
#include <yandex/maps/wiki/configs/editor/restrictions.h>
#include <yandex/maps/wiki/configs/editor/master_role.h>
#include <yandex/maps/wiki/configs/editor/categories.h>
#include <yandex/maps/wiki/configs/editor/topology_groups.h>
#include <yandex/maps/wiki/configs/editor/category_groups.h>
#include <yandex/maps/wiki/configs/editor/overlay_groups.h>
#include <maps/wikimap/mapspro/libs/acl/include/exception.h>
#include <maps/libs/geolib/include/vector.h>

#include <geos/geom/Coordinate.h>
#include <geos/geom/Geometry.h>
#include <geos/geom/Point.h>
#include <geos/geom/Polygon.h>

#include <list>
#include <string>



#include <maps/libs/common/include/profiletimer.h>

namespace maps {
namespace wiki {

namespace {

/// Set of categories, which topology should be validated
/// even if it is not primary and affected inderectly
const StringSet CATEGORIES_TO_ALWAYS_VALIDATE_TOPOLOGY
    {
        CATEGORY_COND_CLOSURE
    };

size_t relationCountAfterSave(
        const GeoObject* obj,
        ObjectsCache& cache,
        RelationType type,
        const std::string& role,
        const std::string& relativeCategoryId)
{
    const auto& diff = obj->relations(type).diff(role, relativeCategoryId);
    int diffCount = diff.added.size() - diff.deleted.size();
    if (!obj->isCreated()) {
        size_t count = cache.revisionsFacade()
            .relativesForRoleCount(type, obj->id(), role, relativeCategoryId);
        if (!cache.hasSavedCommit()) {
            count += diffCount;
        }
        return count;
    } else {
        return diffCount;
    }
}

void checkGeomValidity(const GeoObject* obj)
{
    const auto error = findGeomError(obj->geom());
    WIKI_REQUIRE_WITH_LOCATION(
        !error,
        ERR_TOPO_INVALID_GEOMETRY,
        "Invalid geometry for object " << obj->id()
            << " of category " << obj->categoryId(),
        *error
    );
}

} // namespace

void
Validator::validateObjectMasterRelations(const GeoObject* obj)
{
    const Category& cat = obj->category();
    for (const auto& masterRole: cat.masterRoles()) {
        if (!masterRole.isRestricted()) {
            continue;
        }

        const auto& roleId = masterRole.roleId();
        RelationType relType = RelationType::Master;
        if (!obj->isCreated() && obj->relations(relType).diff(roleId).empty()) {
            continue;
        }
        size_t roleOccurs = relationCountAfterSave(obj, cache_, relType, roleId, masterRole.categoryId());

        WIKI_REQUIRE(
            roleOccurs >= masterRole.minOccurs(),
            ERR_MASTERS_COUNT,
            "Object " << obj->id() << " '" << obj->categoryId() << "' masters for role '" << masterRole.roleId()
            << "' do not meet requirements too few: " << roleOccurs << " < " << masterRole.minOccurs());

        WIKI_REQUIRE(
            !masterRole.maxOccurs() || (roleOccurs <= *masterRole.maxOccurs()),
            ERR_MASTERS_COUNT,
            "Object " << obj->id() << " '" << obj->categoryId() << "' masters for role '" << masterRole.roleId()
            << "' do not meet requirements too many: " << roleOccurs << " > " << *masterRole.maxOccurs());
    }
    if (cat.masterRequired()) {
        WIKI_REQUIRE(obj->masterRelations().range().size(),
            ERR_MASTER_REQUIRED,
            "Atleast one master required for object: " << obj->id() << " (" << obj->categoryId() << ")");
    }

    if (cat.id() == CATEGORY_ADDR) {//NMAPS-7707 TODO Make more superior solution
        WIKI_REQUIRE(!obj->masterRelations().range(ROLE_ASSOCIATED_WITH).empty() ||
            !obj->masterRelations().range(ROLE_ADDR_ASSOCIATED_WITH).empty(),
            ERR_MASTER_REQUIRED,
            "Address point should have associated_with or addr_associated_with role master.");
    }
}

void
Validator::validateParentRelations(const GeoObject* obj)
{
    TOIds ids;
    while (obj) {
        WIKI_REQUIRE(!ids.count(obj->id()),
            ERR_PARENT_RELATIONS_LOOP,
            "Objects " << common::join(ids, ",") << " have loop in parent relations.");
        ids.insert(obj->id());
        obj = obj->parent();
    }
}

void Validator::validateObjectSlaveRelations(const GeoObject* obj)
{
    const Category& cat = obj->category();
    for (const auto& slaveRole : cat.slavesRoles()) {
        const auto& roleId = slaveRole.roleId();
        RelationType relType = RelationType::Slave;
        if (!obj->isCreated() && obj->relations(relType).diff(roleId).empty()) {
            continue;
        }
        size_t roleOccurs = relationCountAfterSave(obj, cache_, relType, roleId, slaveRole.categoryId());

        WIKI_REQUIRE(
            roleOccurs >= slaveRole.minOccurs() && roleOccurs <= slaveRole.maxOccurs(),
            ERR_SLAVES_COUNT,
            "Object " << obj->id() << " slaves for role " << slaveRole.roleId()
            << " do not meet requirements.");
    }
}

void
Validator::validateRelatedAttributes(const GeoObject* obj)
{
    if (!(obj->isModifiedAttr() || obj->isCreated())) {
        return;
    }
    for (const auto& attrDef : obj->category().attrDefs()) {
        if (!obj->attributes().isDefined(attrDef->id())) {
            continue;
        }
        const auto& policyAttrId = attrDef->junctionSpecificPolicyAttribute();
        if (!policyAttrId.empty()) {
            const auto& attrValue = obj->attributes().value(attrDef->id());
            if (attrValue.empty()) {
                continue;
            }
            const auto& policyValue = obj->attributes().value(policyAttrId);
            if (policyValue == "odd" || policyValue == "even") {
                ASSERT(attrDef->valueType() == ValueType::Integer);
                auto value = boost::lexical_cast<long long int>(attrValue);
                WIKI_REQUIRE(
                    ((value & 1) && policyValue == "odd") ||
                    ((value & 1) == 0 && policyValue == "even"),
                    ERR_ATTRIBUTE_VALUE_INVALID,
                    "Attribute: " << attrDef->id() << " value:" << value << "should be:" << policyValue);
            }
        }
    }
}

void
Validator::validateObjectGeometry(const GeoObject* obj)
{
    if (!obj->isModifiedGeom() && !obj->isCreated()) {
        return;
    }

    WIKI_REQUIRE(
        !obj->geom().isNull(),
        ERR_TOPO_NO_GEOMETRY,
        "No geometry for object " << obj->id());
    checkGeomValidity(obj);
    WIKI_REQUIRE(
        obj->geom()->isSimple() && !obj->geom()->isEmpty(),
        ERR_TOPO_INVALID_GEOMETRY,
        "Geometry is empty or not simple for object " << obj->id()
        << " of category " << obj->categoryId());

    const auto& tmpl = obj->category().categoryTemplate();
    WIKI_REQUIRE(
        tmpl.isGeometryTypeValid(obj->geom().geometryTypeName()),
        ERR_TOPO_WRONG_GEOMETRY_TYPE,
        "Wrong geometry type " << obj->geom().geometryTypeName()
        << " for object " << obj->id()
        << " of category " << obj->categoryId());

    if (obj->restrictions().maxVertexes()) {
        WIKI_REQUIRE_WITH_LOCATION(
            obj->geom()->getNumPoints() <= obj->restrictions().maxVertexes()
            || allowToIgnoreRestrictions(obj),
            ERR_TOPO_TOO_MANY_VERTEXES,
            "New geometry contains too many vertexes",
            center(obj->geom()));
    }
}

void
Validator::validateLineGeometry(const GeoObject* obj)
{
    if (!obj->isModifiedGeom() && !obj->isCreated()) {
        return;
    }

    const auto& gabarits = obj->restrictions().gabarits();
    if (!gabarits || !gabarits->length()) {
        return;
    }

    double newLen = obj->geom().realLength();
    double oldLen = obj->hasExistingOriginal()
        ? obj->original()->geom().realLength()
        : 0;

    WIKI_REQUIRE_WITH_LOCATION(
        newLen < oldLen || newLen * *gabarits->lengthAccuracy() < *gabarits->length()
        || allowToIgnoreRestrictions(obj),
        ERR_TOPO_TOO_LONG,
        "Creating too long linear element",
        center(obj->geom()));
}

void
Validator::validatePolygonGeometry(const GeoObject* obj)
{
    if (!obj->isModifiedGeom() && !obj->isCreated()) {
        return;
    }

    //----------------

    auto validateRing = [](const geos::geom::LineString* ring)
    {
        const auto* coordinatesRO = ring->getCoordinatesRO();
        std::vector<geos::geom::Coordinate> points;
        coordinatesRO->toVector(points);
        tryFixRing(points); //here exception gets thrown in case of incorrect geometry
    };

    const auto* polygon = dynamic_cast<const geos::geom::Polygon*>(obj->geom().geosGeometryPtr());

    validateRing(polygon->getExteriorRing());
    for (size_t i = 0; i < polygon->getNumInteriorRing(); i++) {
        validateRing(polygon->getInteriorRingN(i));
    }

    //----------------

    const auto& gabarits = obj->restrictions().gabarits();
    if (!gabarits) {
        return;
    }

    if (!gabarits->maxSize() && !gabarits->minSize()) {
        return;
    }

    auto sizeVecNew = obj->geom().realSize();
    Size newSize(sizeVecNew.x(), sizeVecNew.y());

    if (gabarits->maxSize()) {
        Size oldSize(0, 0);
        if (obj->hasExistingOriginal()) {
            auto sizeVecOld  = obj->original()->geom().realSize();
            oldSize = Size(sizeVecOld.x(), sizeVecOld.y());
        }

        WIKI_REQUIRE_WITH_LOCATION(
            newSize.less(oldSize) || newSize.less(*gabarits->maxSize())
            || allowToIgnoreRestrictions(obj),
            ERR_TOPO_TOO_LARGE,
            "Creating too big areal object",
            center(obj->geom()));
    }
    if (gabarits->minSize()) {
        WIKI_REQUIRE_WITH_LOCATION(
            newSize.greater(*gabarits->minSize())
            || allowToIgnoreRestrictions(obj),
            ERR_TOPO_TOO_SMALL,
            "Creating too small areal object",
            center(obj->geom()));
    }
}

namespace {
configs::editor::Isocodes
prohibitedCountriesIsocodes(const GeoObject* object)
{
    const auto& countries = cfg()->editor()->countries();
    const auto& categoryId = object->categoryId();
    auto isocodes = countries.isocodesWhereCategoryIsProhibited(categoryId);
    for (const auto& attribute : object->attributes()) {
        for (const auto& value : attribute.values()) {
            const auto& attrValueIsocodes =
                countries.isocodesWhereAttributeValueIsProhibited(attribute.id(), value);
            isocodes.insert(attrValueIsocodes.begin(), attrValueIsocodes.end());
        }
    }
    return isocodes;
}
} // namespace

void
Validator::validateProhibitedObject(const GeoObject* object)
{
    if (cache_.branchContext().branch.id() != revision::TRUNK_BRANCH_ID) {
        return;
    }
    const auto& category = object->category();
    const auto& isocodes = prohibitedCountriesIsocodes(object);
    if (isocodes.empty()) {
        return;
    }
    if (allowToIgnoreRestrictions(object)) {
        return;
    }
    std::vector<common::Geom> geoms;
    if (isSimpleGeomCategory(category)) {
        const auto& geom = object->geom();
        geoms.push_back(geom);
    } else if (isDirectGeomMasterCategory(category)) {
        const auto geomPartsRoles = object->category().slaveRoleIds(roles::filters::IsGeom);
        const auto geomParts = object->slaveRelations().range(geomPartsRoles);
        for (const auto& geomPart : geomParts) {
            const auto& geom = geomPart.relative()->geom();
            ASSERT(!geom.isNull());
            geoms.push_back(geom);
        }
    } else if (isIndirectGeomMasterCategory(category)) {
        const auto geomPartsRoles = object->category().slaveRoleIds(roles::filters::IsGeom);
        const auto geomParts = object->slaveRelations().range(geomPartsRoles);
        for (const auto& geomPart : geomParts) {
            const auto* slave = geomPart.relative();
            const auto slaveGeomPartsRoles = slave->category().slaveRoleIds(roles::filters::IsGeom);
            const auto slaveGeomParts = slave->slaveRelations().range(slaveGeomPartsRoles);
            for (const auto& slaveGeomPart : slaveGeomParts) {
                const auto& geom = slaveGeomPart.relative()->geom();
                ASSERT(!geom.isNull());
                geoms.push_back(geom);
            }
        }
    } else {
        REQUIRE(false,
            "Category :" << object->categoryId()
            << " shouldn't be configured as prohibited or have prohibited values. Lack of geometry components.");
    }
    const auto adIds = findContainingAdByView(
        geoms, {},
        {AD_LEVEL_KIND_1_COUNTRY}, isocodes,
        true, cache_.workView());
    WIKI_REQUIRE(
        adIds.empty(),
        ERR_PROHIBITED_CATEGORY,
        "Objects of category: " << object->categoryId()
        << " shouldn't be created or moved to countries: " << common::join(adIds, ','));
}

void
Validator::validateComplexObjectContents(const ComplexObject* obj)
{
    const Category& cat = obj->category();
    for (const auto& slaveRelation : obj->slaveRelations().diff().added) {
        const std::string& roleCategoryId = cat.slaveRole(slaveRelation.roleId()).categoryId();
        WIKI_REQUIRE(
            roleCategoryId == slaveRelation.categoryId(),
            ERR_WRONG_RELATION_ASSOCIATION,
            "Object " << obj->id() << " of category " << cat.id()
            << " should have " << roleCategoryId
            << " on role " << slaveRelation.roleId()
            << " but it is " << slaveRelation.categoryId());
    }
}

void
Validator::validateComplexObjectTopology(const ComplexObject* obj)
{
    const Category& cat = obj->category();
    WIKI_REQUIRE(
        isComplexObjectTopologyValid(obj, cache_),
        ERR_TOPO_INVALID_TOPOLOGY,
        "Object " << obj->id() << " of category " << cat.id() <<
        " topology is invalid");
}

bool
isComplexObjectTopologyValid(const ComplexObject* obj, ObjectsCache& cache)
{
    const Category& cat = obj->category();
    auto geomSlaveRoles = cat.geomSlaveRoleIds();

    if (geomSlaveRoles.empty()) {
        return true;
    }
    cache.relationsManager().loadRelations(
        RelationType::Slave,
        TOIds{obj->id()},
        StringSet(geomSlaveRoles.begin(), geomSlaveRoles.end()));

    for (const auto& slaveRoleId : geomSlaveRoles) {
        const RelationInfos::Range& range = obj->slaveRelations().range(slaveRoleId);
        if (!continous(range.begin(), range.end())) {
            WARN() << "Object " << obj->id() << " of category " << cat.id() <<
            " slaves[" << range.size() << "] for role " << slaveRoleId <<
            " don't make continous group";
            return false;
        }
    }

    if (geomSlaveRoles.size() < 2) {
        return true;
    }
    for (size_t i = 0; i < geomSlaveRoles.size() - 1; ++i) {
        const std::string& curRoleId = geomSlaveRoles[i];
        const std::string& nextRoleId = geomSlaveRoles[i + 1];
        const RelationInfos::Range& curRange = obj->slaveRelations().range(curRoleId);
        const RelationInfos::Range& nextRange = obj->slaveRelations().range(nextRoleId);
        if (
            hasGeom(curRange.begin(), curRange.end()) &&
            hasGeom(nextRange.begin(), nextRange.end())) {
            if (!touches(
                curRange.begin(), curRange.end(),
                nextRange.begin(), nextRange.end())) {
                WARN() << "Object " << obj->id() << " of category " << cat.id() <<
                    " slaves[" << curRange.size() << "] for role " << curRoleId <<
                    " do not touch next role " << nextRoleId <<
                    " objects[" << nextRange.size() << "].";
                return false;
            }
        }
    }
    return true;
}

void
Validator::visitGeoObject(const GeoObject* obj)
{
    DEBUG() << "Process: " << obj->id();

    validateProhibitedObject(obj);
    validateRelatedAttributes(obj);
    validateObjectMasterRelations(obj);
    validateObjectSlaveRelations(obj);
    validateParentRelations(obj);
}

void
Validator::visitArealObject(const ArealObject* obj)
{
    visitGeoObject(obj);
    validateObjectGeometry(obj);
    validatePolygonGeometry(obj);
    if (obj->categoryId() == CATEGORY_OUTSOURCE_REGION) {
        checkOutsourceConsistency(obj, cache_.workCore());
    }
}

void
Validator::visitJunction(const Junction* obj)
{
    WIKI_REQUIRE(obj->masterRelations().range().size(),
        ERR_MASTER_REQUIRED,
        "Stray junction: " << obj->id());
    visitGeoObject(obj);
    validateObjectGeometry(obj);
}

void
Validator::visitLinearElement(const LinearElement* obj)
{
    visitGeoObject(obj);
    validateObjectGeometry(obj);
    validateLineGeometry(obj);
    const auto* lineString =
        dynamic_cast<const geos::geom::LineString*>(obj->geom().geosGeometryPtr());
    std::unique_ptr<geos::geom::Point> startPoint(lineString->getStartPoint());
    ASSERT(startPoint);
    WIKI_REQUIRE_WITH_LOCATION(
        !lineString->isClosed()
        || cfg()->editor()->topologyGroups().findGroup(obj->categoryId())->allowClosedEdge(),
        ERR_TOPO_CLOSED_EDGE_FORBIDDEN,
        "Object: " << obj->id() << " of category: "
            << obj->categoryId() << " shouldn't have closed geometry.",
        TGeoPoint(startPoint->getX(), startPoint->getY()));
}

void
Validator::visitLineObject(const LineObject* obj)
{
    visitGeoObject(obj);
    validateObjectGeometry(obj);
    validateLineGeometry(obj);
}

void
Validator::visitPointObject(const PointObject* obj)
{
    visitGeoObject(obj);
    validateObjectGeometry(obj);
    validateObjectOverlay(obj);
}

bool
hasGeom(const RelationInfo& m)
{
    return !m.geom().isNull();
}

namespace {

void
validateContour(ObjectsCache& cache, const GeoObject& object)
{
    const auto& contourObjectsDefs = cfg()->editor()->contourObjectsDefs();
    if (contourObjectsDefs.partType(object.categoryId()) !=
        ContourObjectsDefs::PartType::Contour)
    {
        return;
    }

    ProfileTimer timer;

    const auto* topoGroup = cfg()->editor()->topologyGroups().findGroup(
        contourObjectsDefs.contourDef(object.categoryId()).contour.linearElement.categoryId);
    REQUIRE(topoGroup, "No element topoGroup found for contour " << object.id());

    TopoStorage storage(*topoGroup, cache);
    topo::Cache topoCache(storage, topoGroup->tolerance());
    auto faceValidator = topoCache.faceValidator();

    (*faceValidator)(object.id());

    DEBUG() << "Validated " << object.id() << " in " << timer.getElapsedTime();
}

} // namespace

void
Validator::visitComplexObject(const ComplexObject* obj)
{
    visitGeoObject(obj);
    validateComplexObjectContents(obj);
    const Category& cat = obj->category();
    if ((obj->primaryEdit() || CATEGORIES_TO_ALWAYS_VALIDATE_TOPOLOGY.count(cat.id()))
            && cat.slavesTopologicalyContinuous()) {
        validateComplexObjectTopology(obj);
    }
    if (!allowInvalidContoursWithinContext(obj->id())) {
        validateContour(cache_, *obj);
    }
}

void
Validator::visitAttrObject(const AttrObject* obj)
{
    visitGeoObject(obj);
}

void
Validator::visitRelationObject(const RelationObject* /* obj */)
{
    //relation objects have no restrictions
}

bool
Validator::allowInvalidContoursWithinContext(TOid oid) const
{
    auto editContextIt = editContexts_.find(UniqueId(oid));
    return editContextIt == editContexts_.end() ||
        editContextIt->second->allowInvalidContours();
}

/// Propagate validation from explicitly modified objects
namespace {
std::unordered_map<std::string, std::string>
categorySlavesToGeomPartMasterRole(const std::unordered_set<std::string>& categoryIds)
{
    std::unordered_map<std::string, std::string> categoryToGeomPartMasterRole;
    for (const auto& categoryId : categoryIds) {
        const auto& category = cfg()->editor()->categories()[categoryId];
        for (const auto& slaveRoleId : category.slaveRoleIds(roles::filters::IsGeom)) {
            const auto& slaveRole = category.slaveRole(slaveRoleId);
            categoryToGeomPartMasterRole.emplace(slaveRole.categoryId(), slaveRoleId);
        }
    }
    return categoryToGeomPartMasterRole;
}

std::unordered_map<std::string, std::string>
categoryToGeomPartProhibitedMasterRole()
{
    const auto& countries = cfg()->editor()->countries();
    const auto& categoriesProhibitedSomewhere = countries.categoriesProhibitedSomewhere();
    auto categoryToRole =
        categorySlavesToGeomPartMasterRole(categoriesProhibitedSomewhere);
    std::unordered_set<std::string> slaveCategories;
    for (const auto& slaveCategoryAndRole : categoryToRole) {
        slaveCategories.insert(slaveCategoryAndRole.first);
    }
    auto slaveCategoriesToGeomPartMasterRole =
        categorySlavesToGeomPartMasterRole(slaveCategories);
    categoryToRole.insert(
        slaveCategoriesToGeomPartMasterRole.begin(),
        slaveCategoriesToGeomPartMasterRole.end());
    return categoryToRole;
}

void addProhibitedMastersToCollection(
    const GeoObject* object,
    ObjectsCache& cache,
    GeoObjectCollection& collection)
{
    static const auto categoryToGeomPartMasterRole = categoryToGeomPartProhibitedMasterRole();
    const auto& countries = cfg()->editor()->countries();
    const auto categoriesProhibitedSomewhere = countries.categoriesProhibitedSomewhere();
    auto it = categoryToGeomPartMasterRole.find(object->categoryId());
    if (it == categoryToGeomPartMasterRole.end()) {
        return;
    }
    for (const auto& master : object->masterRelations().range(it->second)) {
        auto masterPtr = cache.getExisting(master.id());
        if (categoriesProhibitedSomewhere.count(master.categoryId())) {
            collection.add(masterPtr);
        }
        addProhibitedMastersToCollection(masterPtr.get(), cache, collection);
    }
}
} // namespace
void
Validator::collectObjects()
{
    /// For contour validation:
    ///   if element of a contour has modified relations to junctions,
    ///   contour must be validated

    const auto& contourDefs = cfg()->editor()->contourObjectsDefs();
    auto elements = cache_.find(
        [&contourDefs, this] (const GeoObject* object) {
            bool isModifiedPartOfContour =
                is<LinearElement>(object)
                && contourDefs.partType(object->categoryId()) ==
                   ContourObjectsDefs::PartType::LinearElement
                && object->isModifiedGeom();
            if (!isModifiedPartOfContour) {
                return false;
            }
            return !allowInvalidContoursWithinContext(object->id());
        });

    std::map<TOid, StringSet> elementToRoleMap;
    for (const auto& element : elements) {
        const auto& roleId =
            contourDefs.contourDef(element->categoryId()).contour.linearElement.roleId;
        elementToRoleMap.insert({element->id(), StringSet{roleId}});
    }

    cache_.relationsManager().loadRelations(RelationType::Master, elementToRoleMap);
    TOIds contourIds;
    for (const auto& element : elements) {
        const auto& roleId =
            contourDefs.contourDef(element->categoryId()).contour.linearElement.roleId;
        for (const auto& rel : element->masterRelations().range(roleId)) {
            contourIds.insert(rel.id());
            auto it = editContexts_.find(UniqueId(element->id()));
            if (it != editContexts_.end()) {
                editContexts_.insert({UniqueId(rel.id()), it->second});
            }
        }
    }
    collection_.append(cache_.get(contourIds));

    /// For CATEGORIES_TO_ALWAYS_VALIDATE_TOPOLOGY
    /// check geometry altered elements owned by objects
    /// from this list
    auto elementsWithModifiedGeom = cache_.find(
        [] (const GeoObject* object) {
            return is<LinearElement>(object) && object->isModifiedGeom();
        });
    elementToRoleMap.clear();
    for (const auto& element : elementsWithModifiedGeom) {
        const auto& elementCategory = element->category();
        for (const auto& masterRole : elementCategory.masterRoles()) {
            if (CATEGORIES_TO_ALWAYS_VALIDATE_TOPOLOGY.count(masterRole.categoryId())) {
                elementToRoleMap[element->id()].insert(masterRole.roleId());
            }
        }
    }
    cache_.relationsManager().loadRelations(RelationType::Master, elementToRoleMap);
    TOIds oidsToEnforceTopoValidity;
    for (const auto& element : elementsWithModifiedGeom) {
        const auto& elementCategory = element->category();
        for (const auto& masterRole : elementCategory.masterRoles()) {
            if (!CATEGORIES_TO_ALWAYS_VALIDATE_TOPOLOGY.count(masterRole.categoryId())) {
                continue;
            }
            for (const auto& rel : element->masterRelations().range(masterRole.roleId())) {
                if (!collection_.getById(rel.id())) {
                    oidsToEnforceTopoValidity.insert(rel.id());
                }
            }
        }
    }
    collection_.append(cache_.get(oidsToEnforceTopoValidity));

    //For prohibited objects check
    // if object is geompart of prohibited object category
    // load master prohibited object to validation collection
    const auto modifiedGeom =
        cache_.find([](const GeoObject* obj) { return obj->isModifiedGeom(); });
    for (const auto& modifiedObj : modifiedGeom) {
        addProhibitedMastersToCollection(modifiedObj.get(), cache_, collection_);
    }
}

void
Validator::validate()
{
    std::ostringstream os;
    DEBUG() << (collection_.dump(os), os.str());

    for (const auto& obj : collection_) {
        if (!obj->isDeleted()) {
            obj->applyVisitor(*this);
        }
    }
}

bool
Validator::allowToIgnoreRestrictions(const GeoObject* obj)
{
    auto it = ignoreRestrictionsCategories_.find(obj->categoryId());
    if (it != ignoreRestrictionsCategories_.end()) {
        return it->second;
    }
    bool allow = CheckPermissions(uid_, cache_.workCore())
        .allowToIgnoreRestrictions(obj->category());
    ignoreRestrictionsCategories_[obj->categoryId()] = allow;
    return allow;
}

void
Validator::validateObjectOverlay(const GeoObject* obj)
{
    if (!obj->isCreated() && !obj->isModifiedGeom()) {
        return;
    }
    std::vector<OverlayMode> overlayModes;
    enumerateValues(overlayModes);

    auto& workView = cache_.workView();
    const auto& overlayController = cfg()->editor()->overlayGroups();
    CheckPermissions permissionsChecker(uid_, cache_.workCore());
    for (auto overlayMode : overlayModes) {
        const auto overlayGroups =
            overlayController.findGroupsByOverlayCategoryId(obj->categoryId(), overlayMode);
        if (overlayGroups.empty()) {
            continue;
        }

        bool isIntersection = false;
        for (const auto& overlayGroupPtr : overlayGroups) {
            views::ObjectsQuery objectsQuery;
            objectsQuery.addCondition(
                views::CategoriesCondition(workView, overlayGroupPtr->baseCategoryIds()));
            objectsQuery.addCondition(
                views::IntersectsGeometryCondition(workView, obj->geom()));
            if (overlayMode == OverlayMode::Forbidden &&
                permissionsChecker.isUserHasAccessToIgnoreOverlay(
                    overlayGroupPtr->baseCategoryIds()))
            {
                objectsQuery.addCondition(
                    views::NoneOfDomainAttributesCondition(workView, {ATTR_SYS_IGNORE_FORBIDDEN_OVERLAY}));
            }
            isIntersection =
                isIntersection || !objectsQuery.exec(workView, cache_.branchContext().branch.id()).empty();
        }

        if (overlayMode == OverlayMode::Required) {
             WIKI_REQUIRE(
                isIntersection,
                ERR_INDOOR_GEOMETRY_OVERLAY_REQUIRED,
                "Object " << obj->categoryId() << " should intersect indoor");
        } else if (overlayMode == OverlayMode::Forbidden) {
            WIKI_REQUIRE(
                !isIntersection,
                ERR_INDOOR_GEOMETRY_OVERLAY_FORBIDDEN,
                "Object " << obj->categoryId() << " should not intersect indoor");
        }
    }
}

} // namespace wiki
} // namespace maps
