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

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

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

#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/misc.h>

namespace maps::mrc::eye {

namespace {

static const std::map<traffic_signs::TrafficSign, wiki::social::feedback::Type>
TRAFFIC_SIGN_TYPE_TO_FEEDBACK_TYPE = {
    {
        traffic_signs::TrafficSign::ProhibitoryNoEntry,
        wiki::social::feedback::Type::OneWayTrafficSign
    },
    {
        traffic_signs::TrafficSign::PrescriptionEntryToOneWayRoadOnTheRight,
        wiki::social::feedback::Type::OneWayTrafficSign
    },
    {
        traffic_signs::TrafficSign::PrescriptionEntryToOneWayRoadOnTheLeft,
        wiki::social::feedback::Type::OneWayTrafficSign
    },
    {
        traffic_signs::TrafficSign::ProhibitoryNoVehicles,
        wiki::social::feedback::Type::TrafficProhibitedSign
    },
    {
        traffic_signs::TrafficSign::MandatoryRoundabout,
        wiki::social::feedback::Type::TrafficCircleSign
    },
    {
        traffic_signs::TrafficSign::ProhibitoryNoHeavyGoodsVehicles, // 3.4
        wiki::social::feedback::Type::TrucksProhibitedSign
    },
    {
        traffic_signs::TrafficSign::ProhibitoryNoTrailer, // 3.7
        wiki::social::feedback::Type::TrucksProhibitedSign
    },
    {
        traffic_signs::TrafficSign::ProhibitoryMaxWeight, // 3.11
        wiki::social::feedback::Type::WeightLimitingSign
    },
    {
        traffic_signs::TrafficSign::ProhibitoryMaxWeightPerAxle, // 3.12
        wiki::social::feedback::Type::WeightLimitingSign
    },
    {
        traffic_signs::TrafficSign::ProhibitoryMaxHeight, // 3.13
        wiki::social::feedback::Type::DimensionsLimitingSign
    },
    {
        traffic_signs::TrafficSign::ProhibitoryMaxWidth, // 3.14
        wiki::social::feedback::Type::DimensionsLimitingSign
    },
    {
        traffic_signs::TrafficSign::ProhibitoryMaxLength, // 3.15
        wiki::social::feedback::Type::DimensionsLimitingSign
    },
};

static const std::set<traffic_signs::TrafficSign>
MANEUVER_RESTRICTION_SIGN_TYPES = {
    traffic_signs::TrafficSign::ProhibitoryNoRightTurn, // 3.18.1
    traffic_signs::TrafficSign::ProhibitoryNoLeftTurn,  // 3.18.2
    traffic_signs::TrafficSign::ProhibitoryNoUturn,     // 3.19

    traffic_signs::TrafficSign::MandatoryProceedStraight, // 4.1.1
    traffic_signs::TrafficSign::MandatoryTurnRightAhead,  // 4.1.2
    traffic_signs::TrafficSign::MandatoryTurnLeftAhead,   // 4.1.3
    traffic_signs::TrafficSign::MandatoryProceedStraightOrTurnRight, // 4.1.4
    traffic_signs::TrafficSign::MandatoryProceedStraightOrTurnLeft,  // 4.1.5
};

bool hasTrucksTable(const db::eye::Objects& slaveObjects) {
    bool hasTable = false;

    for (const db::eye::Object& slaveObject : slaveObjects) {
        const auto tableType = slaveObject.attrs<db::eye::SignAttrs>().type;
        if (traffic_signs::TrafficSign::InformationHeavyVehicle == tableType) {
            hasTable = true;
            break;
        }
    }

    return hasTable;
}

bool isTrafficSign(traffic_signs::TrafficSign signType) {
    return TRAFFIC_SIGN_TYPE_TO_FEEDBACK_TYPE.contains(signType)
        || MANEUVER_RESTRICTION_SIGN_TYPES.contains(signType);
}

std::optional<wiki::social::feedback::Type> getFeedbackType(
    traffic_signs::TrafficSign signType,
    const db::eye::Objects& slaveObjects)
{
    auto feedbackTypeIt = TRAFFIC_SIGN_TYPE_TO_FEEDBACK_TYPE.find(signType);
    if (TRAFFIC_SIGN_TYPE_TO_FEEDBACK_TYPE.end() != feedbackTypeIt) {
        return feedbackTypeIt->second;
    } else if (hasTrucksTable(slaveObjects)) {
        if (MANEUVER_RESTRICTION_SIGN_TYPES.contains(signType)) {
            return wiki::social::feedback::Type::TrucksManeuverRestrictionSign;
        }
    }

    return std::nullopt;
}

} // namespace

bool TrafficSignGeneratorImpl::appliesToObject(const db::eye::Object& object)
{
    db::eye::SignAttrs attrs = object.attrs<db::eye::SignAttrs>();
    return ! attrs.temporary && isTrafficSign(attrs.type);
}

bool TrafficSignGeneratorImpl::hasDuplicate(
    pqxx::transaction_base& txn,
    const db::eye::Hypothesis& hypothesis,
    db::TId /*objectId*/)
{
    constexpr double SEARCH_RADIUS_METERS = 30.;
    return hasDuplicateDefaultCheck(txn, hypothesis, SEARCH_RADIUS_METERS);
}

db::eye::Hypotheses TrafficSignGeneratorImpl::validate(
    const db::eye::Object& object,
    const db::eye::ObjectLocation& location,
    const db::eye::Objects& slaveObjects,
    object::Loader& /*loader*/)
{
    const traffic_signs::TrafficSign signType = object.attrs<db::eye::SignAttrs>().type;
    const auto mayBeFeedbackType = getFeedbackType(signType, slaveObjects);

    if (!mayBeFeedbackType.has_value()) {
        return{};
    }

    db::eye::TrafficSignAttrs attrs{mayBeFeedbackType.value()};
    db::eye::Hypothesis hypothesis(location.mercatorPos(), attrs);

    return {hypothesis};

}

} // namespace maps::mrc::eye
