#include "compact_bbox.h"
#include "rtree.h"
#include "rtree_view.h"

#include <maps/libs/common/include/exception.h>

#include <util/generic/xrange.h>

namespace maps::mrc::fb_rtree {

namespace {

const fb::Rtree* getRtreePtr(const void* data, size_t len)
{
    auto verifier =
        flatbuffers64::Verifier(reinterpret_cast<const uint8_t*>(data), len);
    REQUIRE(fb::VerifyRtreeBuffer(verifier), "invalid Rtree");
    return fb::GetRtree(data);
}

}  // namespace

Rtree::Rtree(TBlob&& blob)
    : blob_(std::move(blob))
    , ptr_(getRtreePtr(blob_.Data(), blob_.Length()))
    , branching_(ptr_->branching())
    , nodes_(ptr_->nodes())
    , levelBegin_(map(ptr_->levelBegin()))
    , ids_(map(ptr_->ids()))
{
}

Rtree::Rtree(TBuffer buffer) : Rtree(TBlob::FromBuffer(buffer)) {}

Rtree::Rtree(std::string_view filePath, EMappingMode mappingMode)
    : Rtree(common::blobFromFile(filePath, mappingMode))
{
}

Node Rtree::root() const
{
    return {geolib3::BoundingBox{{ptr_->bbox()->minX(), ptr_->bbox()->minY()},
                                 {ptr_->bbox()->maxX(), ptr_->bbox()->maxY()}},
            0,
            0,
            impl::RtreeView{branching_,
                            {&levelBegin_, 0, levelBegin_.size()},
                            nodes_->size() ? nodes_->Get(0) : nullptr},
            std::nullopt};
}

std::string_view Rtree::version() const
{
    return {ptr_->version()->c_str(), ptr_->version()->size()};
}

void Rtree::forEachDirectChild(const Node& node,
                               std::function<void(const Node&)> callback) const
{
    if (!node.rtree) {
        return;
    }
    const bool isLeaf = node.level + 1 == levelBegin_.size();
    node.rtree->forEachDirectChild(node, [&](Node child) {
        if (isLeaf) {
            child.id = ids_[child.index];
            child.rtree.reset();
        }
        callback(child);
    });
}

Ids nearestIds(const Rtree& rtree,
               const geolib3::Point2& point,
               DistanceToBox distanceToBox,
               DistanceToId distanceToId,
               size_t limit)
{
    auto result = Ids{};
    result.reserve(limit);
    auto rng = rtree.nearestIds(point, distanceToBox, distanceToId);
    for (auto i = rng.begin(); i != rng.end() && result.size() < limit; ++i) {
        result.push_back(*i);
    }
    return result;
}

}  // namespace maps::mrc::fb_rtree
