#pragma once

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

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

namespace detail {

template<typename Category>
void checkChildrenLoaded(CheckContext* context, const LinearFeature*)
{
    context->checkLoaded<typename ElementCategory<Category>::type>();
}

template<typename Category>
void checkChildrenLoaded(CheckContext* context, const TransportLine*)
{
    context->checkLoaded<typename ElementCategory<Category>::type>();
}

template<typename Category>
void checkChildrenLoaded(CheckContext* context, const ContourFeature*)
{
    context->checkLoaded<typename FaceCategory<Category>::type>();
}

template<typename Category>
void checkChildrenLoaded(CheckContext* context, const ContourLinearFeature*)
{
    context->checkLoaded<typename ElementCategory<Category>::type>();
    context->checkLoaded<typename FaceCategory<Category>::type>();
}

template<typename Category>
void checkObject(CheckContext* context, const LinearFeature* feature)
{
    if (feature->elements().empty()) {
        context->fatal("empty-feature", boost::none, {feature->id()});
    }
}

template<typename Category>
void checkObject(CheckContext* context, const TransportLine* feature)
{
    if (feature->elements().empty()) {
        context->fatal("empty-feature", boost::none, {feature->id()});
    }
}

template<typename Category>
void checkObject(CheckContext* context, const ContourFeature* feature)
{
    if (feature->faces().empty()) {
        context->fatal("empty-feature", boost::none, {feature->id()});
    }
}

template<typename Category>
void checkObject(CheckContext* context, const ContourLinearFeature* feature)
{
    if (feature->faces().empty() && feature->elements().empty()) {
        context->fatal("empty-feature", boost::none, {feature->id()});
    }
}

} // namespace detail

template<typename ElementCategory, typename ParentCategory>
void runElementsRelationsCheck(CheckContext* context, Severity strayElementSeverity)
{
    context->checkLoaded<ElementCategory>();
    context->checkLoaded<ParentCategory>();

    context->objects<ElementCategory>().visit(
        [&](const typename ElementCategory::TObject* element) {
            if (element->parents().empty()) {
                context->report(
                        strayElementSeverity,
                        "stray-element", geomForReport(element), {element->id()});
            }
        }
    );
}

template<typename ElementCategory, typename LineCategory>
void runTransportElementsRelationsCheck(CheckContext* context, Severity strayElementSeverity)
{
    context->checkLoaded<ElementCategory>();
    context->checkLoaded<LineCategory>();

    context->objects<ElementCategory>().visit(
        [&](const typename ElementCategory::TObject* element) {
            if (element->lines().empty()) {
                context->report(
                        strayElementSeverity,
                        "stray-element", geomForReport(element), {element->id()});
            }
        }
    );
}

template<typename FaceCategory, typename ParentCategory>
void runFacesRelationsCheck(CheckContext* context)
{
    context->checkLoaded<ParentCategory>();
    context->checkLoaded<FaceCategory>();

    context->objects<FaceCategory>().visit(
        [&](const Face* face) {
            if (!face->parent()) {
                context->fatal("stray-face", boost::none, {face->id()});
            }
        }
    );

    typedef typename ElementCategory<FaceCategory>::type ElementCategory;
    context->checkLoaded<ElementCategory>();

    context->objects<FaceCategory>().visit(
        [&](const Face* face) {
            if (face->edges().empty()) {
                context->fatal("empty-face", boost::none, {face->id()});
            }
        }
    );
}

template<typename Category>
void runFeatureRelationsCheck(CheckContext* context)
{
    context->checkLoaded<Category>();

    typedef typename Category::TObject TObject;
    detail::checkChildrenLoaded<Category>(context, static_cast<const TObject*>(nullptr));
    context->objects<Category>().visit([&](const TObject* feature) {
        detail::checkObject<Category>(context, feature);
    });
}

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