#pragma once

#ifndef VALIDATOR_CHECKS_UTILS_OBJECT_ELEMENTS_WITHIN_AOI_INL
#error "direct inclusion of object_elements_within_aoi-inl.h " \
    "is not allowed, please include object_elements_within_aoi.h instead"
#endif

#include "category_traits.h"
#include <yandex/maps/wiki/validator/categories.h>
#include <yandex/maps/wiki/validator/check_context.h>

#include <algorithm>

namespace maps {
namespace wiki {
namespace validator {
namespace utils {

namespace detail {

template<class Category>
inline bool allLoaded(
        CheckContext* context,
        const std::vector<TId>& objectIds)
{
    auto viewObjects = context->objects<Category>();

    return std::all_of(std::begin(objectIds), std::end(objectIds),
        [&](TId id) { return viewObjects.loaded(id); });
}

template<class Category>
inline bool allObjectElementsWithinAoi(
        CheckContext* context,
        const std::vector<TId>& objectIds)
{
    auto viewObjects = context->objects<Category>();

    return std::all_of(std::begin(objectIds), std::end(objectIds),
        [&](TId id)
        { return objectElementsWithinAoi<Category>(
              context,
              viewObjects.byId(id)); });
}

} // namespace detail

inline bool objectElementsWithinAoi(
        CheckContext* context,
        const BuildingComplex* buildingComplex)
{ return detail::allLoaded<categories::BLD>(context, buildingComplex->elements()); }

template<>
inline bool objectElementsWithinAoi<categories::RD>(
        CheckContext* context,
        const Road* road)
{ return detail::allLoaded<categories::RD_EL>(context, road->elements()); }

template<class FaceCategory>
inline bool objectElementsWithinAoi(CheckContext* context, const Face* face)
{
    typedef typename ElementCategory<FaceCategory>::type ElementCategory;
    return detail::allLoaded<ElementCategory>(context, face->edges());
}

template<>
inline bool objectElementsWithinAoi<categories::TRANSPORT_METRO_LINE>(
        CheckContext* context,
        const TransportLine* metroLine)
{
    return detail::allLoaded<categories::TRANSPORT_METRO_EL>(
            context, metroLine->elements())
        && detail::allLoaded<categories::TRANSPORT_METRO_STATION>(
            context, metroLine->stations());
}

template<class FeatureCategory>
inline bool objectElementsWithinAoi(
        CheckContext* context,
        const ContourLinearFeature* feature)
{
    typedef typename ElementCategory<FeatureCategory>::type ElementCategory;
    return detail::allLoaded<ElementCategory>(context, feature->elements());
}


template<>
inline bool objectFacesWithinAoi<categories::AD>(
        CheckContext* context,
        const AdmUnit* admUnit)
{
    return detail::allLoaded<categories::AD_FC>(context, admUnit->faces())
        && detail::allObjectElementsWithinAoi<categories::AD_FC>(
            context, admUnit->faces());
}

template<>
inline bool objectFacesWithinAoi<categories::AD_SUBST>(
        CheckContext* context,
        const AdmUnitSubst* admUnitSubst)
{
    return detail::allLoaded<categories::AD_SUBST_FC>(context, admUnitSubst->faces())
        && detail::allObjectElementsWithinAoi<categories::AD_SUBST_FC>(
            context, admUnitSubst->faces());
}

template<class FeatureCategory>
inline bool objectFacesWithinAoi(
        CheckContext* context,
        const ContourFeature* feature)
{
    typedef typename FaceCategory<FeatureCategory>::type FaceCategory;
    return detail::allLoaded<FaceCategory>(context, feature->faces())
        && detail::allObjectElementsWithinAoi<FaceCategory>(
            context, feature->faces());
}

template<class FeatureCategory>
inline bool objectFacesWithinAoi(
        CheckContext* context,
        const ContourLinearFeature* feature)
{
    typedef typename FaceCategory<FeatureCategory>::type FaceCategory;
    return detail::allLoaded<FaceCategory>(context, feature->faces())
        && detail::allObjectElementsWithinAoi<FaceCategory>(
            context, feature->faces());
}

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