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

#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_hypothesis/include/common.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_hypothesis/include/geom.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_hypothesis/include/graph.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_hypothesis/include/load.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>

#include <maps/libs/common/include/exception.h>
#include <maps/libs/geolib/include/closest_point.h>
#include <maps/libs/geolib/include/common.h>
#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/geolib/include/direction.h>
#include <maps/libs/geolib/include/distance.h>
#include <maps/libs/geolib/include/units_literals.h>
#include <yandex/maps/wiki/graph/graph.h>
#include <yandex/maps/wiki/graph/shortest_path.h>

using namespace maps::geolib3::literals;

namespace maps::mrc::eye {

namespace {

bool isCondition(const db::eye::SignAttrs& attrs) {
    static const std::vector<traffic_signs::TrafficSign> 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
    };

    return wiki::common::isIn(attrs.type, SIGN_TYPES);
}

db::eye::Hypothesis generateProhibitedPathHypothesis(
    const db::eye::ObjectLocation& location,
    const wiki::social::feedback::Movement movement,
    const geolib3::Polyline2& mercatorPath)
{
    db::eye::ProhibitedPathAttrs attrs{movement, mercatorPath};
    return db::eye::Hypothesis(location.mercatorPos(), attrs);
}

class ProhibitedPathCheck {
public:
    ProhibitedPathCheck(const geolib3::Point2& location, object::Loader& loader);

    db::eye::Hypotheses noLeftTurn(const db::eye::Object& object, const db::eye::ObjectLocation& location);
    db::eye::Hypotheses noRightTurn(const db::eye::Object& object, const db::eye::ObjectLocation& location);
    db::eye::Hypotheses noUturn(const db::eye::Object& object, const db::eye::ObjectLocation& location);

    db::eye::Hypotheses onlyForwardMovePossible(const db::eye::Object& object, const db::eye::ObjectLocation& location);
    db::eye::Hypotheses onlyLeftTurnPossible(const db::eye::Object& object, const db::eye::ObjectLocation& location);
    db::eye::Hypotheses onlyRightTurnPossible(const db::eye::Object& object, const db::eye::ObjectLocation& location);
    db::eye::Hypotheses onlyForwardAndRightMovePossible(const db::eye::Object& object, const db::eye::ObjectLocation& location);
    db::eye::Hypotheses onlyForwardAndLeftMovePossible(const db::eye::Object& object, const db::eye::ObjectLocation& location);

private:
    geolib3::Polyline2 getPathGeometry(const DirectedPoint& position, const Path& path) const;
    db::eye::Hypothesis generate(
        const db::eye::ObjectLocation& location,
        wiki::social::feedback::Movement movement,
        const Path& path) const;

