#include "extractor/guidance/turn_discovery.hpp"
#include "extractor/guidance/constants.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/guidance/toolkit.hpp"

namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace lanes
{

bool findPreviousIntersection(const NodeID node_v,
                              const EdgeID via_edge,
                              const Intersection intersection,
                              const IntersectionGenerator &intersection_generator,
                              const util::NodeBasedDynamicGraph &node_based_graph,
                              // output parameters
                              NodeID &result_node,
                              EdgeID &result_via_edge,
                              Intersection &result_intersection)
{
    /* We need to find the intersection that is located prior to via_edge.

     *
     * NODE_U  -> PREVIOUS_ID            -> NODE_V -> VIA_EDGE -> NODE_W:INTERSECTION
     * NODE_U? <- STRAIGHTMOST           <- NODE_V <- UTURN
     * NODE_U? -> UTURN == PREVIOUSE_ID? -> NODE_V -> VIA_EDGE
     *
     * To do so, we first get the intersection atNODE and find the straightmost turn from that
     * node. This will result in NODE_X. The uturn in the intersection at NODE_X should be
     * PREVIOUS_ID. To verify that find, we check the intersection using our PREVIOUS_ID candidate
     * to check the intersection at NODE for via_edge
     */
    const constexpr double COMBINE_DISTANCE_CUTOFF = 30;

    const auto coordinate_extractor = intersection_generator.GetCoordinateExtractor();
    const auto via_edge_length = util::coordinate_calculation::getLength(
        coordinate_extractor.GetForwardCoordinatesAlongRoad(node_v, via_edge),
        &util::coordinate_calculation::haversineDistance);

    // we check if via-edge is too short. In this case the previous turn cannot influence the turn
    // at via_edge and the intersection at NODE_W
    if (via_edge_length > COMBINE_DISTANCE_CUTOFF)
        return false;

    // Node -> Via_Edge -> Intersection[0 == UTURN] -> reverse_of(via_edge) -> Intersection at node
    // (looking at the reverse direction).
    const auto node_w = node_based_graph.GetTarget(via_edge);
    const auto u_turn_at_node_w = intersection[0].eid;
    const auto node_v_reverse_intersection = intersection_generator(node_w, u_turn_at_node_w);

    // Continue along the straightmost turn. If there is no straight turn, we cannot find a valid
    // previous intersection.
    const auto straightmost_at_v_in_reverse =
        node_v_reverse_intersection.findClosestTurn(STRAIGHT_ANGLE);

    // TODO evaluate if narrow turn is the right criterion here... Might be that other angles are
    // valid
    if (util::guidance::angularDeviation(straightmost_at_v_in_reverse->angle, STRAIGHT_ANGLE) >
        GROUP_ANGLE)
        return false;

    const auto node_u = node_based_graph.GetTarget(straightmost_at_v_in_reverse->eid);
    const auto node_u_reverse_intersection =
        intersection_generator(node_v, straightmost_at_v_in_reverse->eid);

    // now check that the u-turn at the given intersection connects to via-edge
    // The u-turn at the now found intersection should, hopefully, represent the previous edge.
    result_node = node_u;
    result_via_edge = node_u_reverse_intersection[0].eid;

    // if the edge is not traversable, we obviously don't have a previous intersection or couldn't
    // find it.
    if (node_based_graph.GetEdgeData(result_via_edge).reversed)
    {
        result_via_edge = SPECIAL_EDGEID;
        result_node = SPECIAL_NODEID;
        return false;
    }

    result_intersection = intersection_generator(node_u, result_via_edge);
    const auto check_via_edge =
        result_intersection.end() !=
        std::find_if(result_intersection.begin(),
                     result_intersection.end(),
                     [via_edge](const ConnectedRoad &road) { return road.eid == via_edge; });

    if (!check_via_edge)
    {
        result_via_edge = SPECIAL_EDGEID;
        result_node = SPECIAL_NODEID;
        return false;
    }

    return true;
}

} // namespace lanes
} // namespace guidance
} // namespace extractor
} // namespace osrm
