class Graph {

  @VisibleForTesting
  @Nonnull
  static Table2<String, String, BgpActivePeerConfig> generateIbgpNeighbors(
      Map<String, Configuration> configurations) {
    // Map of hostname to a set of local IPs of *all* iBGP neighbors
    Map<String, Set<Ip>> ips = new HashMap<>();

    // Match iBGP sessions with pairs of routers and BgpPeerConfig
    for (Entry<String, Configuration> entry : configurations.entrySet()) {
      String hostname = entry.getKey();
      Configuration conf = entry.getValue();
      BgpProcess p = conf.getDefaultVrf().getBgpProcess();
      if (p == null) {
        // No bgp process, nothing to do
        continue;
      }
      for (BgpActivePeerConfig n : p.getActiveNeighbors().values()) {
        if (n.getLocalAs() == null || n.getRemoteAsns().isEmpty()) {
          // Invalid config
          continue;
        }
        if (n.getRemoteAsns().equals(LongSpace.of(n.getLocalAs()))) {
          ips.computeIfAbsent(hostname, key -> new HashSet<>()).add(n.getLocalIp());
        }
      }
    }

    // Init the resulting map
    Table2<String, String, BgpActivePeerConfig> neighbors = new Table2<>();

    // Loop over all iBGP configs and match up
    for (Entry<String, Configuration> entry : configurations.entrySet()) {
      String localHostname = entry.getKey();
      BgpProcess proc = entry.getValue().getDefaultVrf().getBgpProcess();
      if (proc == null) {
        // No bgp process, nothing to do
        continue;
      }

      for (Entry<Prefix, BgpActivePeerConfig> entry2 : proc.getActiveNeighbors().entrySet()) {
        Prefix remotePrefix = entry2.getKey();
        BgpActivePeerConfig localBgpConfig = entry2.getValue();
        if (localBgpConfig.getLocalAs() == null || localBgpConfig.getRemoteAsns().isEmpty()) {
          // Invalid config
          continue;
        }
        if (!localBgpConfig.getRemoteAsns().equals(LongSpace.of(localBgpConfig.getLocalAs()))) {
          // Not iBGP
          continue;
        }

        // Loop over all local IPs computed earlier to find session matches
        for (Entry<String, Set<Ip>> ipEntry : ips.entrySet()) {
          String candidateHostname = ipEntry.getKey();
          Set<Ip> candidateIps = ipEntry.getValue();
          for (Ip candidateLocalIp : candidateIps) {
            // Check that it's not a self-edge and candidate's IP matches remote prefix
            if (!localHostname.equals(candidateHostname)
                && remotePrefix.containsIp(candidateLocalIp)) {
              // We have a neighbor match
              neighbors.put(localHostname, candidateHostname, localBgpConfig);
            }
          }
        }
      }
    }
    return neighbors;
  }

  public  Graph(IBatfish batfish, NetworkSnapshot snapshot);
  public  Graph(IBatfish batfish, NetworkSnapshot snapshot, boolean bddBasedAnalysis);
  public  Graph(
      IBatfish batfish, NetworkSnapshot snapshot, @Nullable Map<String, Configuration> configs);
  public  Graph(
      IBatfish batfish,
      NetworkSnapshot snapshot,
      @Nullable Map<String, Configuration> configs,
      @Nullable Set<String> routers);
  public  Graph(
      IBatfish batfish,
      NetworkSnapshot snapshot,
      @Nullable Map<String, Configuration> configs,
      @Nullable Set<String> routers,
      @Nullable Set<CommunitySetExpr> communities,
      @Nullable Set<String> asPathRegexes);
  public  Graph(
      IBatfish batfish,
      NetworkSnapshot snapshot,
      @Nullable Map<String, Configuration> configs,
      @Nullable Set<String> routers,
      @Nullable Set<CommunitySetExpr> communities,
      @Nullable Set<String> asPathRegexes,
      boolean bddBasedAnalysis);

}