#include "geom_index.h"
#include "db_access.h"
#include "magic_strings.h"

#include <yandex/maps/wiki/common/retry_duration.h>
#include <yandex/maps/wiki/revision/filters.h>
#include <maps/libs/common/include/exception.h>

namespace maps {
namespace wiki {
namespace diffalert {

GeomIndex::GeomIndex(const std::vector<EnvelopeWithObjectId>& envelopeAndObjectIdPairs)
{
     boundingBoxes_.reserve(envelopeAndObjectIdPairs.size());

    for (const auto& envelopeAndObjectId: envelopeAndObjectIdPairs) {
        const Envelope& envelope = envelopeAndObjectId.envelope;
        boundingBoxes_.emplace_back(
            geolib3::Point2{envelope.getMinX(), envelope.getMinY()},
            geolib3::Point2{envelope.getMaxX(), envelope.getMaxY()}
        );
        searcher_.insert(& boundingBoxes_.back(), envelopeAndObjectId.objectId);
    }

    searcher_.build();
}

TIds GeomIndex::objectIdsByEnvelope(const Envelope& envelope) const
{
    const geolib3::BoundingBox query {
        {envelope.getMinX(), envelope.getMinY()},
        {envelope.getMaxX(), envelope.getMaxY()}
    };

    const auto result = searcher_.find(query);

    TIds ids;
    for (auto it = result.first; it != result.second; ++it) {
        ids.insert(it->value());
    }

    return ids;
}

GeomIndexPtr makeBldWithModel3dGeomIndex(RevSnapshotFactory revSnapshotFct)
{
    namespace rf = revision::filters;

    const auto filter = rf::Attr(cat::MODEL3D).defined()
            && rf::ObjRevAttr::isNotDeleted()
            && rf::ObjRevAttr::isNotRelation()
            && !rf::Geom::defined();

    auto model3dRevisions = common::retryDuration([&] {
        auto snapshot = revSnapshotFct.get();
        return snapshot->revisionIdsByFilter(filter);
    });

    std::vector<TId> model3dIds;
    for (const auto& id: model3dRevisions) {
         model3dIds.push_back(id.objectId());
    }

    auto relations = common::retryDuration([&] {
        auto snapshot = revSnapshotFct.get();
        return snapshot->loadSlaveRelations(model3dIds);
    });

    std::vector<TId> bldIds;
    for (const auto& relation: relations) {
        const auto& data = relation.data();
        REQUIRE(
            data.relationData,
            relation.id() << " is not relation!"
        );
        REQUIRE(
            data.attributes && data.attributes->count(rel::ROLE),
            relation.id() << " has no attributre 'rel:role'!"
        );
        if (data.attributes->at(rel::ROLE) == ASSOCIATED_WITH_ROLE) {
            bldIds.push_back(data.relationData->slaveObjectId());
        }
    }

    auto bldRevisions = common::retryDuration([&] {
        auto snapshot = revSnapshotFct.get();
        return snapshot->objectRevisions(bldIds);
    });

    std::vector<EnvelopeWithObjectId> envelopeAndObjectIdPairs;
    for (const auto& [objectId, bld]: bldRevisions) {
        REQUIRE(
            bld.data().geometry,
            "Bld object " << bld.id() << " has no geometry!"
        );
        const Geom geom(*bld.data().geometry);
        ASSERT(geom->getEnvelopeInternal());
        envelopeAndObjectIdPairs.emplace_back(*geom->getEnvelopeInternal(), objectId);
    }

    return std::make_shared<GeomIndex>(envelopeAndObjectIdPairs);
}

} // namespace diffalert
} // namespace wiki
} // namespace maps
