#include "db.h"
#include "utility.h"

#include <maps/libs/geolib/include/prepared_polygon.h>
#include <maps/libs/geolib/include/spatial_relation.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/object_in_photo_gateway.h>

#include <mutex>

namespace maps::mrc::blur_mds {
namespace {

constexpr auto ThreadsNumber = 4;
constexpr auto BatchSize = 10'000;

ObjectsByFeatureId groupByFeatureId(db::ObjectsInPhoto&& objects)
{
    auto result = ObjectsByFeatureId{};
    for (auto&& object : objects) {
        auto featureId = object.featureId();
        result[featureId].push_back(std::move(object));
    }
    INFO() << concat("features with objects ", result.size());
    return result;
}

}  // namespace

db::Features loadFeatures(pgpool3::Pool& pool,
                          const geolib3::MultiPolygon2& mercatorGeom)
{
    auto result = db::Features{};
    auto featureIds = db::FeatureGateway(*pool.slaveTransaction())
                          .loadIds(db::table::Feature::pos.intersects(
                              mercatorGeom.boundingBox()));
    INFO() << concat("loaded ", featureIds.size(), " feature ids");
    result.reserve(featureIds.size());
    auto preparedGeom = geolib3::PreparedPolygon2(mercatorGeom);
    auto guard = std::mutex{};
    parallelForEachBatch<ThreadsNumber, BatchSize>(featureIds, [&](auto batch) {
        auto features = db::FeatureGateway(*pool.slaveTransaction())
                            .loadByIds({batch.begin(), batch.end()});
        std::erase_if(features, [&](const auto& feature) {
            return !geolib3::spatialRelation(
                preparedGeom,
                feature.mercatorPos(),
                geolib3::SpatialRelation::Intersects);
        });
        auto lock = std::lock_guard{guard};
        auto prev = result.size();
        std::move(features.begin(), features.end(), std::back_inserter(result));
        if (prev < ceil(result.size(), 100 * BatchSize)) {
            INFO() << concat("loaded ", result.size(), " features");
        }
    });
    INFO() << concat("loaded ", result.size(), " features");
    return result;
}

ObjectsByFeatureId loadObjectsInPhoto(pgpool3::Pool& pool,
                                      const db::TIds& featureIds)
{
    auto result = db::ObjectsInPhoto{};
    auto guard = std::mutex{};
    parallelForEachBatch<ThreadsNumber, BatchSize>(featureIds, [&](auto batch) {
        auto objects = db::ObjectInPhotoGateway(*pool.slaveTransaction())
                           .load(db::table::ObjectInPhotoTable::featureId.in(
                               {batch.begin(), batch.end()}));
        auto lock = std::lock_guard{guard};
        auto prev = result.size();
        std::move(objects.begin(), objects.end(), std::back_inserter(result));
        if (prev < ceil(result.size(), 100 * BatchSize)) {
            INFO() << concat("loaded ", result.size(), " objects");
        }
    });
    INFO() << concat("loaded ", result.size(), " objects");
    return groupByFeatureId(std::move(result));
}

}  // namespace maps::mrc::blur_mds
