#include "module.h"
#include "../utils/misc.h"
#include "../utils/road_utils.h"

#include <yandex/maps/wiki/common/misc.h>
#include <yandex/maps/wiki/validator/check.h>
#include <yandex/maps/wiki/validator/categories.h>
#include <maps/libs/geolib/include/convex_hull.h>
#include <maps/libs/geolib/include/vector.h>

namespace maps {
namespace wiki {
namespace validator {
namespace checks {

using categories::RD_EL;
using categories::RD_JC;

namespace {

const double ANGLE_TOLERANCE = M_PI / 10.0;

enum class NodeType {
    RoadStart,
    RoadEnd
};

bool
isStraightContinuation(
    const geolib3::Polyline2& geom1, NodeType nodeType1,
    const geolib3::Polyline2& geom2, NodeType nodeType2)
{
    const auto& points1 = geom1.points();
    const auto& points2 = geom2.points();

    auto isApproxStraightLine =
        [&](const geolib3::Point2& p1, const geolib3::Point2& p2, const geolib3::Point2& p3) {
            return geolib3::angle(p1 - p2, p2 - p3) <= ANGLE_TOLERANCE;
        };

    return isApproxStraightLine(
        nodeType1 == NodeType::RoadStart ? points1.at(1) : points1.at(points1.size() - 2),
        nodeType1 == NodeType::RoadStart ? points1.front() : points1.back(),
        nodeType2 == NodeType::RoadStart ? points2.at(1) : points2.at(points2.size() - 2));
}

} // namespace

VALIDATOR_SIMPLE_CHECK( wrong_oneways, RD_EL, RD_JC )
{
    context->objects<RD_EL>().visit(
        [&](const RoadElement* element) {
            if (!utils::RoadAccessIdFilter(common::AccessId::Car)(element) ||
                element->direction() == RoadElement::Direction::Both || element->fc() > 6) {
                    return;
            }

            if (!context->objects<RD_JC>().loaded(element->startJunction()) ||
                !context->objects<RD_JC>().loaded(element->endJunction())) {
                    return;
            }

            auto startJunction = context->objects<RD_JC>().byId(element->startJunction());
            auto endJunction = context->objects<RD_JC>().byId(element->endJunction());

            auto hasContinuationInVector =
                [&](NodeType fromNodeType, const std::vector<TId>& toIds, NodeType toNodeType) {
                    for (auto toId : toIds) {
                        if (toId == element->id()) {
                            continue;
                        }
                        if (!context->objects<RD_EL>().loaded(toId)) {
                            continue;
                        }
                        auto to = context->objects<RD_EL>().byId(toId);
                        if (!utils::RoadAccessIdFilter(common::AccessId::Car)(to) ||
                            to->direction() != RoadElement::Direction::Both || to->fc() > 6) {
                                continue;
                        }
                        if (isStraightContinuation(element->geom(), fromNodeType, to->geom(), toNodeType)) {
                            return true;
                        }
                    }
                    return false;
                };

            auto hasContinuationInJunction =
                [&](const Junction* junction, NodeType fromNodeType) {
                    return
                        hasContinuationInVector(fromNodeType, junction->outElements(), NodeType::RoadStart) ||
                        hasContinuationInVector(fromNodeType, junction->inElements(), NodeType::RoadEnd);
                };

            if (hasContinuationInJunction(startJunction, NodeType::RoadStart) &&
                hasContinuationInJunction(endJunction, NodeType::RoadEnd)) {
                    context->report(
                        element->fow() == common::FOW::Ramp ? Severity::Warning : Severity::Error,
                        "wrong-oneway",
                        geolib3::bufferedConvexHull(element->geom().points(), utils::BUFFER_DISTANCE),
                        { element->id() });
            }
        }
    );
}

} // namespace checks
} // namespace validator
} // namespace wiki
} // namespace maps
