#include "railway.h"
#include "calc.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/objects/attr_object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/areal_object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/junction.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/linear_element.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/point_object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/complex_object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/relation_object.h>
#include <maps/wikimap/mapspro/services/editor/src/edit_options.h>


namespace maps
{
namespace wiki
{
namespace srv_attrs
{
namespace
{
const std::string TRANSPORT_RAILWAY_NM = "transport_railway_nm";

const StringVec RAILWAY_FT_TYPES = {"611", "612", "613", "614", "615", "616", "620"};
}

typedef std::multimap<std::string, TOid> LevelKindToId;

class RAILWAY_JC : public ServiceAttributesRegistry::Registrar
{
public:
    RAILWAY_JC(ServiceAttributesRegistry& registry):ServiceAttributesRegistry::Registrar(registry, CATEGORY_TRANSPORT_RAILWAY_JC)
    {
        registerAttr(
            CATEGORY_TRANSPORT_RAILWAY_JC + SUFFIX_CAN_DELETE,
            CallbackWrapper<Junction>(canDelete))
            .depends(
        {
            {Aspect::Type::Attribute, ""},
            {{RelationType::Master, ROLE_START, CATEGORY_TRANSPORT_RAILWAY_EL}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, ""},
            {{RelationType::Master, ROLE_END, CATEGORY_TRANSPORT_RAILWAY_EL}}
        });
        registerAttr(SRV_VALENCY, CallbackWrapper<Junction>(valency))
            .depends(
        {
            {Aspect::Type::Relations, STR_TO_ATTR_OWNER},
            {{RelationType::Master, ROLE_START, CATEGORY_TRANSPORT_RAILWAY_EL}}
        })
            .depends(
        {
            {Aspect::Type::Relations, STR_TO_ATTR_OWNER},
            {{RelationType::Master, ROLE_END, CATEGORY_TRANSPORT_RAILWAY_EL}}
        });
    }

private:
    static std::string canDelete(const Junction* jc, ObjectsCache&)
    {
        return maps::wiki::canDelete(jc, 0)
            ? SRV_ATTR_TRUE
            : s_emptyString;
    }

    static std::string valency(const Junction* jc, ObjectsCache&)
    {
        return boost::lexical_cast<std::string>(jc->valency());
    }
};

class RAILWAY_EL : public ServiceAttributesRegistry::Registrar
{
public:
    RAILWAY_EL(ServiceAttributesRegistry& registry):ServiceAttributesRegistry::Registrar(registry, CATEGORY_TRANSPORT_RAILWAY_EL)
    {
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<LinearElement, std::string>(uiLabel, NAME_TYPE_OFFICIAL))
            .depends(
        {
            {Aspect::Type::Attribute, TRANSPORT_RAILWAY_NM},
            {{RelationType::Master, ROLE_PART, CATEGORY_TRANSPORT_RAILWAY}}
        });
        registerAttr(SRV_RENDER_LABEL, CallbackWrapper<LinearElement, std::string>(uiLabel, NAME_TYPE_RENDER_LABEL))
            .depends(
        {
            {Aspect::Type::Attribute, TRANSPORT_RAILWAY_NM},
            {{RelationType::Master, ROLE_PART, CATEGORY_TRANSPORT_RAILWAY}}
        });
        //inherited ft_type_id
        for (const std::string& ftType : RAILWAY_FT_TYPES) {
            registerAttr("srv:ft_type_id_" + ftType, CallbackWrapper<LinearElement, std::string>(hasParentWithFtType, ftType))
                .depends(
            {
                {Aspect::Type::Attribute, "transport_railway:ft_type_id"},
                {{RelationType::Master, ROLE_PART, CATEGORY_TRANSPORT_RAILWAY}}
            });
        }
    }

private:
    static std::string uiLabel(const LinearElement* el, ObjectsCache& cache, const std::string& nameType)
    {
        const auto& railwayLines = el->masterRelations().range(ROLE_PART);
        for (const auto& railwayLine : railwayLines) {
            std::string lineName = objectNameByType(railwayLine.id(),
                    nameType,
                    cache);
            if (!lineName.empty() ) {
                return lineName;
            }
        }
        return s_emptyString;
    }

