#include <maps/wikimap/mapspro/services/mrc/eye/lib/detect_object/include/detect_object.h>

#include <maps/wikimap/mapspro/services/mrc/eye/lib/common/include/metadata.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/common/include/txn.h>

#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/frame_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/recognition_gateway.h>

#include <algorithm>
#include <iterator>
#include <utility>


namespace maps::mrc::eye {

db::TIds keepWithoutRecognition(
        pqxx::transaction_base& txn,
        db::eye::RecognitionType type,
        int16_t version,
        db::TIds frameIds)
{
    if (frameIds.empty()) {
        return frameIds;
    }

    db::TIds diffIds;

    namespace table = db::eye::table;
    db::TIds recognizedFrameIds = db::eye::FrameGateway(txn).loadIds(
        table::Frame::id.in(frameIds)
            and table::Frame::id == table::Recognition::frameId
            and table::Frame::orientation == table::Recognition::orientation
            and table::Recognition::type == type
            and table::Recognition::source == db::eye::RecognitionSource::Model
            and table::Recognition::version == version
    );

    std::sort(frameIds.begin(), frameIds.end());
    std::sort(recognizedFrameIds.begin(), recognizedFrameIds.end());

    std::set_difference(
        frameIds.begin(), frameIds.end(),
        recognizedFrameIds.begin(), recognizedFrameIds.end(),
        std::back_inserter(diffIds)
    );

    return diffIds;
}

namespace {

void updateValue(
    db::eye::Recognition& recognitionToUpdate,
    const db::eye::Recognition& recognition)
{
    switch (recognition.type()) {
        case db::eye::RecognitionType::DetectSign:
            {
                auto value = recognition.value<db::eye::DetectedSigns>();
                recognitionToUpdate.setValue(value);
                break;
            }
        case db::eye::RecognitionType::DetectTrafficLight:
            {
                auto value = recognition.value<db::eye::DetectedTrafficLights>();
                recognitionToUpdate.setValue(value);
                break;
            }
        case db::eye::RecognitionType::DetectHouseNumber:
            {
                auto value = recognition.value<db::eye::DetectedHouseNumbers>();
                recognitionToUpdate.setValue(value);
                break;
            }
        case db::eye::RecognitionType::DetectRoadMarking:
            {
                auto value = recognition.value<db::eye::DetectedRoadMarkings>();
                recognitionToUpdate.setValue(value);
                break;
            }
        case db::eye::RecognitionType::DetectPanel:
            {
                auto value = recognition.value<db::eye::DetectedPanel>();
                recognitionToUpdate.setValue(value);
                break;
            }
    }
}

} // namespace

void updateRecognitions(
    pqxx::transaction_base& txn,
    db::eye::RecognitionType type,
    int16_t version,
    db::eye::Recognitions& recognitions)
{
    namespace table = db::eye::table;

    db::TIds frameIds;
    for (const db::eye::Recognition& recognition : recognitions) {
        REQUIRE(recognition.type() == type,
                "Incorrect recognition type: " << recognition.type() << " != " << type);

        frameIds.push_back(recognition.frameId());
    }

    db::eye::Recognitions recognitionsToUpdate = db::eye::RecognitionGateway(txn).load(
        table::Recognition::frameId.in(frameIds)
            and table::Recognition::type == type
            and table::Recognition::source == db::eye::RecognitionSource::Model
            and table::Recognition::version == version
    );

    db::IdTo<db::eye::Recognitions> recognitionsToUpdateByFrameId;
    for (db::eye::Recognition& recognitionToUpdate : recognitionsToUpdate) {
        recognitionsToUpdateByFrameId[recognitionToUpdate.frameId()]
            .push_back(std::move(recognitionToUpdate));
    }

    for (db::eye::Recognition& recognition : recognitions) {
        auto recognitionsIt = recognitionsToUpdateByFrameId.find(recognition.frameId());
        if (recognitionsToUpdateByFrameId.end() == recognitionsIt) {
            continue;
        }

        for (db::eye::Recognition& recognitionToUpdate : recognitionsIt->second) {
            if (recognitionToUpdate.orientation() == recognition.orientation()) {
                updateValue(recognitionToUpdate, recognition);

                recognition = std::move(recognitionToUpdate);
            }
        }
    }

    db::eye::RecognitionGateway(txn).upsertx(recognitions);
}

} // namespace maps::mrc::eye