    Graph graph_;
};

bool roadElementFilter(const object::RoadElement& element)
{
    return not element.underConstruction()
                && element.fc() <= object::RoadElement::FunctionalClass::UnclassifiedRoad;
}

ProhibitedPathCheck::ProhibitedPathCheck(const geolib3::Point2& location, object::Loader& loader)
    : graph_(loadGraph(loader, location, roadElementFilter, 150 /* meters*/))
{}

geolib3::Polyline2 ProhibitedPathCheck::getPathGeometry(
        const DirectedPoint& position,
        const Path& path) const
{
    ASSERT(not path.empty());
    geolib3::Polyline2 result;
    constexpr double END_LENGTH_METERS = 20;
    for (size_t i = 0; i < path.size(); ++i) {
        const auto directedId = graph_.getDirectedId(path[i]);
        geolib3::Polyline2 polyline = graph_.elementByDirectedId(directedId).geom();
        if (directedId.direction() == graph::Direction::Backward) {
            polyline.reverse();
        }
        if (i == 0) {
            const geolib3::Point2 projection = geolib3::closestPoint(polyline, position.mercator());
            result = geolib3::partitionToEnd(polyline, projection);
            const double length = geolib3::toMercatorUnits(END_LENGTH_METERS, projection);
            if (geolib3::length(result) < length) {
                result = cutFromEnd(polyline, END_LENGTH_METERS);
            }
            continue;
        }
        if (i + 1 == path.size()) {
            polyline = cutFromStart(polyline, END_LENGTH_METERS);
        }
        result.extend(polyline, geolib3::EndPointMergePolicy::MergeEqualPoints);
    }
    result = geolib3::unique(result);
    if (not geolib3::isSimple(result)) {
         constexpr double SHIFT_METERS = 1;
         const double SHIFT = geolib3::toMercatorUnits(SHIFT_METERS, result.points().front());
         return geolib3::equidistant(result, SHIFT, geolib3::Orientation::Clockwise);
    }
    return result;
}

db::eye::Hypothesis ProhibitedPathCheck::generate(
        const db::eye::ObjectLocation& location,
        wiki::social::feedback::Movement movement,
        const Path& path) const
{
    return generateProhibitedPathHypothesis(location, movement, getPathGeometry(location, path));
}

inline Path pushBack(Path path, NodeId nodeId)
{
    path.push_back(nodeId);
    return path;
}

bool isShorterThan(const geolib3::Polyline2& mercator, double lengthMeters)
{
    const geolib3::PointsVector& points = mercator.points();
    ASSERT(points.size() >= 2);
    return geolib3::length(mercator) < geolib3::toMercatorUnits(lengthMeters, points.front());
}

bool hasTwoWayInIntersection(
        const RoadIntersections& intersections,
        Graph& graph,
        NodeId fromNodeId)
{
    return std::any_of(
        intersections.begin(), intersections.end(),
        [&, fromNodeId](const auto& intersection) {
            const auto& element = graph.elementById(intersection.roadElementId);
            const auto& junction = graph.endJunction(fromNodeId);
            return element.fow() == object::RoadElement::FormOfWay::TwoWayRoad
                && element.direction() == element.directionTo(junction.id());
        }
    );
}

db::eye::Hypotheses ProhibitedPathCheck::noLeftTurn(
    const db::eye::Object& object,
    const db::eye::ObjectLocation& location)
{
    ASSERT(object.attrs<db::eye::SignAttrs>().type == traffic_signs::TrafficSign::ProhibitoryNoLeftTurn);
    bool anyLeftIntersection = false;
    for (const auto startNodeId: graph_.getClosestCodirectional(location)) {
        db::eye::Hypotheses hypotheses;
        Path path{startNodeId};
        for (OptionalNodeId nodeId = startNodeId; nodeId; ) {
            const RoadCross roadCross = graph_.getRoadCross(*nodeId);
            for (auto nodeId: roadCross.leftMoves()) {
                if (not graph_.hasBarrier(nodeId)) {
                    hypotheses.push_back(
                        generate(location, wiki::social::feedback::Movement::LeftTurn, pushBack(path, nodeId))
                    );
                }
            }
            if (not roadCross.left().empty()) {
                anyLeftIntersection = true;
                if (not hasTwoWayInIntersection(roadCross.left(), graph_, *nodeId)) {
                    break;
                }
            }
            nodeId = tryPushForwardMove(path, roadCross);
        }
        if (not hypotheses.empty()) {
            return hypotheses;
        }
    }
    if (not anyLeftIntersection) {
        // No any left intersection!
    }
    return {};
}

db::eye::Hypotheses ProhibitedPathCheck::noRightTurn(
    const db::eye::Object& object,
    const db::eye::ObjectLocation& location)
{
    ASSERT(object.attrs<db::eye::SignAttrs>().type == traffic_signs::TrafficSign::ProhibitoryNoRightTurn);
    const NodeIds startNodeIds = graph_.getClosestCodirectional(
        location, /*distance meters*/ 20, /*tolerance meters*/ 5, /*angle*/ 30_deg
    );
    bool anyRightIntersection = false;
    for (const auto startNodeId: startNodeIds) {
        db::eye::Hypotheses hypotheses;
        Path path{startNodeId};
        for (OptionalNodeId nodeId = startNodeId; nodeId;) {
            const RoadCross roadCross = graph_.getRoadCross(*nodeId);
            for (const auto nodeId: roadCross.rightMoves()) {
                const auto geom = getPathGeometry(location, pushBack(path, nodeId));
                if (not graph_.hasBarrier(nodeId)) {
                    hypotheses.push_back(
                        generate(location, wiki::social::feedback::Movement::RightTurn, pushBack(path, nodeId))
                    );
                }
            }
            if (not roadCross.right().empty()) {
                anyRightIntersection = true;
                break;
            }
            nodeId = tryPushForwardMove(path, roadCross);
        }
        if (not hypotheses.empty()) {
            return hypotheses;
        }
    }
    if (not anyRightIntersection) {
        // No any right intersection!
    }
    return {};
}

NodeIds join(NodeIds lhs, NodeIds rhs)
{
    std::sort(lhs.begin(), lhs.end());
    std::sort(rhs.begin(), rhs.end());
    NodeIds result;
    std::set_union(
        lhs.begin(), lhs.end(),
        rhs.begin(), rhs.end(),
        std::back_inserter(result)
    );
    return result;
}

db::eye::Hypotheses ProhibitedPathCheck::onlyForwardMovePossible(
    const db::eye::Object& object,
    const db::eye::ObjectLocation& location)
{
    ASSERT(object.attrs<db::eye::SignAttrs>().type == traffic_signs::TrafficSign::MandatoryProceedStraight);
    const NodeIds startNodeIds = graph_.getClosestCodirectional(
        location, /*distance meters*/ 30, /*tolerance meters*/ 10, 30_deg
    );
    bool anyIntersection = false;
    for (const auto startNodeId: startNodeIds) {
        db::eye::Hypotheses hypotheses;
        Path path{startNodeId};
        for (OptionalNodeId nodeId = startNodeId; nodeId;) {
            const RoadCross roadCross = graph_.getRoadCross(*nodeId);
            for (const auto nodeId: roadCross.leftMoves()) {
                const geolib3::Polyline2 geom = getPathGeometry(location, pushBack(path, nodeId));
                if (isShorterThan(geom, 120 /*meters*/)) {
                    hypotheses.push_back(
                        generateProhibitedPathHypothesis(location, wiki::social::feedback::Movement::LeftTurn, geom)
                    );
                }
            }
            for (const auto nodeId: roadCross.rightMoves()) {
                const geolib3::Polyline2 geom = getPathGeometry(location, pushBack(path, nodeId));
                if (isShorterThan(geom, 120 /*meters*/)) {
                    hypotheses.push_back(
                        generateProhibitedPathHypothesis(location, wiki::social::feedback::Movement::RightTurn, geom)
                    );
                }
            }
            if (not (roadCross.left().empty() && roadCross.right().empty())) {
                if (roadCross.anyForwardMove()) {
                    anyIntersection = true;
                }
                break;
            }
            nodeId = tryPushForwardMove(path, roadCross);
        }
        if (not hypotheses.empty()) {
            return hypotheses;
        }
    }
    if (not anyIntersection) {
        // No any turns!
    }
    return {};
}

db::eye::Hypotheses ProhibitedPathCheck::onlyForwardAndRightMovePossible(
    const db::eye::Object& object,
    const db::eye::ObjectLocation& location)
{
    ASSERT(object.attrs<db::eye::SignAttrs>().type == traffic_signs::TrafficSign::MandatoryProceedStraightOrTurnRight);
    const NodeIds startNodeIds = graph_.getClosestCodirectional(
        location, /*distance meters*/ 25, /*tolerance meters*/ 10
    );
    bool anyForwardAndRightMoves = false;
    for (const auto startNodeId: startNodeIds) {
        db::eye::Hypotheses hypotheses;
        Path path {startNodeId};
        bool passTurnOnTwoWayRoad = false;
        for (OptionalNodeId nodeId = startNodeId; nodeId;) {
            const RoadCross roadCross = graph_.getRoadCross(path.back());
            for (const auto nodeId: roadCross.leftMoves()) {
                const geolib3::Polyline2 geom = getPathGeometry(location, pushBack(path, nodeId));
                if (!graph_.hasBarrier(nodeId) and isShorterThan(geom, 120 /*meters*/)) {
                    hypotheses.push_back(
                        generateProhibitedPathHypothesis(location, wiki::social::feedback::Movement::LeftTurn, geom)
                    );
                }
            }
            // The sign 4.1.4 is often placed at rather complicated road crosses with two carriageways.
            // All predicates below helps us to check full crossroad.
            // So, we should stop our check if and only if we leave the crossroad.
            // In case of two carriageways we may cross one or two roads at right side.
            const NodeIds rightMoves = roadCross.rightMoves();
            const bool hasTurnOnRegularRoad = std::any_of(
                rightMoves.begin(), rightMoves.end(),
                [this](NodeId nodeId) {
                    const auto& element = graph_.elementByNodeId(nodeId);
                    return element.fow() != object::RoadElement::FormOfWay::TwoWayRoad;
                }
            );
            if (hasTurnOnRegularRoad || (passTurnOnTwoWayRoad
                    && hasTwoWayInIntersection(roadCross.right(), graph_, *nodeId)))
            {
                if (roadCross.anyForwardMove()) {
                    anyForwardAndRightMoves = true;
                }
                if (not hypotheses.empty()) {
                    return hypotheses;
                }
                break;
            }
            const bool hasTurnOnTwoWayRoad = std::any_of(
                rightMoves.begin(), rightMoves.end(),
                [this](NodeId nodeId) {
                    const auto& element = graph_.elementByNodeId(nodeId);
                    return element.fow() == object::RoadElement::FormOfWay::TwoWayRoad;
                }
            );
            if (hasTurnOnTwoWayRoad) {
                passTurnOnTwoWayRoad = true;
            }
            nodeId = tryPushForwardMove(path, roadCross);
        }
    }
    if (not anyForwardAndRightMoves) {
        // No forward and right moves
    }
    return {};
}

db::eye::Hypotheses ProhibitedPathCheck::onlyForwardAndLeftMovePossible(
    const db::eye::Object& object,
    const db::eye::ObjectLocation& location)
{
    ASSERT(object.attrs<db::eye::SignAttrs>().type == traffic_signs::TrafficSign::MandatoryProceedStraightOrTurnLeft);
    const NodeIds startNodeIds = graph_.getClosestCodirectional(
        location, /*distance meters*/ 25, /*tolerance meters*/ 5
    );
    bool anyForwardAndLeftMoves = false;
    for (const auto startNodeId: startNodeIds) {
        db::eye::Hypotheses hypotheses;
        Path path {startNodeId};
        for (OptionalNodeId nodeId = startNodeId; nodeId;) {
            const RoadCross roadCross = graph_.getRoadCross(path.back());
            for (const auto nodeId: roadCross.rightMoves()) {
                const geolib3::Polyline2 geom = getPathGeometry(location, pushBack(path, nodeId));
                if (!graph_.hasBarrier(nodeId) and isShorterThan(geom, 120 /*meters*/)) {
                    hypotheses.push_back(
                        generateProhibitedPathHypothesis(location, wiki::social::feedback::Movement::RightTurn, geom)
                    );
                }
            }
            if (roadCross.anyLeftMove()) {
                if (roadCross.anyForwardMove()) {
                    anyForwardAndLeftMoves = true;
                }
                if (not hypotheses.empty()) {
                    return hypotheses;
                }
                break;
            }
            nodeId = tryPushForwardMove(path, roadCross);
        }
    }
    if (not anyForwardAndLeftMoves) {
        // No forward and left moves
    }
    return {};
}

db::eye::Hypotheses ProhibitedPathCheck::onlyRightTurnPossible(
    const db::eye::Object& object,
    const db::eye::ObjectLocation& location)
{
    ASSERT(object.attrs<db::eye::SignAttrs>().type == traffic_signs::TrafficSign::MandatoryTurnRightAhead);
    // It's impossible define true sign orientation.
    // But sign of this type is often located at right adjacent road.
    const DirectedPoint forward(location);
    const DirectedPoint right = forward.rotateClockwise(-90_deg);
    const NodeIds startNodeIds = join(
        graph_.getClosestCodirectional(forward, /*distance meters*/ 25, /*tolerance meters*/ 5),
        graph_.getClosestCodirectional(right, /*distance meters*/ 25, /*tolerance meters*/ 5)
    );
    bool anyRightMove = false;
    for (const auto startNodeId: startNodeIds) {
        db::eye::Hypotheses hypotheses;
        Path path {startNodeId};
        for (OptionalNodeId nodeId = startNodeId; nodeId; ) {
            const RoadCross roadCross = graph_.getRoadCross(
                path.back(),
                Graph::Mode::UseFirstSegmentAndApproximation
            );
            for (const auto nodeId: roadCross.leftMoves()) {
                const geolib3::Polyline2 geom = getPathGeometry(location, pushBack(path, nodeId));
                if (isShorterThan(geom, 120 /*meters*/)) {
                    hypotheses.push_back(
                        generateProhibitedPathHypothesis(location, wiki::social::feedback::Movement::LeftTurn, geom)
                    );
                }
            }
            if (not roadCross.right().empty()) {
                for (const auto nodeId: roadCross.forwardMoves()) {
                    const geolib3::Polyline2 geom = getPathGeometry(location, pushBack(path, nodeId));
                    if (isShorterThan(geom, 120 /*meters*/)) {
                        hypotheses.push_back(
                            generateProhibitedPathHypothesis(location, wiki::social::feedback::Movement::Forward, geom)
                        );
                    }
                }
                if (not hypotheses.empty()) {
                    return hypotheses;
                }
                anyRightMove = true;
                break;
            }
            nodeId = tryPushForwardMove(path, roadCross);
        }
    }
    if (not anyRightMove) {
        // No any right move!
    }
    return {};
}

db::eye::Hypotheses ProhibitedPathCheck::onlyLeftTurnPossible(
    const db::eye::Object& object,
    const db::eye::ObjectLocation& location)
{
    ASSERT(object.attrs<db::eye::SignAttrs>().type == traffic_signs::TrafficSign::MandatoryTurnLeftAhead);
    const NodeIds startNodeIds = graph_.getClosestCodirectional(
        location, /*distance meters*/ 30, /*tolerance meters*/ 10
    );
    bool anyLeftMove = false;
    for (const auto startNodeId: startNodeIds) {
        db::eye::Hypotheses hypotheses;
        Path path{startNodeId};
        for (OptionalNodeId nodeId = startNodeId; nodeId; ) {
            const RoadCross roadCross = graph_.getRoadCross(
                path.back(),
                Graph::Mode::UseFirstSegmentAndApproximation
            );
            for (const auto nodeId: roadCross.rightMoves()) {
                const geolib3::Polyline2 geom = getPathGeometry(location, pushBack(path, nodeId));
                if (!graph_.hasBarrier(nodeId) and isShorterThan(geom, 120 /*meters*/)) {
                    hypotheses.push_back(
                        generateProhibitedPathHypothesis(location, wiki::social::feedback::Movement::RightTurn, geom)
                    );
                }
            }
            if (roadCross.anyLeftMove()) {
                for (const auto nodeId: roadCross.forwardMoves()) {
                    const geolib3::Polyline2 geom = getPathGeometry(location, pushBack(path, nodeId));
                    if (!graph_.hasBarrier(nodeId) and isShorterThan(geom, 120 /*meters*/) ) {
                        hypotheses.push_back(
                            generateProhibitedPathHypothesis(location, wiki::social::feedback::Movement::Forward, geom)
                        );
                    }
                }
                if (not hypotheses.empty()) {
                    return hypotheses;
                }
                anyLeftMove = true;
                break;
            }
            nodeId = tryPushForwardMove(path, roadCross);
        }
    }
    if (not anyLeftMove) {
        // No any left move!
    }
    return {};
}

bool isUturn(const geolib3::Polyline2& mercator)
{
    ASSERT(mercator.pointsNumber() >= 2);
    if (not isShorterThan(mercator, 150 /*meters*/)) {
        return false;
    }
    constexpr geolib3::Radians MAX_RIGHT_TURN = geolib3::toRadians(45_deg);
    geolib3::Radians rightTurn = 0_rad;
    for (size_t i = 1; i < mercator.segmentsNumber(); ++i) {
        const geolib3::Segment2 from = mercator.segmentAt(i-1);
        const geolib3::Segment2 to = mercator.segmentAt(i);
        const double angle = geolib3::signedAngle(from.vector(), to.vector());
        if (angle < 0) {
            rightTurn += tagged::abs(geolib3::Radians(angle));
        }
    }
    return rightTurn < MAX_RIGHT_TURN;
}

db::eye::Hypotheses ProhibitedPathCheck::noUturn(
    const db::eye::Object& object,
    const db::eye::ObjectLocation& location)
{
    ASSERT(
        object.attrs<db::eye::SignAttrs>().type == traffic_signs::TrafficSign::ProhibitoryNoUturn
        || object.attrs<db::eye::SignAttrs>().type == traffic_signs::TrafficSign::MandatoryTurnRightAhead
        || object.attrs<db::eye::SignAttrs>().type == traffic_signs::TrafficSign::MandatoryProceedStraight
        || object.attrs<db::eye::SignAttrs>().type == traffic_signs::TrafficSign::MandatoryProceedStraightOrTurnRight
    );
    const DirectedPoint position(location);
    const NodeIds startNodeIds = graph_.getClosestCodirectional(
        position, /*distance meters*/ 40, /*tolerance meters*/ 20
    );
    for (auto startNodeId: startNodeIds) {
        const object::RoadElement& start = graph_.elementByNodeId(startNodeId);
        if (start.direction() == ymapsdf::rd::Direction::Both) {
            if (not graph_.hasUturn(startNodeId)) {
                continue;
            }
            const DirectedId directedId = graph_.getDirectedId(startNodeId);
            const auto endNodeId = graph_.getNodeId(directedId.reverse());
            ASSERT(endNodeId);
            return {
                generate(location, wiki::social::feedback::Movement::Uturn, {startNodeId, *endNodeId})
            };
        }
        const NodeIds endNodeIds = graph_.getClosestCodirectional(
            position.reverse(), /*distance meters*/ 50, /*tolerace meters*/ 20
        );
        for (auto endNodeId: endNodeIds) {
            const object::RoadElement& end = graph_.elementByNodeId(endNodeId);
            if (end.direction() == ymapsdf::rd::Direction::Both) {
                continue;
            }
            const DirectedId directedId = graph_.getDirectedId(endNodeId);
            const auto shortestPath = wiki::graph::findShortestPath(
                [this](graph::NodeId nodeId) { return graph_.edges(nodeId); },
                startNodeId,
                [&](graph::NodeId nodeId) {
                    return graph_.getDirectedId(nodeId) == directedId;
                }
            );
            if (not shortestPath.path().empty()) {
                const geolib3::Polyline2 geom = getPathGeometry(location, shortestPath.path());
                if (isUturn(geom)) {
                    return {generateProhibitedPathHypothesis(location, wiki::social::feedback::Movement::Uturn, geom)};
                }
            } else {
                // Check that there is some left intersection!
            }
        }
    }
    return {};
}

db::eye::Hypotheses mergeHypotheses(
    db::eye::Hypotheses lhs,
    const db::eye::Hypotheses& rhs)
{
    lhs.insert(lhs.end(), rhs.begin(), rhs.end());
    return lhs;
}

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;
}

} // namespace

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

