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

#include <maps/libs/log8/include/log8.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/geometry.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/threadpool_wrapper.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/utility.h>

#include <tbb/tbb.h>

namespace maps::mrc::excess_features_eraser {
namespace {

using Subline = common::geometry::SubPolylineWithValue<Photo*>;
using Sublines = std::vector<Subline>;

auto ages = allOf<AgeCategory>();
auto cameras = {db::CameraDeviation::Front, db::CameraDeviation::Right};
auto lessEdge = common::lessFn(&fb::PhotoToEdgePair::edgeId);
auto equalEdge = common::equalFn(&fb::PhotoToEdgePair::edgeId);

bool lessPriority(const Photo* lhs, const Photo* rhs)
{
    return std::tuple{lhs->dayPart, lhs->quality, lhs->featureId} <
           std::tuple{rhs->dayPart, rhs->quality, rhs->featureId};
}

Sublines makeEdgeCoverage(PhotoToEdgeRefs::iterator first,
                          PhotoToEdgeRefs::iterator last,
                          const PhotoById& photoById,
                          AgeCategory ageCategory,
                          db::CameraDeviation cameraDeviation,
                          db::FeaturePrivacy privacy)
{
    auto sublines = Sublines{};
    std::for_each(first, last, [&](const auto& photoToEdge) {
        auto& photo = photoById.at(photoToEdge.get().featureId()).get();
        if (photo.ageCategory == ageCategory &&
            photo.cameraDeviation == cameraDeviation &&
            photo.privacy <= privacy) {
            sublines.emplace_back(
                common::geometry::SubPolyline(
                    convertToCommonPolylinePosition(photoToEdge.get().begin()),
                    convertToCommonPolylinePosition(photoToEdge.get().end())),
                std::addressof(photo));
        }
    });
    return common::geometry::merge(sublines, lessPriority);
}

db::FeaturePrivacy maxPrivacy(const Sublines& sublines)
{
    auto result = db::FeaturePrivacy::Min;
    for (const auto& subline : sublines) {
        result = std::max(subline.value->privacy, result);
    }
    return result;
}

void copyFeatureIds(const Sublines& sublines, db::TIds& result)
{
    for (const auto& subline : sublines) {
        result.push_back(subline.value->featureId);
    }
}

db::TIds getFeatureIdsOfAgedEdgeCoverage(PhotoToEdgeRefs::iterator first,
                                         PhotoToEdgeRefs::iterator last,
                                         const PhotoById& photoById)
{
    auto result = db::TIds{};
    for (auto age : ages) {
        for (auto camera : cameras) {
            for (auto privacy = db::FeaturePrivacy::Max;;) {
                auto coverage = makeEdgeCoverage(
                    first, last, photoById, age, camera, privacy);
                copyFeatureIds(coverage, result);
                privacy = maxPrivacy(coverage);
                if (privacy == db::FeaturePrivacy::Min) {
                    break;
                }
                advance(privacy, -1);
            }
        }
    }
    common::sortUnique(result);
    return result;
}

void setUseful(const db::TIds& featureIds, PhotoById& result)
{
    for (auto featureId : featureIds) {
        result.at(featureId).get().useful = true;
    }
}

void setUsefulForCoverages(const Datasets& datasets, Photos& photos)
{
    auto threadPool = common::ThreadpoolWrapper{THREADS_NUMBER};
    auto guard = std::mutex{};
    auto photoById = common::makeRefMap(photos, &Photo::featureId);

    // active coverage
    setUseful(datasets.getCoverageFeatureIds(), photoById);

    // aged coverage for each edge
    auto photoToEdgeRefs = datasets.getPhotoToEdgeRefs();
    tbb::parallel_sort(photoToEdgeRefs, lessEdge);
    common::forEachEqualRange(
        photoToEdgeRefs.begin(),
        photoToEdgeRefs.end(),
        equalEdge,
        [&](PhotoToEdgeRefs::iterator first, PhotoToEdgeRefs::iterator last) {
            threadPool->add([&, first, last] {
                auto featureIds =
                    getFeatureIdsOfAgedEdgeCoverage(first, last, photoById);
                auto lock = std::lock_guard{guard};
                setUseful(featureIds, photoById);
            });
        });
    threadPool->drain();
    threadPool.checkExceptions();
}

}  // namespace

db::TIds loadNotForCoverFeatureIds(pgpool3::Pool& pgPool,
                                   const Datasets& datasets,
                                   chrono::TimePoint now)
{
    auto stringSet = StringSet{};
    auto photos =
        loadPhotos(pgPool, datasets.getAllFeatureIds(), stringSet, now);
    setMedianQualityPerRide(photos);
    setUsefulForCoverages(datasets, photos);
    propagateUsefulOnRide(photos);
    return getUselessFeatureIds(photos);
}

}  // namespace maps::mrc::excess_features_eraser
