#include "graph.h"

#include <maps/libs/log8/include/log8.h>
#include <maps/wikimap/mapspro/services/mrc/libs/fb/include/version.h>

namespace maps::mrc::browser {

Graph::Graph(const std::string& graphFolder,
             EMappingMode mappingMode)
    : graph_(graphFolder + "/road_graph.fb", mappingMode)
    , rtree_(graphFolder + "/rtree.fb", graph_, mappingMode)
    , persistentIndex_(graphFolder + "/edges_persistent_index.fb", mappingMode)
    , coverage_(graphFolder + "/graph_coverage.fb", mappingMode)
    , coverageRtreeReader_(graph_, graphFolder, mappingMode)
{
    if (!coverage_.mrcVersion().empty()) {
        generatedAt_ = fb::parseVersion(std::string{coverage_.mrcVersion()});
    }
}

std::vector<fb::CoveredSubPolyline>
Graph::getCoveredSubpolylinesForEdgeSegments(
    road_graph::EdgeId edgeId,
    const std::vector<road_graph::SegmentIndex>& sortedSegments,
    db::CameraDeviation cameraDeviation,
    db::FeaturePrivacy privacy) const
{
    std::vector<fb::CoveredSubPolyline> result;
    if (sortedSegments.empty()) {
        return result;
    }
    auto edge = coverage_.edgeById(edgeId.value());
    if (!edge) {
        return result;
    }

    for (auto& coverage : edge->coverages) {
        if (coverage.cameraDeviation != cameraDeviation ||
            coverage.privacy != privacy) {
            continue;
        }
        auto segmentIt = sortedSegments.begin();
        for (auto& coveredSubpolyline : coverage.coveredSubpolylines) {
            while (segmentIt->value() <
                       coveredSubpolyline.begin().segmentIdx() &&
                   segmentIt != sortedSegments.end()) {
                ++segmentIt;
            }
            if (segmentIt == sortedSegments.end()) {
                break;
            }
            if (coveredSubpolyline.begin().segmentIdx() <= segmentIt->value() &&
                segmentIt->value() <= coveredSubpolyline.end().segmentIdx()) {
                result.push_back(coveredSubpolyline);
            }
        }
    }
    return result;
}

std::vector<road_graph::EdgeId> Graph::getCoveredEdgeIdsByBbox(
    const geolib3::BoundingBox& geoBbox,
    db::TFc maxFc) const
{
    auto range = coverageRtreeReader_.getCoveredEdgeIdsByBbox(geoBbox, maxFc);
    return {range.begin(), range.end()};
}

std::vector<road_graph::EdgeId> Graph::getNearestCoveredEdges(
    const geolib3::Point2& geoPos,
    size_t limit,
    db::TFc maxFc) const
{
    return coverageRtreeReader_.getNearestCoveredEdgeIds(geoPos, limit, maxFc);
}

db::TIds Graph::getFeatureIdsByBbox(const geolib3::BoundingBox& geoBbox) const
{
    db::TIdSet idSet;
    auto edgeIds =
        coverageRtreeReader_.getCoveredEdgeIdsByBbox(geoBbox, fb::FC_MAX);
    for (auto edgeId : edgeIds) {
        auto edge = coverage_.edgeById(edgeId.value());
        for (const fb::TEdgeCoverage& edgeCoverage : edge->coverages) {
            for (const fb::CoveredSubPolyline& coveredSubpolyline :
                 edgeCoverage.coveredSubpolylines) {
                idSet.insert(coveredSubpolyline.featureId());
            }
        }
    }
    return db::TIds(idSet.begin(), idSet.end());
}

db::TIds Graph::getFeatureIdsByEdgeId(road_graph::EdgeId edgeId) const
{
    db::TIdSet idSet;
    auto edge = coverage_.edgeById(edgeId.value());
    if (!edge) {
        return {};
    }
    for (auto& coverage : edge->coverages) {
        for (auto& coveredSubpolyline : coverage.coveredSubpolylines) {
            idSet.insert(coveredSubpolyline.featureId());
        }
    }
    return db::TIds(idSet.begin(), idSet.end());
}

std::optional<fb::TEdge> Graph::getCoveredEdgeById(
    road_graph::EdgeId edgeId) const
{
    return coverage_.edgeById(edgeId.value());
}

std::size_t Graph::getEdgeSegmentsNum(road_graph::EdgeId edgeId) const
{
    return graph().edgeData(edgeId).geometry().segmentsNumber();
}

PhotoToEdge::PhotoToEdge(const std::string& photoToEdgeFolder,
                         EMappingMode mappingMode)
    : reader_(photoToEdgeFolder + "/photo_to_edge.fb", mappingMode)
    , generatedAt_(fb::parseVersion(std::string{reader_.mrcVersion()}))
{
}

std::vector<fb::PhotoToEdgePair> PhotoToEdge::lookupByFeatureId(
    db::TId featureId) const
{
    return reader_.lookupByFeatureId(featureId);
}

}  // namespace maps::mrc::browser
