#include "layer_processor.h"
#include "ft_type.h"
#include "validate.h"
#include "line_splitter.h"

#include <boost/lexical_cast.hpp>
#include <yandex/maps/wiki/configs/editor/restrictions.h>

#include <regex>
#include <unordered_map>
#include <unordered_set>

namespace cfg = maps::wiki::configs::editor;

namespace maps {
namespace wiki {
namespace importer {

namespace {

namespace field {
const std::string DB_ID = "db_id";
const std::string FT_TYPE_ID = "ft_type_id";
const std::string RD_ID = "rd_id";
const std::string TEMP_RD_ID = "temp_rd_id";
const std::string AD_ID = "ad_id";
const std::string TEMP_BLD_COMPLEX_ID = "temp_bc_id";
const std::string INDOOR_UNIVERSAL_ID = "uni_id";
const std::string RUBRIC_ID = "rubric_id";
const std::string RUBRIC = "rubric";
} // namespace field

const std::string POI_LAYER_NAME = "poi";
const std::string AD_LAYER_NAME = "ad";
const std::string ADDR_LAYER_NAME = "addr";
const std::string RD_LAYER_NAME = "rd";
const std::string INDOOR_LEVEL_LAYER_NAME = "indoor_level";
const std::string INDOOR_POI_LAYER_NAME = "indoor_poi";

const std::string INDOOR_NAME_REGEX_PATTERN = "(\\w+)_(-?\\d+)"; // ex: indoor_barrier_1

const std::unordered_set<std::string> CONTOUR_CATEGORY_NAMES {
    "hydro",
    "relief",
    "vegetation"
};

const std::unordered_set<std::string> CONTOUR_ELEMENTS_CATEGORY_NAMES {
    "ad_el",
    "hydro_fc_el",
    "relief_el",
    "vegetation_el"
};

template<typename T>
std::string getListAsString(const T* valueList, int count)
{
    std::vector<T> values;
    for (int i = 0; i < count; ++i) {
        values.push_back(valueList[i]);
    }
    return common::join(values, IDS_DELIM);
}

std::string getValue(int i, OGRFieldType type, const OGRFeaturePtr& ogrFeature)
{
    std::string value;
    if (type == OGRFieldType::OFTString) {
        value = ogrFeature->GetFieldAsString(i);
        REQUIRE(CPLIsUTF8(value.data(), value.length()), "Non utf-8 encoding");
    } else if (type == OGRFieldType::OFTInteger) {
        value = std::to_string(ogrFeature->GetFieldAsInteger(i));
    } else if (type == OGRFieldType::OFTInteger64) {
        value = std::to_string(ogrFeature->GetFieldAsInteger64(i));
    } else if (type == OGRFieldType::OFTIntegerList) {
        int count = 0;
        const int* valueList = ogrFeature->GetFieldAsIntegerList(i, &count);
        value = getListAsString<int>(valueList, count);
    } else if (type == OGRFieldType::OFTInteger64List) {
        int count = 0;
        const long long* valueList = ogrFeature->GetFieldAsInteger64List(i, &count);
        value = getListAsString<long long>(valueList, count);
    } else if (type == OGRFieldType::OFTReal) {
        //Cartographers use double numbers for object ids
        value = std::to_string(static_cast<uint64_t>(ogrFeature->GetFieldAsDouble(i)));
    } else {
        throw maps::RuntimeError() << "Field " << i << " has wrong type " << static_cast<int>(type);
    }
    return value;
}

void addJunctions(ObjectPtr& object, const OGRLineStringPtr& line, const cfg::Category& jcCategory)
{
    ASSERT(line);

    OGRPoint startPoint;
    OGRPoint endPoint;
    line->StartPoint(&startPoint);
    line->EndPoint(&endPoint);

    auto startJc = object->cache().getOrCreateJunction(object->id(), startPoint, jcCategory);
    auto endJc = object->cache().getOrCreateJunction(object->id(), endPoint, jcCategory);

    object->addSlave(role::START, startJc);
    object->addSlave(role::END, endJc);
}

void makeCenterForAd(
    ObjectPtr& object,
    const OGRGeometry* geometry,
    const cfg::Category& adCntCategory)
{
    REQUIRE(geometry, "Null geometry");
    REQUIRE(geometry->getGeometryType() == wkbPolygon
        || geometry->getGeometryType() == wkbMultiPolygon,
        "Wrong geometry type " << OGRGeometryTypeToName(geometry->getGeometryType()));

    auto makeCenter = [&](const OGRPolygon* polygon)
    {
        OGRPoint center(0.0, 0.0);
        auto result = polygon->PointOnSurface(&center);
        REQUIRE(result == OGRERR_NONE, "Failed to compute polygon internal point");

        validateGeometry(adCntCategory, &center);
        auto& adCnt = object->addSlave(role::CENTER, adCntCategory).relatedObject;
        adCnt->setWkb(getWkb(&center));
    };

    if (geometry->getGeometryType() == wkbPolygon) {
        const auto* polygon = static_cast<const OGRPolygon*>(geometry);
        makeCenter(polygon);
    } else {
        const auto* multiPolygon = static_cast<const OGRMultiPolygon*>(geometry);
        REQUIRE(multiPolygon->getNumGeometries() > 0, "Zero parts in multipolygon");

        const auto* polygon = static_cast<const OGRPolygon*>(multiPolygon->getGeometryRef(0));
        makeCenter(polygon);
    }
}

void makeSlavesForContourObject(
    ObjectPtr& object,
    const OGRGeometry* geometry,
    const std::string& fcRoleName,
    const cfg::Category& fcCategory,
    const cfg::Category& elCategory,
    const cfg::Category& jcCategory)
{
    REQUIRE(geometry, "Null geometry");
    REQUIRE(geometry->getGeometryType() == wkbPolygon
        || geometry->getGeometryType() == wkbMultiPolygon,
        "Wrong geometry type " << OGRGeometryTypeToName(geometry->getGeometryType()));

    LineSplitter splitter(elCategory.restrictions());

    enum class FaceType
    {
        Exterior,
        Interior
    };

    auto makeFace = [&](const OGRLinearRing* ring, FaceType faceType)
    {
        ASSERT(ring);

        OGRPoint startPoint;
        OGRPoint endPoint;
        ring->StartPoint(&startPoint);
        ring->EndPoint(&endPoint);
        REQUIRE(startPoint.Equals(&endPoint), "Ring should be closed");

        auto& fc = object->addSlave(fcRoleName, fcCategory).relatedObject;
        if (faceType == FaceType::Interior) {
            fc->addAttribute(fcCategory.id() + ":is_interior", "1");
        }

        auto lines = splitter.split(ring);
        for (const auto& line : lines) {
            validateGeometry(elCategory, line.get());

            auto& el = fc->addSlave(role::PART, elCategory).relatedObject;
            el->setWkb(getWkb(line.get()));

            addJunctions(el, line, jcCategory);
        }
    };

    auto processPolygon = [&](const OGRPolygon* polygon)
    {
        ASSERT(polygon);
        const auto* exteriorRing = polygon->getExteriorRing();
        makeFace(exteriorRing, FaceType::Exterior);

        for (int i = 0; i < polygon->getNumInteriorRings(); ++i) {
            const auto* interiorRing = polygon->getInteriorRing(i);
            makeFace(interiorRing, FaceType::Interior);
        }
    };

    if (geometry->getGeometryType() == wkbPolygon) {
        const auto* polygon = static_cast<const OGRPolygon*>(geometry);
        processPolygon(polygon);
    } else {
        const auto* multiPolygon = static_cast<const OGRMultiPolygon*>(geometry);
        REQUIRE(multiPolygon->getNumGeometries() > 0, "Zero parts in multipolygon");

        for (int i = 0; i < multiPolygon->getNumGeometries(); ++i) {
            const auto* polygon = static_cast<const OGRPolygon*>(multiPolygon->getGeometryRef(i));
            processPolygon(polygon);
        }
    }
}

} // namespace

//======================================================================


LayerProcessor::LayerProcessor(
    Action action,
    ObjectsCache& cache,
    const EditorConfig& editorConfig,
    const ImportConfig& importConfig,
    const std::string& layerName)
        : cache_(cache)
        , editorConfig_(editorConfig)
        , importConfig_(importConfig)
        , action_(action)
        , rubricsMatcher_(editorConfig)
{
    // check if layer has master layer id
    std::regex pattern(INDOOR_NAME_REGEX_PATTERN);
    std::smatch match;
    if (std::regex_match(layerName, match, pattern)) {
        layerName_ = match[1];
        masterIndoorLevelId_ = boost::lexical_cast<LevelId>(match[2]);
    } else {
        layerName_ = layerName;
    }

    if (layerName_ == POI_LAYER_NAME) {
        mode_ = WorkMode::Poi;
    } else if (layerName_ == AD_LAYER_NAME) {
        mode_ = WorkMode::Ad;
    } else if (layerName_ == ADDR_LAYER_NAME) {
        mode_ = WorkMode::Addr;
    } else if (layerName_ == RD_LAYER_NAME) {
        mode_ = WorkMode::Rd;
    } else if (CONTOUR_CATEGORY_NAMES.count(layerName_)) {
        mode_ = WorkMode::Contour;
    } else if (CONTOUR_ELEMENTS_CATEGORY_NAMES.count(layerName_)) {
        mode_ = WorkMode::ContourElement;
    } else if (layerName_ == INDOOR_LEVEL_LAYER_NAME) {
        mode_ = WorkMode::IndoorLevel;
    } else if (layerName_.starts_with(INDOOR_POI_LAYER_NAME)) { // ex: indoor_poi, indoor_poi_shopping, indoor_poi_food etc
        mode_ = WorkMode::IndoorPoi;
    } else {
        mode_ = WorkMode::Category;
    }
}

void LayerProcessor::findAndPrepareCategories(OGRLayer& layer)
{
    auto* featureDefn = layer.GetLayerDefn();
    if (featureDefn->GetGeomType() != wkbNone) {
        validateLayerSRS(layer);
    }

    std::set<std::string> categoryIds;

    if (mode_ == WorkMode::Poi) {
        auto ftTypeIdIndex = featureDefn->GetFieldIndex(field::FT_TYPE_ID.c_str());
        REQUIRE(ftTypeIdIndex >= 0, "Field ft_type_id is not found");

        layer.ResetReading();
        OGRFeaturePtr ogrFeature(layer.GetNextFeature());
        while (ogrFeature) {
            categoryIds.emplace(featureCategoryId(ogrFeature));
            ogrFeature.reset(layer.GetNextFeature());
        }
    } else if (mode_ == WorkMode::IndoorPoi) {
        auto rubricIndex = featureDefn->GetFieldIndex(field::RUBRIC.c_str());
        if (rubricIndex < 0) {
            rubricIndex = featureDefn->GetFieldIndex(field::RUBRIC_ID.c_str());
        }
        REQUIRE(rubricIndex >= 0, "Field `rubric` or `rubric_id` is not found");

        layer.ResetReading();
        OGRFeaturePtr ogrFeature(layer.GetNextFeature());
        while (ogrFeature) {
            categoryIds.emplace(featureCategoryId(ogrFeature));
            ogrFeature.reset(layer.GetNextFeature());
        }
    } else {
        categoryIds.emplace(layerName_);
    }

    for (const auto& categoryId : categoryIds) {
        if (action_ == Action::Delete) {
            REQUIRE(mode_ == WorkMode::Poi
                || mode_ == WorkMode::Rd
                || mode_ == WorkMode::Ad
                || importConfig_.isCategoryAllowedToDelete(categoryId),
                "Deletion is not allowed for category " << categoryId);
        }

        createFeatureProcessor(categoryId, featureDefn);
    }
}

Objects LayerProcessor::prepare(const OGRFeaturePtr& ogrFeature) const
{
    auto* featureDefn = ogrFeature->GetDefnRef();

    auto categoryId = featureCategoryId(ogrFeature);
    const auto& category = editorConfig_.category(categoryId);
    const auto& processor = featureProcessor(categoryId);
    auto featureId = ogrFeature->GetFID();
    if (mode_ == WorkMode::IndoorLevel) {
        auto universalIdIndex = featureDefn->GetFieldIndex(field::INDOOR_UNIVERSAL_ID.c_str());
        REQUIRE(universalIdIndex >= 0, "Field: " + field::INDOOR_UNIVERSAL_ID  + " is not found");
        auto* fieldDefn = featureDefn->GetFieldDefn(universalIdIndex);
        auto value = getValue(universalIdIndex, fieldDefn->GetType(), ogrFeature);
        featureId = cast<TFeatureId>(value);
    }

    auto firstObject = cache_.makeObject(ID{layerName_, featureId}, EMPTY_DB_ID, category);
    auto objects = processor.processGeometry(firstObject, ogrFeature->GetGeometryRef());

    REQUIRE(!objects.empty(), "Empty objects after geometry processing");
    REQUIRE(objects.front() == firstObject, "Wrong array after geometry processing");
    if (action_ == Action::Edit || action_ == Action::Delete) {
        REQUIRE(objects.size() == 1, "One object per row in edit and delete modes");
    }
    auto futureMasterRelationInfo = importConfig_.getIndoorFutureMasterRelationInfo(category.id());
    for (auto& object : objects) {
        if (masterIndoorLevelId_ && futureMasterRelationInfo) {
            const auto& role = (*futureMasterRelationInfo).role;
            const auto& masterLayer = (*futureMasterRelationInfo).masterLayer;
            processor.addMasterIndoorLevel(object, role, ID{masterLayer, *masterIndoorLevelId_});
        }

        for (int i = 0; i < featureDefn->GetFieldCount(); ++i) {
            auto* fieldDefn = featureDefn->GetFieldDefn(i);
            REQUIRE(fieldDefn, "Invalid field by index " << i);

            auto value = getValue(i, fieldDefn->GetType(), ogrFeature);
            if (value.empty()) {
                continue;
            }

            processor.processField(object, fieldDefn->GetNameRef(), value);
        }
    }

    if (action_ == Action::Edit || action_ == Action::Delete) {
        for (auto& object : objects) {
            REQUIRE(object->dbId(), "Object id is empty");
        }
    }

    return objects;
}

std::string LayerProcessor::featureCategoryId(const OGRFeaturePtr& ogrFeature) const
{
    if (mode_ == WorkMode::Poi) {
        auto* featureDefn = ogrFeature->GetDefnRef();

        auto ftTypeIdIndex = featureDefn->GetFieldIndex(field::FT_TYPE_ID.c_str());
        REQUIRE(ftTypeIdIndex >= 0, "Field ft_type_id is not found");

        auto* fieldDefn = featureDefn->GetFieldDefn(ftTypeIdIndex);
        REQUIRE(fieldDefn, "Invalid field by index " << ftTypeIdIndex);

        auto value = getValue(ftTypeIdIndex, fieldDefn->GetType(), ogrFeature);
        REQUIRE(!value.empty(), "ft_type_id is empty");

        auto ftTypeId = cast<uint64_t>(value);
        return ftTypeToCategory(static_cast<ymapsdf::ft::Type>(ftTypeId));
    } else if (mode_ == WorkMode::IndoorPoi) {
        auto* featureDefn = ogrFeature->GetDefnRef();

        auto rubricIndex = featureDefn->GetFieldIndex(field::RUBRIC.c_str());
        if (rubricIndex < 0) {
            rubricIndex = featureDefn->GetFieldIndex(field::RUBRIC_ID.c_str());
        }
        REQUIRE(rubricIndex >= 0, "Field `rubric` or `rubric_id` is not found");

        auto* fieldDefn = featureDefn->GetFieldDefn(rubricIndex);
        REQUIRE(fieldDefn, "Invalid field by index " << rubricIndex);

        auto value = getValue(rubricIndex, fieldDefn->GetType(), ogrFeature);
        REQUIRE(!value.empty(), "rubric is empty");

        return rubricsMatcher_.rubricToCategory(value);
    }
    return layerName_;
}

void LayerProcessor::createFeatureProcessor(
    const std::string& categoryId,
    OGRFeatureDefn* featureDefn)
{
    const auto& category = editorConfig_.category(categoryId);

    FeatureProcessor processor(editorConfig_, category, rubricsMatcher_);

    for (int i = 0; i < featureDefn->GetFieldCount(); ++i) {
        auto* fieldDefn = featureDefn->GetFieldDefn(i);
        REQUIRE(fieldDefn, "Invalid field by index " << i);

        makeFieldProcessor(processor, fieldDefn);
    }

    validateFields(processor, featureDefn);

    switch (action_) {
    case Action::Add:
        makeGeometryProcessorForAddAction(processor, featureDefn);
        break;
    case Action::Edit:
        makeGeometryProcessorForEditAction(processor, featureDefn);
        break;
    case Action::Delete:
        REQUIRE(featureDefn->GetGeomType() == wkbNone
            || featureDefn->GetGeomType() == wkbUnknown,
            "No geometry is allowed in delete mode");
        break;
    }

    auto res = categoryIdToFeatureProcessor_.emplace(category.id(), std::move(processor));
    REQUIRE(res.second, "Attempt to create processor for category " << category.id() << " twice");
}

const FeatureProcessor& LayerProcessor::featureProcessor(const std::string& categoryId) const
{
    auto it = categoryIdToFeatureProcessor_.find(categoryId);
    REQUIRE(it != categoryIdToFeatureProcessor_.end(),
        "There is no feature processor for category " << categoryId);
    return it->second;
}

void LayerProcessor::makeFieldProcessor(FeatureProcessor& processor, OGRFieldDefn* fieldDefn) const
{
    const auto& category = processor.category();

    std::string fieldName = fieldDefn->GetNameRef();
    if (processor.tryAddNameField(fieldName, fieldDefn->GetType())) {
        return;
    }

    if (processor.tryDeleteNameField(fieldName, fieldDefn->GetType())) {
        return;
    }

    if ((action_ == Action::Edit || action_ == Action::Delete) && fieldName == field::DB_ID) {
        processor.addDbIdField(fieldName);
        return;
    }

    auto masterRelation = importConfig_.getMasterRelation(category.id(), fieldName);
    if (masterRelation) {
        processor.addMasterField(fieldName, masterRelation->role, editorConfig_.category(masterRelation->masterCategoryId));
        return;
    }

    auto futureMasterRelation = importConfig_.getFutureMasterRelation(category.id(), fieldName);
    if (futureMasterRelation) {
        processor.addFutureMasterField(fieldName, futureMasterRelation->role, futureMasterRelation->masterLayer);
        return;
    }

    // avaliable fieldName's: "rubric_id", "rubric" и "type"
    if (rubricsMatcher_.isFieldNameMatched(category.id(), fieldName)) {
        auto attrName = importConfig_.getAttributeName(category.id(), fieldName);
        processor.addIndoorRubricField(fieldName, attrName);
        return;
    }

    auto attrName = importConfig_.getAttributeName(category.id(), fieldName);
    if (category.isAttributeDefined(category.id() + ":" + attrName)) {
        processor.addNormalField(fieldName, category.id() + ":" + attrName, fieldDefn->GetType());
        return;
    }

    if (category.isAttributeDefined(attrName)) {
        processor.addNormalField(fieldName, attrName, fieldDefn->GetType());
        return;
    }

    if (mode_ == WorkMode::Poi && fieldName == field::FT_TYPE_ID) {
        //skip ft_type_id field for the poi categories without ft_type_id attribute
        return;
    }

    if (processor.tryParseRelation(fieldName)) {
        return;
    }

    if (fieldName == field::TEMP_BLD_COMPLEX_ID) {
        REQUIRE(category.id() == cat::BLD, "Attempt to add bld complex to wrong category " << category.id());
        processor.addTempBldComplexField(fieldName);
        return;
    }

    if (fieldName.starts_with(cat::POI_PREFIX)) {
        processor.addMasterPoiIdsField(fieldName);
        return;
    }

    throw maps::RuntimeError()
        << "Failed to parse field " << fieldName << " for category " << category.id();
}

void LayerProcessor::validateFields(
    const FeatureProcessor& processor,
    OGRFeatureDefn* featureDefn) const
{
    if (action_ == Action::Edit) {
        REQUIRE(processor.hasFieldProcessor(field::DB_ID), "There is no db_id field");
    } else if (action_ == Action::Delete) {
        REQUIRE(processor.hasFieldProcessor(field::DB_ID), "There is no db_id field");

        auto allowedFieldCount = (mode_ == WorkMode::Poi) ? 2 : 1; //db_id and ft_type_id for poi
        REQUIRE(featureDefn->GetFieldCount() == allowedFieldCount, "There must be only db_id field");
    }

    if (action_ == Action::Add && mode_ == WorkMode::Addr) {
        REQUIRE(processor.hasFieldProcessor(field::RD_ID)
            || processor.hasFieldProcessor(field::TEMP_RD_ID)
            || processor.hasFieldProcessor(field::AD_ID),
            "Addr has no rd_id or temp_rd_id or ad_id field");
    }

    if (action_ == Action::Add) {
        for (const auto& field : importConfig_.requiredFields(processor.category().id())) {
            REQUIRE(processor.hasFieldProcessor(field), "Required field '" << field << "' is missing");
        }
    }
}

void LayerProcessor::makeGeometryProcessorForAddAction(
    FeatureProcessor& processor,
    OGRFeatureDefn* featureDefn) const
{
    switch (mode_) {
    case WorkMode::Ad:
        if (featureDefn->GetGeomType() == wkbPoint) {
            makeGeometryProcessorForPointAd(processor, featureDefn);
        } else if (featureDefn->GetGeomType() == wkbPolygon
                || featureDefn->GetGeomType() == wkbMultiPolygon) {
            makeGeometryProcessorForContourObjects(processor, featureDefn);
        } else {
            throw maps::RuntimeError()
                << "Wrong AD geometry type " << OGRGeometryTypeToName(featureDefn->GetGeomType());
        }
        break;
    case WorkMode::Contour:
        makeGeometryProcessorForContourObjects(processor, featureDefn);
        break;
    case WorkMode::ContourElement:
        makeGeometryProcessorForContourElementObjects(processor, featureDefn);
        break;
    case WorkMode::Rd:
        //no geometry for rd
        break;
    default:
        processor.setGeometryProcessor(
            [](ObjectPtr& object, const OGRGeometry* geometry) -> Objects {
                REQUIRE(geometry, "Can't find object geometry for category " << object->category().id());

                validateGeometry(object->category(), geometry);
                object->setWkb(getWkb(geometry));

                return { object };
            });
    }
}

void LayerProcessor::makeGeometryProcessorForEditAction(
    FeatureProcessor& processor,
    OGRFeatureDefn* featureDefn) const
{
    if (featureDefn->GetGeomType() == wkbNone
        || featureDefn->GetGeomType() == wkbUnknown) {
        return;
    }

    if (mode_ == WorkMode::Ad) {
        makeGeometryProcessorForContourObjects(processor, featureDefn);
        return;
    }
    if (mode_ == WorkMode::Addr) {
        processor.setGeometryProcessor(
            [](ObjectPtr& object, const OGRGeometry* geometry) -> Objects {
                REQUIRE(geometry, "Can't find object geometry for category " << object->category().id());

                validateGeometry(object->category(), geometry);
                object->setWkb(getWkb(geometry));

                return { object };
            });
        return;
    }

    throw maps::LogicError()
        << "No geometry is allowed in edit mode for category " << processor.category().id();
}

void LayerProcessor::makeGeometryProcessorForContourObjects(
    FeatureProcessor& processor,
    OGRFeatureDefn* featureDefn) const
{
    REQUIRE(featureDefn->GetGeomType() == wkbPolygon
            || featureDefn->GetGeomType() == wkbMultiPolygon,
            "Wrong contour geometry type " << OGRGeometryTypeToName(featureDefn->GetGeomType()));

    const auto& category = processor.category();

    REQUIRE(category.isSlaveRoleDefined(role::PART) || category.isSlaveRoleDefined(role::FC_PART),
        "Category " << category.id() << " has not part or fc_part role");
    auto fcRoleName = category.isSlaveRoleDefined(role::PART) ? role::PART : role::FC_PART;

    const auto& fcRole = category.slaveRole(fcRoleName);
    const auto& fcCategory = editorConfig_.category(fcRole.categoryId());

    REQUIRE(fcCategory.isSlaveRoleDefined(role::PART), "Category " << category.id() << " has not part role");

    const auto& elRole = fcCategory.slaveRole(role::PART);
    const auto& elCategory = editorConfig_.category(elRole.categoryId());

    REQUIRE(elCategory.isSlaveRoleDefined(role::START),
        "Category " << elCategory.id() << " has no start role");
    REQUIRE(elCategory.isSlaveRoleDefined(role::END),
        "Category " << elCategory.id() << " has no end role");

    const auto& jcRole = elCategory.slaveRole(role::START);
    const auto& jcCategory = editorConfig_.category(jcRole.categoryId());

    if (action_ == Action::Add) {
        const auto& adCntCategory = editorConfig_.category(cat::AD_CNT);

        processor.setGeometryProcessor(
            [&adCntCategory,
            fcRoleName,
            &fcCategory,
            &elCategory,
            &jcCategory]
            (ObjectPtr& object, const OGRGeometry* geometry) -> Objects
            {
                REQUIRE(geometry, "Can't find object geometry for category " << object->category().id());

                if (object->category().id() == cat::AD) {
                    makeCenterForAd(object, geometry, adCntCategory);
                }
                makeSlavesForContourObject(object, geometry, fcRoleName, fcCategory, elCategory, jcCategory);

                return { object };
            });
    } else if (action_ == Action::Edit) {
        processor.setGeometryProcessor(
            [fcRoleName,
            &fcCategory,
            &elCategory,
            &jcCategory]
            (ObjectPtr& object, const OGRGeometry* geometry) -> Objects
            {
                REQUIRE(geometry, "Can't find object geometry for category " << object->category().id());

                makeSlavesForContourObject(object, geometry, fcRoleName, fcCategory, elCategory, jcCategory);

                return { object };
            });
    }
}

void LayerProcessor::makeGeometryProcessorForPointAd(
        FeatureProcessor& processor,
        OGRFeatureDefn*) const
{
    const auto& adCntCategory = editorConfig_.category(cat::AD_CNT);

    processor.setGeometryProcessor(
        [&adCntCategory]
        (ObjectPtr& object, const OGRGeometry* geometry) -> Objects
        {
            REQUIRE(geometry, "Can't find object geometry for category " << adCntCategory.id());

            validateGeometry(adCntCategory, geometry);
            auto& adCnt = object->addSlave(role::CENTER, adCntCategory).relatedObject;
            adCnt->setWkb(getWkb(geometry));

            return { object };
        });
}

void LayerProcessor::makeGeometryProcessorForContourElementObjects(
    FeatureProcessor& processor,
    OGRFeatureDefn* featureDefn) const
{
    REQUIRE(featureDefn->GetGeomType() == wkbLineString,
            "Wrong contour element geometry type " << OGRGeometryTypeToName(featureDefn->GetGeomType()));

    const auto& category = processor.category();

    REQUIRE(category.isSlaveRoleDefined(role::START),
        "Category " << category.id() << " has no start role");
    REQUIRE(category.isSlaveRoleDefined(role::END),
        "Category " << category.id() << " has no end role");

    const auto& jcRole = category.slaveRole(role::START);
    const auto& jcCategory = editorConfig_.category(jcRole.categoryId());

    processor.setGeometryProcessor(
        [&jcCategory]
        (ObjectPtr& object, const OGRGeometry* geometry) -> Objects
        {
            REQUIRE(geometry, "Can't find object geometry for category " << object->category().id());

            Objects outputObjects;

            LineSplitter splitter(object->category().restrictions());
            auto lines = splitter.split(static_cast<const OGRLineString*>(geometry));
            for (const auto& line : lines) {
                auto currentObject = (line == lines.front())
                    ? object
                    : object->cache().makeObject(object->id(), EMPTY_DB_ID, object->category());

                outputObjects.push_back(currentObject);

                validateGeometry(currentObject->category(), line.get());
                currentObject->setWkb(getWkb(line.get()));

                addJunctions(currentObject, line, jcCategory);
            }

            REQUIRE(outputObjects.size() == lines.size(), "Wrong output object size");

            return outputObjects;
        });
}

} // importer
} // wiki
} // maps
