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

#include <boost/format.hpp>

using namespace flatbuffers64;

namespace maps::mrc::fb {

namespace {

std::string makeMdsPath(const Feature& feature)
{
    auto result = std::string{decode(*feature.mdsPathPattern())};
    boost::replace_all(result, PARAM_MARKER, std::to_string(feature.id()));
    return result;
}

db::Feature getFeature(const Feature& feature)
{
    auto result =
        db::Feature(feature.id())
            .setTimestamp(fromMilliseconds(feature.time()))
            .setSourceId(feature.sourceId()
                             ? std::string(decode(*feature.sourceId()))
                             : db::feature::NO_SOURCE_ID)
            .setDataset(decode(feature.dataset()))
            .setMdsKey(
                {std::to_string(feature.mdsGroupId()), makeMdsPath(feature)})
            .setGeodeticPos(getPos(feature))
            .setHeading(geolib3::Heading(feature.heading()))
            .setSize({feature.width(), feature.height()})
            .setOrientation(
                common::ImageOrientation::fromExif(feature.orientation()))
            .setCameraDeviation(
                static_cast<db::CameraDeviation>(feature.cameraDeviation()))
            .setPrivacy(decode(feature.privacy()))
            .setGraph(decode(feature.graph()))
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true);
    if (feature.userId()) {
        result.setUserId(std::to_string(feature.userId()))
            .setShowAuthorship(true);
    }
    if (feature.walkObjectId()) {
        result.setWalkObjectId(feature.walkObjectId());
    }
    return result;
}

db::ObjectsInPhoto getObjectsInPhoto(const Feature& feature)
{
    auto result = db::ObjectsInPhoto{};
    if (feature.objectsInPhoto()) {
        for (const auto& objectInPhoto : *feature.objectsInPhoto()) {
            result.emplace_back(feature.id(),
                                decode(objectInPhoto->type()),
                                objectInPhoto->minX(),
                                objectInPhoto->minY(),
                                objectInPhoto->maxX(),
                                objectInPhoto->maxY(),
                                /*confidence*/ 0.5);
        }
    }
    return result;
}

}  // anonymous namespace

FeaturesReader::FeaturesReader(const std::string& fromDirectory,
                               EMappingMode mappingMode)
    : featuresStorage_{common::blobFromFile(fromDirectory + "/" + FEATURES_FILE,
                                            mappingMode)}
    , features_{GetRoot<Features>(featuresStorage_.Data())}
    , photoTimelinesStorage_{common::blobFromFile(
          fromDirectory + "/" + PHOTO_TIMELINES_FILE,
          mappingMode)}
    , photoTimelines_{GetRoot<PhotoTimelines>(photoTimelinesStorage_.Data())}
    , rtree_{fromDirectory + "/" + RTREE_FILE, mappingMode}
    , walkObjectPhotosStorage_{common::blobFromFile(
          fromDirectory + "/" + WALK_OBJECT_PHOTOS_FILE,
          mappingMode)}
    , walkObjectPhotos_{
          GetRoot<WalkObjectPhotos>(walkObjectPhotosStorage_.Data())}
{
    bool areVersionsConsistent =
        rtree_.version() == decode(*features_->version()) &&
        rtree_.version() == decode(*photoTimelines_->version()) &&
        rtree_.version() == decode(*walkObjectPhotos_->version());
    REQUIRE(areVersionsConsistent,
            "inconsistent versions of "
                << FEATURES_FILE << "(" << decode(*features_->version())
                << "), " << PHOTO_TIMELINES_FILE << "("
                << decode(*photoTimelines_->version()) << "), "
                << WALK_OBJECT_PHOTOS_FILE << "("
                << decode(*walkObjectPhotos_->version()) << "), " << RTREE_FILE
                << "(" << rtree_.version() << ")");
}

std::string_view FeaturesReader::version() const
{
    return decode(*features_->version());
}

size_t FeaturesReader::featuresNumber() const
{
    return features_->features()->size();
}

db::TId FeaturesReader::lastTxnId() const
{
    return features_->lastTxnId();
}

TSchemaVersion FeaturesReader::schemaVersion() const
{
    return features_->schemaVersion();
}

db::Feature FeaturesReader::feature(size_t offset) const
{
    REQUIRE(offset < featuresNumber(), "out of features");
    return getFeature(*features_->features()->Get(offset));
}

db::ObjectsInPhoto FeaturesReader::objectsInPhoto(size_t offset) const
{
    REQUIRE(offset < featuresNumber(), "out of features");
    return getObjectsInPhoto(*features_->features()->Get(offset));
}

std::optional<db::Feature> FeaturesReader::featureById(db::TId featureId) const
{
    if (auto ptr = features_->features()->LookupByKey(featureId)) {
        return getFeature(*ptr);
    }
    return std::nullopt;
}

db::ObjectsInPhoto FeaturesReader::objectsInPhotoByFeatureId(
    db::TId featureId) const
{
    if (auto ptr = features_->features()->LookupByKey(featureId)) {
        return getObjectsInPhoto(*ptr);
    }
    return {};
}

db::TIds FeaturesReader::lookupFeatureIdsByWalkObjectId(
    db::TId walkObjectId) const
{
    auto searchValue = WalkObjectPhoto(walkObjectId, {});
    auto first = walkObjectPhotos_->walkObjectPhotos()->begin();
    auto last = walkObjectPhotos_->walkObjectPhotos()->end();
    auto [lowerBound, upperBound] = std::equal_range(
        first, last, &searchValue, [](const auto& lhs, const auto& rhs) {
            return lhs->walkObjectId() < rhs->walkObjectId();
        });
    auto result = db::TIds{};
    std::for_each(lowerBound, upperBound, [&](const auto& item) {
        result.push_back(item->featureId());
    });
    return result;
}

}  // namespace maps::mrc::fb
