#include "photo.h"

#include <maps/libs/chrono/include/days.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/utility.h>

#include <tbb/tbb.h>

namespace maps::mrc::excess_features_eraser {
namespace {

auto lessRide =
    common::lessFn(common::makeTupleFn(&Photo::sourceId, &Photo::timestamp));

bool equalRide(const Photo& lhs, const Photo& rhs)
{
    return lhs.sourceId != db::feature::NO_SOURCE_ID &&
           lhs.sourceId == rhs.sourceId &&
           abs(lhs.timestamp - rhs.timestamp) <= std::chrono::seconds{15};
}

void propagateUseful(Photos::iterator first, Photos::iterator last)
{
    if (std::any_of(first, last, std::mem_fn(&Photo::useful))) {
        std::for_each(first, last, [](Photo& photo) { photo.useful = true; });
    }
}

void setMedianQuality(Photos::iterator first, Photos::iterator last)
{
    auto count = std::distance(first, last);
    ASSERT(count);
    auto quals = std::vector<float>(count);
    std::transform(first, last, quals.begin(), [](const Photo& photo) {
        return photo.quality;
    });
    auto n = count / 2;
    std::nth_element(quals.begin(), quals.begin() + n, quals.end());
    std::for_each(first, last, [&](Photo& photo) { photo.quality = quals[n]; });
}

AgeCategory getAgeCategory(chrono::TimePoint time, chrono::TimePoint now)
{
    auto days = std::chrono::duration_cast<chrono::Days>(now - time).count();
    ASSERT(days >= 0);
    if (days < 7) {
        return AgeCategory::Age_0_7;
    }
    if (days < 30) {
        return AgeCategory::Age_7_30;
    }
    if (days < 90) {
        return AgeCategory::Age_30_90;
    }
    if (days < 180) {
        return AgeCategory::Age_90_180;
    }
    return AgeCategory::Age_180;
}

}  // namespace

Photo toPhoto(const db::Feature& feature,
              StringSet& stringSet,
              chrono::TimePoint now)
{
    const auto ageCategory = getAgeCategory(feature.timestamp(), now);
    return Photo{
        .geodeticPos = feature.geodeticPos(),
        .sourceId = *stringSet.insert(feature.sourceId()).first,
        .featureId = feature.id(),
        .timestamp = feature.timestamp(),
        .quality = static_cast<float>(feature.quality()),
        .cameraDeviation = feature.cameraDeviation(),
        .ageCategory = ageCategory,
        .dayPart = common::getDayPart(feature.timestamp(),
                                      feature.geodeticPos().y(),
                                      feature.geodeticPos().x()),
        .privacy = feature.privacy(),
        .useful = (db::Dataset::Rides == feature.dataset() ||
                   AgeCategory::Age_0_7 == ageCategory ||
                   AgeCategory::Age_7_30 == ageCategory),
    };
}

void propagateUsefulOnRide(Photos& photos)
{
    tbb::parallel_sort(photos, lessRide);
    common::forEachEqualRange(
        photos.begin(), photos.end(), equalRide, propagateUseful);
}

void setMedianQualityPerRide(Photos& photos)
{
    tbb::parallel_sort(photos, lessRide);
    common::forEachEqualRange(
        photos.begin(), photos.end(), equalRide, setMedianQuality);
}

db::TIds getUselessFeatureIds(const Photos& photos)
{
    auto result = db::TIds{};
    for (const auto& photo : photos) {
        if (!photo.useful) {
            result.push_back(photo.featureId);
        }
    }
    return result;
}

}  // namespace maps::mrc::excess_features_eraser
