// Extracts the geometry for each segment and calculates the traveled distance
// Combines the geometry form the phantom node with the PathData
// to the full route geometry.
//
// turn    0   1   2   3   4
//         s...x...y...z...t
//         |---|segment 0
//             |---| segment 1
//                 |---| segment 2
//                     |---| segment 3
inline LegGeometry assembleGeometry(const datafacade::BaseDataFacade &facade,
                                    const std::vector<PathData> &leg_data,
                                    const PhantomNode &source_node,
                                    const PhantomNode &target_node)
{
    LegGeometry geometry;

    // segment 0 first and last
    geometry.segment_offsets.push_back(0);
    geometry.locations.push_back(source_node.location);

    // Need to get the node ID preceding the source phantom node
    // TODO: check if this was traversed in reverse?
    std::vector<NodeID> reverse_geometry;
    facade.GetUncompressedGeometry(source_node.reverse_packed_geometry_id, reverse_geometry);
    geometry.osm_node_ids.push_back(facade.GetOSMNodeIDOfNode(
        reverse_geometry[reverse_geometry.size() - source_node.fwd_segment_position - 1]));

    std::vector<uint8_t> forward_datasource_vector;
    facade.GetUncompressedDatasources(source_node.forward_packed_geometry_id,
                                      forward_datasource_vector);

    auto cumulative_distance = 0.;
    auto current_distance = 0.;
    auto prev_coordinate = geometry.locations.front();
    for (const auto &path_point : leg_data)
    {
        auto coordinate = facade.GetCoordinateOfNode(path_point.turn_via_node);
        current_distance =
            util::coordinate_calculation::haversineDistance(prev_coordinate, coordinate);
        cumulative_distance += current_distance;

        // all changes to this check have to be matched with assemble_steps
        if (path_point.turn_instruction.type != extractor::guidance::TurnType::NoTurn)
        {
            geometry.segment_distances.push_back(cumulative_distance);
            geometry.segment_offsets.push_back(geometry.locations.size());
            cumulative_distance = 0.;
        }

        prev_coordinate = coordinate;
        geometry.annotations.emplace_back(LegGeometry::Annotation{
            current_distance, path_point.duration_until_turn / 10., path_point.datasource_id});
        geometry.locations.push_back(std::move(coordinate));
        geometry.osm_node_ids.push_back(facade.GetOSMNodeIDOfNode(path_point.turn_via_node));
    }
    current_distance =
        util::coordinate_calculation::haversineDistance(prev_coordinate, target_node.location);
    cumulative_distance += current_distance;
    // segment leading to the target node
    geometry.segment_distances.push_back(cumulative_distance);

    std::vector<DatasourceID> forward_datasources;
    facade.GetUncompressedDatasources(target_node.forward_packed_geometry_id, forward_datasources);

    geometry.annotations.emplace_back(
        LegGeometry::Annotation{current_distance,
                                target_node.forward_weight / 10.,
                                forward_datasources[target_node.fwd_segment_position]});
    geometry.segment_offsets.push_back(geometry.locations.size());
    geometry.locations.push_back(target_node.location);

    // Need to get the node ID following the destination phantom node
    // TODO: check if this was traversed in reverse??
    std::vector<NodeID> forward_geometry;
    facade.GetUncompressedGeometry(target_node.forward_packed_geometry_id, forward_geometry);
    geometry.osm_node_ids.push_back(
        facade.GetOSMNodeIDOfNode(forward_geometry[target_node.fwd_segment_position]));

    BOOST_ASSERT(geometry.segment_distances.size() == geometry.segment_offsets.size() - 1);
    BOOST_ASSERT(geometry.locations.size() > geometry.segment_distances.size());
    BOOST_ASSERT(geometry.annotations.size() == geometry.locations.size() - 1);

    return geometry;
}
// Extracts the geometry for each segment and calculates the traveled distance
// Combines the geometry form the phantom node with the PathData
// to the full route geometry.
//
// turn    0   1   2   3   4
//         s...x...y...z...t
//         |---|segment 0
//             |---| segment 1
//                 |---| segment 2
//                     |---| segment 3
inline LegGeometry assembleGeometry(const datafacade::BaseDataFacade &facade,
                                    const std::vector<PathData> &leg_data,
                                    const PhantomNode &source_node,
                                    const PhantomNode &target_node,
                                    const bool reversed_source,
                                    const bool reversed_target)
{
    LegGeometry geometry;

    // segment 0 first and last
    geometry.segment_offsets.push_back(0);
    geometry.locations.push_back(source_node.location);

    //                          u       *      v
    //                          0 -- 1 -- 2 -- 3
    // fwd_segment_position:  1
    // source node fwd:       1      1 -> 2 -> 3
    // source node rev:       2 0 <- 1 <- 2
    const auto source_segment_start_coordinate =
        source_node.fwd_segment_position + (reversed_source ? 1 : 0);
    const auto source_node_id =
        reversed_source ? source_node.reverse_segment_id.id : source_node.forward_segment_id.id;
    const auto source_gemetry_id = facade.GetGeometryIndex(source_node_id).id;
    const std::vector<NodeID> source_geometry =
        facade.GetUncompressedForwardGeometry(source_gemetry_id);
    geometry.osm_node_ids.push_back(
        facade.GetOSMNodeIDOfNode(source_geometry[source_segment_start_coordinate]));

    auto cumulative_distance = 0.;
    auto current_distance = 0.;
    auto prev_coordinate = geometry.locations.front();
    for (const auto &path_point : leg_data)
    {
        auto coordinate = facade.GetCoordinateOfNode(path_point.turn_via_node);
        current_distance =
            util::coordinate_calculation::haversineDistance(prev_coordinate, coordinate);
        cumulative_distance += current_distance;

        // all changes to this check have to be matched with assemble_steps
        if (path_point.turn_instruction.type != extractor::guidance::TurnType::NoTurn)
        {
            geometry.segment_distances.push_back(cumulative_distance);
            geometry.segment_offsets.push_back(geometry.locations.size());
            cumulative_distance = 0.;
        }

        prev_coordinate = coordinate;
        geometry.annotations.emplace_back(
            LegGeometry::Annotation{current_distance,
                                    path_point.duration_until_turn / 10.,
                                    path_point.weight_until_turn / facade.GetWeightMultiplier(),
                                    path_point.datasource_id});
        geometry.locations.push_back(std::move(coordinate));
        geometry.osm_node_ids.push_back(facade.GetOSMNodeIDOfNode(path_point.turn_via_node));
    }
    current_distance =
        util::coordinate_calculation::haversineDistance(prev_coordinate, target_node.location);
    cumulative_distance += current_distance;
    // segment leading to the target node
    geometry.segment_distances.push_back(cumulative_distance);

    const auto target_node_id =
        reversed_target ? target_node.reverse_segment_id.id : target_node.forward_segment_id.id;
    const auto target_gemetry_id = facade.GetGeometryIndex(target_node_id).id;
    const std::vector<DatasourceID> forward_datasources =
        facade.GetUncompressedForwardDatasources(target_gemetry_id);

    // FIXME if source and target phantoms are on the same segment then duration and weight
    // will be from one projected point till end of segment
    // testbot/weight.feature:Start and target on the same and adjacent edge
    geometry.annotations.emplace_back(LegGeometry::Annotation{
        current_distance,
        (reversed_target ? target_node.reverse_duration : target_node.forward_duration) / 10.,
        (reversed_target ? target_node.reverse_weight : target_node.forward_weight) /
            facade.GetWeightMultiplier(),
        forward_datasources[target_node.fwd_segment_position]});

    geometry.segment_offsets.push_back(geometry.locations.size());
    geometry.locations.push_back(target_node.location);

    //                           u       *      v
    //                           0 -- 1 -- 2 -- 3
    // fwd_segment_position:  1
    // target node fwd:       2  0 -> 1 -> 2
    // target node rev:       1       1 <- 2 <- 3
    const auto target_segment_end_coordinate =
        target_node.fwd_segment_position + (reversed_target ? 0 : 1);
    const std::vector<NodeID> target_geometry =
        facade.GetUncompressedForwardGeometry(target_gemetry_id);
    geometry.osm_node_ids.push_back(
        facade.GetOSMNodeIDOfNode(target_geometry[target_segment_end_coordinate]));

    BOOST_ASSERT(geometry.segment_distances.size() == geometry.segment_offsets.size() - 1);
    BOOST_ASSERT(geometry.locations.size() > geometry.segment_distances.size());
    BOOST_ASSERT(geometry.annotations.size() == geometry.locations.size() - 1);

    return geometry;
}