#include "hd_map.h"
#include "registry.h"
#include "generic.h"

#include <maps/wikimap/mapspro/services/editor/src/configs/categories_strings.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/config.h>
#include <maps/wikimap/mapspro/services/editor/src/format.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/category_traits.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/line_object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/areal_object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/point_object.h>

#include <yandex/maps/wiki/configs/editor/categories.h>
#include <yandex/maps/wiki/common/string_utils.h>

namespace maps::wiki::srv_attrs {

namespace {

const std::string DEFAULT_ZLEV = "0";
const std::string ROAD_MARKING_POINT_TEXT_FT_TYPE_ID = "11000";

const std::map<std::string, std::map<std::string, std::string>> HD_MAP_CATEGORY_TYPE_TO_FT_TYPE
{
    {
        CATEGORY_ROAD_SURFACE, {
            {"road", "10010"},
            {"bridge", "10020"},
            {"tunnel", "10030"},
            {"tram", "10040"},
            {"divider", "10050"},
            {"vegetation", "10060"},
            {"under_construction", "10070"},
        }
    },
    {
        CATEGORY_ROAD_MARKING_POLYGONAL, {
            {"island", "20010"},
            {"pedestrian_14_1", "20020"},
            {"pedestrian_14_2", "20030"},
            {"pedestrian_14_3", "20040"},
            {"pedestrian_14_4", "20050"},
            {"bicycle_15_1", "20060"},
            {"bicycle_15_2", "20070"},
            {"waffel", "20080"},
        }
    },
    {
        CATEGORY_ROAD_MARKING_LINEAR, {
            {"lane_1", "30010"},
            {"lane_2", "30020"},
            {"lane_3", "30030"},
            {"lane_4", "30040"},
            {"lane_5", "30050"},
            {"lane_6", "30060"},
            {"lane_7", "30070"},
            {"lane_8", "30080"},
            {"lane_9", "30090"},
            {"lane_10", "30100"},
            {"lane_11", "30110"},
            {"lane_12", "30120"},
            {"lane_13", "30130"},
            {"transport_stop_17_1", "30140"},
            {"transport_stop_17_2", "30150"},
            {"bump_25", "30160"},
            {"bump_26","30170"},
            {"parking_33", "30180"},
            {"parking_diagonal", "30181"},
            {"parking_dotted_line", "30182"},
            {"parking_perpendicular", "30183"},
        }
    },
    {
        CATEGORY_ROAD_MARKING_POINT_LANE_DIRECTION, {
            {"1", "11010"},
            {"2", "11011"},
            {"3", "11012"},
            {"4", "11013"},
            {"5", "11014"},
            {"6", "11015"},
            {"7", "11016"},
            {"8", "11017"},
            {"9", "11018"},
            {"10", "11019"},
            {"11", "11020"},
            {"12", "11021"},
            {"13", "11022"},
            {"bit_right", "11027"},
            {"bit_left", "11028"},
            {"bit_right_right", "11023"},
            {"bit_left_left", "11024"},
            {"right_right_u_turn", "11025"},
            {"left_left_u_turn", "11026"},
        }
    },
    {
        CATEGORY_ROAD_MARKING_POINT_SYMBOL, {
            {"narrowing_1", "11031"},
            {"narrowing_2", "11032"},
            {"narrowing_3", "11033"},
            {"narrowing_4", "11034"},
            {"yield", "11035"},
            {"stop_1", "11036"},
            {"stop_2", "11037"},
            {"bus_lane", "11038"},
            {"bicycle", "11074"},
            {"pedestrian", "11075"},
        }
    },
    {
        CATEGORY_ROAD_MARKING_POINT_ROAD_SIGN, {
            {"pedestrian", "11051"},
            {"camera", "11052"},
            {"disabled", "11053"},
            {"auto", "11054"},
            {"bus", "11055"},
            {"truck", "11056"},
            {"charging", "11057"},
            {"bicycle", "11058"},
            {"no_u_turn", "11059"},
            {"children", "11060"},
            {"dangerous_right_turn", "11061"},
            {"dangerous_left_turn", "11062"},
        }
    },
    {
        CATEGORY_ROAD_MARKING_POINT_ROAD_SIGN_SPEED_LIMIT, {
            {"5", "11100"},
            {"10", "11101"},
            {"15", "11102"},
            {"20", "11103"},
            {"25", "11104"},
            {"30", "11105"},
            {"35", "11106"},
            {"40", "11107"},
            {"45", "11108"},
            {"50", "11109"},
            {"55", "11110"},
            {"60", "11111"},
            {"65", "11112"},
            {"70", "11113"},
            {"75", "11114"},
            {"80", "11115"},
            {"85", "11116"},
            {"90", "11117"},
            {"95", "11118"},
            {"100", "11119"},
            {"105", "11120"},
            {"110", "11121"},
            {"115", "11122"},
            {"120", "11123"},
            {"125", "11124"},
            {"130", "11125"},
            {"135", "11126"},
            {"140", "11127"},
            {"145", "11128"},
            {"150", "11129"},
        }
    },
};

std::string
parentSurfaceAttribute(
    const GeoObject* object,
    ObjectsCache&,
    const std::string& attrName,
    const std::string& defaultValue)
{
    for (const auto& rel : object->masterRelations().range()) {
        if (rel.categoryId() == CATEGORY_ROAD_SURFACE) {
            return rel.relative()->attributes().value(attrName);
        }
    }
    return defaultValue;
}

std::string
parentSurfaceId(
    const GeoObject* object,
    ObjectsCache&)
{
    for (const auto& rel : object->masterRelations().range()) {
        if (rel.categoryId() == CATEGORY_ROAD_SURFACE) {
            return std::to_string(rel.relative()->id());
        }
    }
    return {};
}

std::string
ftType(const GeoObject* object, ObjectsCache&)
{
    const auto& catId = object->categoryId();
    if (catId == CATEGORY_ROAD_MARKING_POINT_TEXT) {
        return ROAD_MARKING_POINT_TEXT_FT_TYPE_ID;
    }
    const auto catIt = HD_MAP_CATEGORY_TYPE_TO_FT_TYPE.find(catId);
    if (catIt == HD_MAP_CATEGORY_TYPE_TO_FT_TYPE.end()) {
        return {};
    }
    const auto& typeAttr = enumeratedTypeAttrDefForCategory(catId);
    if (!typeAttr) {
        return {};
    }
    const auto& typeToFtType = catIt->second;
    const auto ftTypeIdIt = typeToFtType.find(object->attributes().value(typeAttr->id()));
    if (typeToFtType.end() == ftTypeIdIt) {
        return {};
    }
    return ftTypeIdIt->second;
}

} // namespace

class ROAD_MARKING_POLYGONAL : public ServiceAttributesRegistry::Registrar
{
public:
    ROAD_MARKING_POLYGONAL(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_ROAD_MARKING_POLYGONAL)
    {
        registerAttr(ATTR_ROAD_SURFACE_F_ZLEV,
            CallbackWrapper<ArealObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_F_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_F_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POLYGONAL, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(ATTR_ROAD_SURFACE_T_ZLEV,
            CallbackWrapper<ArealObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_T_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_T_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POLYGONAL, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(SRV_ROAD_SURFACE_ID,
            CallbackWrapper<ArealObject>(parentSurfaceId));
        registerAttr(SRV_FT_TYPE_ID,
            CallbackWrapper<ArealObject>(ftType));
    }
};

class ROAD_MARKING_LINEAR : public ServiceAttributesRegistry::Registrar
{
public:
    ROAD_MARKING_LINEAR(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_ROAD_MARKING_LINEAR)
    {
        registerAttr(ATTR_ROAD_SURFACE_F_ZLEV,
            CallbackWrapper<LineObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_F_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_F_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_LINEAR, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(ATTR_ROAD_SURFACE_T_ZLEV,
            CallbackWrapper<LineObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_T_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_T_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_LINEAR, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(SRV_ROAD_SURFACE_ID,
            CallbackWrapper<LineObject>(parentSurfaceId));
        registerAttr(SRV_FT_TYPE_ID,
            CallbackWrapper<LineObject>(ftType));
    }
};

class ROAD_MARKING_POINT_LANE_DIRECTION : public ServiceAttributesRegistry::Registrar
{
public:
    ROAD_MARKING_POINT_LANE_DIRECTION(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_ROAD_MARKING_POINT_LANE_DIRECTION)
    {
        registerAttr(ATTR_ROAD_SURFACE_F_ZLEV,
            CallbackWrapper<PointObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_F_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_F_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POINT_LANE_DIRECTION, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(ATTR_ROAD_SURFACE_T_ZLEV,
            CallbackWrapper<PointObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_T_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_T_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POINT_LANE_DIRECTION, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(SRV_ROAD_SURFACE_ID,
            CallbackWrapper<PointObject>(parentSurfaceId));
        registerAttr(SRV_FT_TYPE_ID,
            CallbackWrapper<PointObject>(ftType));
    }
};

class ROAD_MARKING_POINT_SYMBOL : public ServiceAttributesRegistry::Registrar
{
public:
    ROAD_MARKING_POINT_SYMBOL(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_ROAD_MARKING_POINT_SYMBOL)
    {
        registerAttr(ATTR_ROAD_SURFACE_F_ZLEV,
            CallbackWrapper<PointObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_F_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_F_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POINT_SYMBOL, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(ATTR_ROAD_SURFACE_T_ZLEV,
            CallbackWrapper<PointObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_T_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_T_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POINT_SYMBOL, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(SRV_ROAD_SURFACE_ID,
            CallbackWrapper<PointObject>(parentSurfaceId));
        registerAttr(SRV_FT_TYPE_ID,
            CallbackWrapper<PointObject>(ftType));
    }
};

class ROAD_MARKING_POINT_ROAD_SIGN : public ServiceAttributesRegistry::Registrar
{
public:
    ROAD_MARKING_POINT_ROAD_SIGN(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_ROAD_MARKING_POINT_ROAD_SIGN)
    {
        registerAttr(ATTR_ROAD_SURFACE_F_ZLEV,
            CallbackWrapper<PointObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_F_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_F_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POINT_ROAD_SIGN, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(ATTR_ROAD_SURFACE_T_ZLEV,
            CallbackWrapper<PointObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_T_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_T_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POINT_ROAD_SIGN, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(SRV_ROAD_SURFACE_ID,
            CallbackWrapper<PointObject>(parentSurfaceId));
        registerAttr(SRV_FT_TYPE_ID,
            CallbackWrapper<PointObject>(ftType));
    }
};

class ROAD_MARKING_POINT_ROAD_SIGN_SPEED_LIMIT : public ServiceAttributesRegistry::Registrar
{
public:
    ROAD_MARKING_POINT_ROAD_SIGN_SPEED_LIMIT(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_ROAD_MARKING_POINT_ROAD_SIGN_SPEED_LIMIT)
    {
        registerAttr(ATTR_ROAD_SURFACE_F_ZLEV,
            CallbackWrapper<PointObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_F_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_F_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POINT_ROAD_SIGN_SPEED_LIMIT, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(ATTR_ROAD_SURFACE_T_ZLEV,
            CallbackWrapper<PointObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_T_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_T_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POINT_ROAD_SIGN_SPEED_LIMIT, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(SRV_ROAD_SURFACE_ID,
            CallbackWrapper<PointObject>(parentSurfaceId));
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<PointObject>(screenLabel));
        registerAttr(SRV_FT_TYPE_ID,
            CallbackWrapper<PointObject>(ftType));
    }
private:
    static std::string screenLabel(const PointObject* pt, ObjectsCache&)
    {
        const auto speedLimit = pt->attributes().value(ATTR_ROAD_MARKING_POINT_ROAD_SIGN_SPEED_LIMIT_VALUE);
        return !speedLimit.empty()
            ? (pt->category().label() + " " + speedLimit)
            : std::string();
    }
};

class ROAD_MARKING_POINT_TEXT : public ServiceAttributesRegistry::Registrar
{
public:
    ROAD_MARKING_POINT_TEXT(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_ROAD_MARKING_POINT_TEXT)
    {
        registerAttr(ATTR_ROAD_SURFACE_F_ZLEV,
            CallbackWrapper<PointObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_F_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_F_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POINT_TEXT, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(ATTR_ROAD_SURFACE_T_ZLEV,
            CallbackWrapper<PointObject, std::string, std::string>(parentSurfaceAttribute, ATTR_ROAD_SURFACE_T_ZLEV, DEFAULT_ZLEV))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_ROAD_SURFACE_T_ZLEV},
                {{RelationType::Master, ROLE_ASSIGNED_ROAD_MARKING_POINT_TEXT, CATEGORY_ROAD_SURFACE}}
            });
        registerAttr(SRV_ROAD_SURFACE_ID,
            CallbackWrapper<PointObject>(parentSurfaceId));
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<PointObject>(screenLabel));
        registerAttr(SRV_FT_TYPE_ID,
            CallbackWrapper<PointObject>(ftType));
    }


private:
    static std::string screenLabel(const PointObject* pt, ObjectsCache&)
    {
        return pt->attributes().value(ATTR_ROAD_MARKING_POINT_TEXT_TEXT);
    }
};

class ROAD_SURFACE : public ServiceAttributesRegistry::Registrar
{
public:
    ROAD_SURFACE(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_ROAD_SURFACE)
    {
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ArealObject>(typeWithZlevels));
        registerAttr(SRV_FT_TYPE_ID,
            CallbackWrapper<ArealObject>(ftType));
    }
private:
    static std::string typeWithZlevels(const ArealObject* surface, ObjectsCache&)
    {
        const auto& attrs = surface->attributes();
        return
            valueLabel(ATTR_ROAD_SURFACE_TYPE, attrs.value(ATTR_ROAD_SURFACE_TYPE)) + " (" +
            attrs.value(ATTR_ROAD_SURFACE_F_ZLEV) + ", " +
            attrs.value(ATTR_ROAD_SURFACE_T_ZLEV) + ")";
    }
};

HDMapServiceAttributes::HDMapServiceAttributes(ServiceAttributesRegistry &registry)
{
    ROAD_MARKING_POLYGONAL polygonal(registry);
    ROAD_MARKING_LINEAR linear(registry);
    ROAD_SURFACE surface(registry);
    ROAD_MARKING_POINT_LANE_DIRECTION laneDirection(registry);
    ROAD_MARKING_POINT_SYMBOL symbol(registry);
    ROAD_MARKING_POINT_ROAD_SIGN sign(registry);
    ROAD_MARKING_POINT_ROAD_SIGN_SPEED_LIMIT speedLimit(registry);
    ROAD_MARKING_POINT_TEXT text(registry);
}

} // namespace maps::wiki::srv_attrs
