class TopologyUtil {

  @VisibleForTesting
  static @Nonnull Topology computeRawLayer3Topology(
      @Nonnull Layer1Topology rawLayer1Topology,
      @Nonnull Layer1Topology layer1LogicalTopology,
      @Nonnull Layer2Topology layer2Topology,
      @Nonnull Map<String, Configuration> configurations) {
    Set<String> layer1TailNodes =
        Stream.concat(
                rawLayer1Topology.getGraph().edges().stream(),
                layer1LogicalTopology.getGraph().edges().stream())
            .map(l1Edge -> l1Edge.getNode1().getHostname())
            .collect(ImmutableSet.toImmutableSet());
    Stream<Edge> filteredEdgeStream =
        synthesizeL3Topology(configurations).getEdges().stream()
            // keep if either node is not in tail of edge in layer-1, or if vertices are in
            // same broadcast domain
            .filter(
                edge ->
                    !layer1TailNodes.contains(edge.getNode1())
                        || !layer1TailNodes.contains(edge.getNode2())
                        || layer2Topology.inSameBroadcastDomain(edge.getHead(), edge.getTail()));
    NetworkConfigurations nc = NetworkConfigurations.of(configurations);
    // Look over all L1 logical edges and see if they both have link-local addresses
    Stream<Edge> layer1LLAEdgeStream =
        layer1LogicalTopology.getGraph().edges().stream()
            .filter(
                edge ->
                    // at least one link-local address exists on both edge endpoints
                    !nc.getInterface(
                                edge.getNode1().getHostname(), edge.getNode1().getInterfaceName())
                            .map(Interface::getAllLinkLocalAddresses)
                            .map(Set::isEmpty)
                            .orElse(true)
                        && !nc.getInterface(
                                edge.getNode2().getHostname(), edge.getNode2().getInterfaceName())
                            .map(Interface::getAllLinkLocalAddresses)
                            .map(Set::isEmpty)
                            .orElse(true))
            .flatMap(
                edge -> {
                  Edge l3Edge =
                      new Edge(
                          NodeInterfacePair.of(
                              edge.getNode1().getHostname(), edge.getNode1().getInterfaceName()),
                          NodeInterfacePair.of(
                              edge.getNode2().getHostname(), edge.getNode2().getInterfaceName()));
                  // Return forward and reverse edges (L1 topology not guaranteed to be symmetric)
                  // In the end it collapses to a set anyway
                  return Stream.of(l3Edge, l3Edge.reverse());
                });
    // Special-case sub-interfaces of aggregate interfaces
    ImmutableSet<NodeInterfacePair> subInterfaces =
        configurations.values().stream()
            .flatMap(Configuration::activeInterfaces)
            .filter(i -> i.getInterfaceType() == InterfaceType.AGGREGATE_CHILD)
            .map(i -> NodeInterfacePair.of(i.getOwner().getHostname(), i.getName()))
            .collect(ImmutableSet.toImmutableSet());

    Stream<Edge> subInterfaceLLAStream =
        subInterfaces.stream()
            .flatMap(
                i1 ->
                    subInterfaces.stream()
                        .filter(
                            i2 -> !i1.equals(i2) && layer2Topology.inSameBroadcastDomain(i1, i2))
                        .map(i2 -> new Edge(i1, i2)));
    return new Topology(
        Streams.concat(filteredEdgeStream, layer1LLAEdgeStream, subInterfaceLLAStream)
            .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.naturalOrder())));
  }

  private  TopologyUtil();

}