#include "categories_list.h"
#include <yandex/maps/wiki/validator/has_geom.h>

#include <maps/libs/common/include/exception.h>
#include <unordered_map>

namespace maps {
namespace wiki {
namespace validator {

namespace {

template<typename TypeList> struct InitCategoryIds;

template<typename... Categories>
struct InitCategoryIds<TypeList<Categories...>>
{
    explicit InitCategoryIds(std::unordered_set<TCategoryId>& allCategoryIds)
    {
        allCategoryIds.insert({Categories::id()...});
    }
};

template<typename Geom> struct CalcGeomType;

template<>
struct CalcGeomType<geolib3::Point2>
{ static const GeomType value = GeomType::Point; };

template<>
struct CalcGeomType<geolib3::Polygon2>
{ static const GeomType value = GeomType::Polygon; };

template<>
struct CalcGeomType<geolib3::Polyline2>
{ static const GeomType value = GeomType::Polyline; };


template<class TObject, bool hasGeom = HasGeom<TObject>::value>
struct ObjectGeomType;

template<class TObject>
struct ObjectGeomType<TObject, false> { static const GeomType value = GeomType::None; };

template<class TObject>
const GeomType ObjectGeomType<TObject, false>::value;

template<class TObject>
struct ObjectGeomType<TObject, true>
{
    static const GeomType value = CalcGeomType<typename TObject::TGeom>::value;
};

template<class TObject>
const GeomType ObjectGeomType<TObject, true>::value;

template<typename Category>
std::pair<TCategoryId, GeomType>
categoryGeomTypePair()
{
    return {Category::id(), ObjectGeomType<typename Category::TObject>::value};
}

template<typename TypeList> struct InitCategoryGeomType;

template<typename... Categories>
struct InitCategoryGeomType<TypeList<Categories...>>
{
    explicit InitCategoryGeomType(
            std::unordered_map<TCategoryId, GeomType>& categoryIdToGeomType)
    {
        categoryIdToGeomType.insert({categoryGeomTypePair<Categories>()...});
    }
};

} // namespace

const std::unordered_set<TCategoryId>& allCategoryIds()
{
    static std::unordered_set<TCategoryId> s_allCategoryIds;
    static InitCategoryIds<CategoriesList> s_initter(s_allCategoryIds);

    return s_allCategoryIds;
}

bool isCategoryExisting(const TCategoryId& categoryId)
{
    return allCategoryIds().count(categoryId) > 0;
}

void checkCategoryExists(const TCategoryId& categoryId)
{
    REQUIRE(isCategoryExisting(categoryId),
            "category '" << categoryId << "' does not exist");
}

GeomType categoryGeomType(const TCategoryId& categoryId) {
    static std::unordered_map<TCategoryId, GeomType> s_categoryIdToGeomType;
    static InitCategoryGeomType<CategoriesList> s_initter(s_categoryIdToGeomType);

    auto it = s_categoryIdToGeomType.find(categoryId);
    REQUIRE(it != s_categoryIdToGeomType.end(),
            "category '" << categoryId << "' does not exist");
    return it->second;
}

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