bool WrongConditionGeneratorImpl::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 WrongConditionGeneratorImpl::validate(
    const db::eye::Object& object,
    const db::eye::ObjectLocation& location,
    const db::eye::Objects& slaveObjects,
    object::Loader& loader)
{
    if (hasTrucksTable(slaveObjects)) {
        return {};
    }

    ProhibitedPathCheck check(location.mercatorPos(), loader);

    switch(object.attrs<db::eye::SignAttrs>().type) {
        case traffic_signs::TrafficSign::ProhibitoryNoRightTurn: // 3.18.1
            return check.noRightTurn(object, location);
        case traffic_signs::TrafficSign::ProhibitoryNoLeftTurn: // 3.18.2
            return check.noLeftTurn(object, location);
        case traffic_signs::TrafficSign::ProhibitoryNoUturn: // 3.19
            return check.noUturn(object, location);
        case traffic_signs::TrafficSign::MandatoryProceedStraight: // 4.1.1
            return mergeHypotheses(
                check.onlyForwardMovePossible(object, location),
                check.noUturn(object, location)
            );
        case traffic_signs::TrafficSign::MandatoryTurnRightAhead: // 4.1.2
            return mergeHypotheses(
                check.onlyRightTurnPossible(object, location),
                check.noUturn(object, location)
            );
        case traffic_signs::TrafficSign::MandatoryTurnLeftAhead: // 4.1.3
            return check.onlyLeftTurnPossible(object, location);
        case traffic_signs::TrafficSign::MandatoryProceedStraightOrTurnRight: // 4.1.4
            return mergeHypotheses(
                check.onlyForwardAndRightMovePossible(object, location),
                check.noUturn(object, location)
            );
        case traffic_signs::TrafficSign::MandatoryProceedStraightOrTurnLeft: // 4.1.5
            return check.onlyForwardAndLeftMovePossible(object, location);
        default:
            throw RuntimeError() << "Unexpected sign type " << object.attrs<db::eye::SignAttrs>().type;
    }
}

} // namespace maps::mrc::eye
