#include "module.h"
#include "indoor_common.h"

#include "../utils/names.h"
#include <maps/libs/geolib/include/spatial_relation.h>

namespace maps::wiki::validator::checks {

using categories::INDOOR_NM;
using categories::INDOOR_LEVEL;
using categories::INDOOR_PLAN;

std::set<std::string>
getLevelNames(const IndoorLevel* level, CheckContext* context)
{
    std::set<std::string> result;
    auto nameData = utils::officialNames<INDOOR_NM>(level, context);
    for (const auto& nameDatum : nameData) {
        result.insert(nameDatum.name);
    }
    return result;
}

bool areIndoorLevelsCompatible(
    const IndoorLevel* level1,
    const IndoorLevel* level2,
    const std::set<std::string>& level1Names,
    const std::set<std::string>& level2Names)
{
    return
        level1->isUnderground() == level2->isUnderground() &&
        level1->order() == level2->order() &&
        level1->universal() == level2->universal() &&
        level1Names == level2Names &&
        !geolib3::spatialRelation(level1->geom(), level2->geom(), geolib3::Intersects);
}

std::vector<TId>
findIncompatibleLevels(
    const std::vector<const IndoorLevel*>& indoorLevels,
    const std::map<TId, std::set<std::string>>& levelNamesById)
{
    if (indoorLevels.size() == 1) {
        return {};
    }

    auto containsIncompatibleLevels = false;
    const auto startLevel = indoorLevels[0];
    for (size_t i = 1; i < indoorLevels.size(); ++i) {
        if (!areIndoorLevelsCompatible(
                startLevel,
                indoorLevels[i],
                levelNamesById.at(startLevel->id()),
                levelNamesById.at(indoorLevels[i]->id())))
        {
            containsIncompatibleLevels = true;
            break;
        }
    }

    std::vector<TId> result;
    if (containsIncompatibleLevels) {
        for (const auto* indoorLevel : indoorLevels) {
            result.push_back(indoorLevel->id());
        }
    }
    return result;
}

VALIDATOR_SIMPLE_CHECK(indoor_plan_uniqueness, INDOOR_PLAN, INDOOR_LEVEL, INDOOR_NM)
{
    auto indoorLevelsByPlan = getIndoorLevelsByPlan(context);

    for (const auto& [indoorPlan, indoorLevels] : indoorLevelsByPlan) {
        std::vector<const IndoorLevel*> defaultLevels;
        std::map<std::string, std::vector<const IndoorLevel*>> indoorLevelsByUniversal;
        for (const auto* indoorLevel : indoorLevels) {
            if (indoorLevel->isDefault()) {
                defaultLevels.push_back(indoorLevel);
            }
            indoorLevelsByUniversal[indoorLevel->universal()].push_back(indoorLevel);
        }

        std::map<TId, std::set<std::string>> levelNamesById;
        for (const auto& indoorLevel : indoorLevels) {
            levelNamesById[indoorLevel->id()] = getLevelNames(indoorLevel, context);
        }

        if (defaultLevels.empty()) {
            context->fatal(
                "indoor-plan-no-default-level",
                indoorPlan->geom(),
                {indoorPlan->id()});
        } else {
            auto reportIds = findIncompatibleLevels(defaultLevels, levelNamesById);
            if (!reportIds.empty()) {
                reportIds.push_back(indoorPlan->id());
                context->fatal(
                    "indoor-plan-multiple-default-levels",
                    indoorPlan->geom(),
                    reportIds);
            }
        }

        for (const auto& [_, indoorLevels] : indoorLevelsByUniversal) {
            auto reportIds = findIncompatibleLevels(indoorLevels, levelNamesById);
            if (!reportIds.empty()) {
                reportIds.push_back(indoorPlan->id());
                context->fatal(
                    "indoor-plan-multiple-similar-levels",
                    indoorPlan->geom(),
                    reportIds);
            }
        }
    }
}

VALIDATOR_SIMPLE_CHECK(indoor_plan_parent_uniqueness, INDOOR_PLAN)
{
    std::unordered_map<TId, std::vector<TId>> poiToIndoorPlans;
    context->objects<INDOOR_PLAN>().visit(
        [&](const IndoorPlan* indoorPlan)
    {
        for (auto parentId : indoorPlan->parentIds()) {
            poiToIndoorPlans[parentId].push_back(indoorPlan->id());
        }
    });
    for (const auto& [parentId, indoorPlanIds] : poiToIndoorPlans) {
        if (indoorPlanIds.size() > 1) {
            auto indoorPlan = context->objects<INDOOR_PLAN>().byId(indoorPlanIds.front());
            auto elementIds = indoorPlanIds;
            elementIds.push_back(parentId);

            context->fatal(
                "indoor-plan-share-main-poi",
                indoorPlan->geom(),
                elementIds);
        }
    }
}

} // namespace maps::wiki::validator::checks