    static std::string hasParentWithFtType(const LinearElement* el, ObjectsCache&, const std::string& ftType)
    {
        const auto& railwayLines = el->masterRelations().range(ROLE_PART);
        for (const auto& railwayLine : railwayLines) {
            if (railwayLine.relative()->attributes().value("transport_railway:ft_type_id")
                == ftType) {
                    return SRV_ATTR_TRUE;
            }
        }
        return s_emptyString;
    }
};

class RAILWAY_STATION : public ServiceAttributesRegistry::Registrar
{
public:
    RAILWAY_STATION(ServiceAttributesRegistry& registry):ServiceAttributesRegistry::Registrar(registry, CATEGORY_TRANSPORT_RAILWAY_STATION)
    {
        registerSuggestCallback(SuggestCallbackWrapper<PointObject>(GenericNamedObject::suggestTexts));
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<PointObject>(screenLabel));
        registerAttr(SRV_RENDER_LABEL, CallbackWrapper<PointObject>(renderLabel));
        registerAttr(SRV_HOTSPOT_LABEL, CallbackWrapper<PointObject>(hotspotLabel))
            .depends(
        {
            {Aspect::Type::Attribute, TRANSPORT_RAILWAY_NM},
            {{RelationType::Master, ROLE_ASSIGNED, CATEGORY_TRANSPORT_RAILWAY}}
        });
    }

private:
    static std::string screenLabel(const PointObject* pt, ObjectsCache& cache)
    {
        return objectNameByType(pt->id(),
                NAME_TYPE_OFFICIAL,
                cache);
    }

    static std::string hotspotLabel(const PointObject* pt, ObjectsCache& cache)
    {
        std::string stationLabel = objectNameByType(
                            pt->id(),
                            NAME_TYPE_OFFICIAL,
                            cache);
        const auto& railwayLines = pt->masterRelations().range(ROLE_ASSIGNED);
        for (const auto& railwayLine : railwayLines) {
            std::string railwayLineName = objectNameByType(
                    railwayLine.id(),
                    NAME_TYPE_OFFICIAL,
                    cache);
            if (!railwayLineName.empty()) {
                return stationLabel + " (" + railwayLineName + ")";
            }
        }
        return stationLabel;
    }

    static std::string renderLabel(const PointObject* pt, ObjectsCache& cache)
    {
        return objectNameByType(
                pt->id(),
                NAME_TYPE_RENDER_LABEL,
                cache);
    }
};

class RAILWAY_PLATFORM : public ServiceAttributesRegistry::Registrar
{
public:
    RAILWAY_PLATFORM(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_TRANSPORT_RAILWAY_PLATFORM)
    {
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ArealObject>(screenLabel));
        registerAttr(SRV_RENDER_LABEL, CallbackWrapper<ArealObject>(renderLabel));
        registerAttr(SRV_HOTSPOT_LABEL, CallbackWrapper<ArealObject>(hotspotLabel))
            .depends(
        {
            {Aspect::Type::Attribute, TRANSPORT_RAILWAY_NM},
            {{RelationType::Master, ROLE_ASSIGNED, CATEGORY_TRANSPORT_RAILWAY_STATION}}
        });
    }

private:
    static std::string screenLabel(const ArealObject* pt, ObjectsCache& cache)
    {
        return objectNameByType(pt->id(),
                NAME_TYPE_OFFICIAL,
                cache);
    }

    static std::string hotspotLabel(const ArealObject* pt, ObjectsCache& cache)
    {
        std::string platformLabel = objectNameByType(
                            pt->id(),
                            NAME_TYPE_OFFICIAL,
                            cache);
        const auto& railwayStations = pt->masterRelations().range(ROLE_ASSIGNED);
        for (const auto& railwayStation : railwayStations) {
            std::string stationName = objectNameByType(
                    railwayStation.id(),
                    NAME_TYPE_OFFICIAL,
                    cache);
            if (!stationName.empty()) {
                return platformLabel + " (" + stationName + ")";
            }
        }
        return platformLabel;
    }

    static std::string renderLabel(const ArealObject* pt, ObjectsCache& cache)
    {
        return objectNameByType(
                pt->id(),
                NAME_TYPE_RENDER_LABEL,
                cache);
    }
};

class RAILWAY_LINE : public ServiceAttributesRegistry::Registrar
{
public:
    RAILWAY_LINE(ServiceAttributesRegistry& registry):ServiceAttributesRegistry::Registrar(registry, CATEGORY_TRANSPORT_RAILWAY)
    {
        registerSuggestCallback(SuggestCallbackWrapper<ComplexObject>(GenericNamedObject::suggestTexts));
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ComplexObject>(screenLabel));
    }

private:
    static std::string screenLabel(const ComplexObject* obj, ObjectsCache& cache)
    {
        return objectNameByType(
                obj->id(),
                NAME_TYPE_OFFICIAL,
                cache);
    }
};

RAILWAYServiceAttributes::RAILWAYServiceAttributes(ServiceAttributesRegistry& registry)
{
    RAILWAY_STATION station(registry);
    RAILWAY_PLATFORM platform(registry);
    RAILWAY_LINE line(registry);
    RAILWAY_EL el(registry);
    RAILWAY_JC jc(registry);
}

}//srv_attrs
}//wiki
}//maps
