#include "module.h"
#include <yandex/maps/wiki/validator/check.h>
#include <yandex/maps/wiki/validator/categories.h>

#include "../utils/names.h"
#include "../utils/misc.h"

#include <unordered_map>

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

using categories::AD;
using categories::ADDR;
using categories::ADDR_NM;
using categories::RD;

VALIDATOR_SIMPLE_CHECK( addr_uniqueness, ADDR, ADDR_NM, AD, RD )
{
    std::unordered_map<TId, std::unordered_map<NameDatum, TId>> addrNames;

    auto viewAd = context->objects<AD>();
    auto viewRd = context->objects<RD>();

    context->objects<ADDR>().visit([&](const AddressPoint* addressPoint)
    {
        if (addressPoint->associatedObject().type ==
            AddressPoint::AssociatedObject::Type::NotSet)
        {
            return;
        }

        TId addrSystemId = addressPoint->associatedObject().id;
        if (addressPoint->associatedObject().type ==
            AddressPoint::AssociatedObject::Type::AdmUnit &&
            viewAd.loaded(addressPoint->associatedObject().id))
        {
            auto ad = viewAd.byId(addressPoint->associatedObject().id);
            if (ad->addrUnity()) {
                addrSystemId = ad->addrUnity();
            }
        } else if (addressPoint->associatedObject().type ==
            AddressPoint::AssociatedObject::Type::Road &&
            viewRd.loaded(addressPoint->associatedObject().id))
        {
            auto rd = viewRd.byId(addressPoint->associatedObject().id);
            if (rd->addrUnity()) {
                addrSystemId = rd->addrUnity();
            } else if (rd->associatedObject().type == Road::AssociatedObject::Type::Feature) {
                addrSystemId = rd->associatedObject().id;
            } else if (rd->associatedObject().type == Road::AssociatedObject::Type::AdmUnit &&
                viewAd.loaded(rd->associatedObject().id))
            {
                auto ad = viewAd.byId(rd->associatedObject().id);
                if (ad->isAddrUnity()) {
                    addrSystemId = ad->id();
                } else if (ad->addrUnity()) {
                    addrSystemId = ad->addrUnity();
                }
            }
        }

        for (NameDatum& officialNameDatum
                : utils::officialNames<ADDR_NM>(addressPoint, context)) {
            if (!utils::isNameEmpty(officialNameDatum.name)) {
                auto insertionResult =
                    addrNames[addrSystemId].emplace(
                        std::move(officialNameDatum),
                        addressPoint->id());
                if (!insertionResult.second) {
                    context->error(
                        "duplicate-address-point-names",
                        addressPoint->geom(),
                        {
                            addrSystemId,
                            addressPoint->id(),
                            insertionResult.first->second
                        });
                }
            }
        }
    });
}

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