#include "mapspro_reader.h"

#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/geolib/include/serialization.h>
#include <maps/libs/geolib/include/conversion.h>
#include <yandex/maps/wiki/revision/revisionsgateway.h>
#include <pqxx/binarystring>
#include <pqxx/pqxx>

#include <optional>
#include <sstream>

namespace rev = maps::wiki::revision;

namespace maps::mrc::semgen {

namespace {

rev::Revisions
loadObjectsFromRevisionByBbox(pqxx::transaction_base& txn,
                              const std::string& geomCategory,
                              const geolib3::BoundingBox& bbox)
{
    rev::RevisionsGateway gtw(txn);
    auto snapshot = gtw.snapshot(gtw.headCommitId());
    auto objects = snapshot.objectRevisionsByFilter(
            rev::filters::Attr("cat:" + geomCategory).defined() &&
            rev::filters::Geom::intersects(bbox.minX(), bbox.minY(), bbox.maxX(), bbox.maxY())
        );
    return objects;
}

template<class Geometry>
std::list<Geometry>
loadGeometriesFromRevisionByBbox(pqxx::transaction_base& txn,
                                 const std::string& geomCategory,
                                 const geolib3::BoundingBox& bbox)
{
    INFO() << "Loading category: " << geomCategory;
    std::list<Geometry> result;
    auto objects = loadObjectsFromRevisionByBbox(txn, geomCategory, bbox);
    for (const auto& obj : objects) {
        if (obj.data().geometry) {
            std::stringstream ss(*obj.data().geometry);
            result.push_back(geolib3::WKB::read<Geometry>(ss));
        }
    }
    INFO() << "Loaded " << result.size() << " objects";
    return result;
}

} // namespace

std::list<Building> MapsproLoader::loadBuildings() const
{
    INFO() << "Loading category: bld";
    std::list<Building> result;
    auto objects = loadObjectsFromRevisionByBbox(txn_, "bld", bbox_);
    for (const auto& obj : objects) {
        const auto& data = obj.data();
        if (data.geometry) {
            std::stringstream ss(*data.geometry);
            geolib3::Polygon2 footprint = geolib3::WKB::read<geolib3::Polygon2>(ss);

            std::optional<double> height;
            if (data.attributes) {
                const rev::Attributes& attributes = *data.attributes;
                if (attributes.find("bld:height") != attributes.end()) {
                    height = std::stod(attributes.at("bld:height"));
                }
            }
            result.emplace_back(footprint, height);
        }
    }
    INFO() << "Loaded " << result.size() << " objects";
    return result;
}

std::list<Road> MapsproLoader::loadRoads() const
{
    return loadGeometriesFromRevisionByBbox<Road>(txn_, "rd_el", bbox_);
}

std::list<Vegetation> MapsproLoader::loadVegetation() const
{
    return loadGeometriesFromRevisionByBbox<Vegetation>(txn_, "vegetation", bbox_);
}

std::list<Hydro> MapsproLoader::loadHydro() const
{
    return loadGeometriesFromRevisionByBbox<Hydro>(txn_, "hydro", bbox_);
}

} // namespace maps::mrc::semgen
