/*
 * Copyright (C) 2014 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.graph;

import static com.google.common.graph.Graphs.copyOf;
import static com.google.common.graph.Graphs.inducedSubgraph;
import static com.google.common.graph.Graphs.reachableNodes;
import static com.google.common.graph.Graphs.transitiveClosure;
import static com.google.common.graph.Graphs.transpose;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.common.collect.ImmutableSet;
import java.util.Set;
import org.jspecify.annotations.NullUnmarked;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Tests for {@link Graphs}. Tests assume that the implementation of the method {@code addEdge} adds
 * the missing nodes to the graph, then adds the edge between them.
 */
@RunWith(JUnit4.class)
@NullUnmarked
public class GraphsTest {
  private static final Integer N1 = 1;
  private static final Integer N2 = 2;
  private static final Integer N3 = 3;
  private static final Integer N4 = 4;
  private static final String E11 = "1-1";
  private static final String E11_A = "1-1a";
  private static final String E12 = "1-2";
  private static final String E12_A = "1-2a";
  private static final String E12_B = "1-2b";
  private static final String E21 = "2-1";
  private static final String E13 = "1-3";
  private static final String E31 = "3-1";
  private static final String E34 = "3-4";
  private static final String E44 = "4-4";
  private static final int NODE_COUNT = 20;
  private static final int EDGE_COUNT = 20;
  // TODO(user): Consider adding both error messages from here and {@link AbstractNetworkTest}
  // in one class (may be a utility class for error messages).
  private static final String ERROR_PARALLEL_EDGE = "connected by a different edge";
  private static final String ERROR_NEGATIVE_COUNT = "is non-negative";
  static final String ERROR_SELF_LOOP = "self-loops are not allowed";

  @Test
  public void transitiveClosure_directedGraph() {
    MutableGraph<Integer> directedGraph = GraphBuilder.directed().allowsSelfLoops(false).build();
    directedGraph.putEdge(N1, N2);
    directedGraph.putEdge(N1, N3);
    directedGraph.putEdge(N2, N3);
    directedGraph.addNode(N4);

    MutableGraph<Integer> expectedClosure = GraphBuilder.directed().allowsSelfLoops(true).build();
    expectedClosure.putEdge(N1, N1);
    expectedClosure.putEdge(N1, N2);
    expectedClosure.putEdge(N1, N3);
    expectedClosure.putEdge(N2, N2);
    expectedClosure.putEdge(N2, N3);
    expectedClosure.putEdge(N3, N3);
    expectedClosure.putEdge(N4, N4);

    checkTransitiveClosure(directedGraph, expectedClosure);
  }

  @Test
  public void transitiveClosure_undirectedGraph() {
    MutableGraph<Integer> undirectedGraph =
        GraphBuilder.undirected().allowsSelfLoops(false).build();
    undirectedGraph.putEdge(N1, N2);
    undirectedGraph.putEdge(N1, N3);
    undirectedGraph.putEdge(N2, N3);
    undirectedGraph.addNode(N4);

    MutableGraph<Integer> expectedClosure = GraphBuilder.undirected().allowsSelfLoops(true).build();
    expectedClosure.putEdge(N1, N1);
    expectedClosure.putEdge(N1, N2);
    expectedClosure.putEdge(N1, N3);
    expectedClosure.putEdge(N2, N2);
    expectedClosure.putEdge(N2, N3);
    expectedClosure.putEdge(N3, N3);
    expectedClosure.putEdge(N4, N4);

    checkTransitiveClosure(undirectedGraph, expectedClosure);
  }

  @Test
  public void transitiveClosure_directedPathGraph() {
    MutableGraph<Integer> directedGraph = GraphBuilder.directed().allowsSelfLoops(false).build();
    directedGraph.putEdge(N1, N2);
    directedGraph.putEdge(N2, N3);
    directedGraph.putEdge(N3, N4);

    MutableGraph<Integer> expectedClosure = GraphBuilder.directed().allowsSelfLoops(true).build();
    expectedClosure.putEdge(N1, N1);
    expectedClosure.putEdge(N1, N2);
    expectedClosure.putEdge(N1, N3);
    expectedClosure.putEdge(N1, N4);
    expectedClosure.putEdge(N2, N2);
    expectedClosure.putEdge(N2, N3);
    expectedClosure.putEdge(N2, N4);
    expectedClosure.putEdge(N3, N3);
    expectedClosure.putEdge(N3, N4);
    expectedClosure.putEdge(N4, N4);

    checkTransitiveClosure(directedGraph, expectedClosure);
  }

  @Test
  public void transitiveClosure_undirectedPathGraph() {
    MutableGraph<Integer> undirectedGraph =
        GraphBuilder.undirected().allowsSelfLoops(false).build();
    undirectedGraph.putEdge(N1, N2);
    undirectedGraph.putEdge(N2, N3);
    undirectedGraph.putEdge(N3, N4);

    MutableGraph<Integer> expectedClosure = GraphBuilder.undirected().allowsSelfLoops(true).build();
    expectedClosure.putEdge(N1, N1);
    expectedClosure.putEdge(N1, N2);
    expectedClosure.putEdge(N1, N3);
    expectedClosure.putEdge(N1, N4);
    expectedClosure.putEdge(N2, N2);
    expectedClosure.putEdge(N2, N3);
    expectedClosure.putEdge(N2, N4);
    expectedClosure.putEdge(N3, N3);
    expectedClosure.putEdge(N3, N4);
    expectedClosure.putEdge(N4, N4);

    checkTransitiveClosure(undirectedGraph, expectedClosure);
  }

  @Test
  public void transitiveClosure_directedCycleGraph() {
    MutableGraph<Integer> directedGraph = GraphBuilder.directed().allowsSelfLoops(false).build();
    directedGraph.putEdge(N1, N2);
    directedGraph.putEdge(N2, N3);
    directedGraph.putEdge(N3, N4);
    directedGraph.putEdge(N4, N1);

    MutableGraph<Integer> expectedClosure = GraphBuilder.directed().allowsSelfLoops(true).build();
    expectedClosure.putEdge(N1, N1);
    expectedClosure.putEdge(N1, N2);
    expectedClosure.putEdge(N1, N3);
    expectedClosure.putEdge(N1, N4);
    expectedClosure.putEdge(N2, N1);
    expectedClosure.putEdge(N2, N2);
    expectedClosure.putEdge(N2, N3);
    expectedClosure.putEdge(N2, N4);
    expectedClosure.putEdge(N3, N1);
    expectedClosure.putEdge(N3, N2);
    expectedClosure.putEdge(N3, N3);
    expectedClosure.putEdge(N3, N4);
    expectedClosure.putEdge(N4, N1);
    expectedClosure.putEdge(N4, N2);
    expectedClosure.putEdge(N4, N3);
    expectedClosure.putEdge(N4, N4);

    checkTransitiveClosure(directedGraph, expectedClosure);
  }

  @Test
  public void transitiveClosure_undirectedCycleGraph() {
    MutableGraph<Integer> undirectedGraph =
        GraphBuilder.undirected().allowsSelfLoops(false).build();
    undirectedGraph.putEdge(N1, N2);
    undirectedGraph.putEdge(N2, N3);
    undirectedGraph.putEdge(N3, N4);
    undirectedGraph.putEdge(N4, N1);

    MutableGraph<Integer> expectedClosure = GraphBuilder.undirected().allowsSelfLoops(true).build();
    expectedClosure.putEdge(N1, N1);
    expectedClosure.putEdge(N1, N2);
    expectedClosure.putEdge(N1, N3);
    expectedClosure.putEdge(N1, N4);
    expectedClosure.putEdge(N2, N2);
    expectedClosure.putEdge(N2, N3);
    expectedClosure.putEdge(N2, N4);
    expectedClosure.putEdge(N3, N3);
    expectedClosure.putEdge(N3, N4);
    expectedClosure.putEdge(N4, N4);

    checkTransitiveClosure(undirectedGraph, expectedClosure);
  }

  @Test
  public void transpose_undirectedGraph() {
    MutableGraph<Integer> undirectedGraph = GraphBuilder.undirected().build();
    undirectedGraph.putEdge(N1, N2);

    assertThat(transpose(undirectedGraph)).isSameInstanceAs(undirectedGraph);
  }

  @Test
  public void transpose_directedGraph() {
    MutableGraph<Integer> directedGraph = GraphBuilder.directed().allowsSelfLoops(true).build();
    directedGraph.putEdge(N1, N3);
    directedGraph.putEdge(N3, N1);
    directedGraph.putEdge(N1, N2);
    directedGraph.putEdge(N1, N1);
    directedGraph.putEdge(N3, N4);

    MutableGraph<Integer> expectedTranspose = GraphBuilder.directed().allowsSelfLoops(true).build();
    expectedTranspose.putEdge(N3, N1);
    expectedTranspose.putEdge(N1, N3);
    expectedTranspose.putEdge(N2, N1);
    expectedTranspose.putEdge(N1, N1);
    expectedTranspose.putEdge(N4, N3);

    Graph<Integer> transpose = transpose(directedGraph);
    assertThat(transpose).isEqualTo(expectedTranspose);
    assertThat(transpose(transpose)).isSameInstanceAs(directedGraph);
    AbstractGraphTest.validateGraph(transpose);

    for (Integer node : directedGraph.nodes()) {
      assertThat(directedGraph.inDegree(node)).isSameInstanceAs(transpose.outDegree(node));
      assertThat(directedGraph.outDegree(node)).isSameInstanceAs(transpose.inDegree(node));
    }

    assertThat(transpose.successors(N1)).doesNotContain(N2);
    directedGraph.putEdge(N2, N1);
    // View should be updated.
    assertThat(transpose.successors(N1)).contains(N2);
    AbstractGraphTest.validateGraph(transpose);
  }

  @Test
  public void transpose_undirectedValueGraph() {
    MutableValueGraph<Integer, String> undirectedGraph = ValueGraphBuilder.undirected().build();
    undirectedGraph.putEdgeValue(N1, N2, E12);

    assertThat(transpose(undirectedGraph)).isSameInstanceAs(undirectedGraph);
  }

  @Test
  public void transpose_directedValueGraph() {
    MutableValueGraph<Integer, String> directedGraph =
        ValueGraphBuilder.directed().allowsSelfLoops(true).build();
    directedGraph.putEdgeValue(N1, N3, E13);
    directedGraph.putEdgeValue(N3, N1, E31);
    directedGraph.putEdgeValue(N1, N2, E12);
    directedGraph.putEdgeValue(N1, N1, E11);
    directedGraph.putEdgeValue(N3, N4, E34);

    MutableValueGraph<Integer, String> expectedTranspose =
        ValueGraphBuilder.directed().allowsSelfLoops(true).build();
    expectedTranspose.putEdgeValue(N3, N1, E13);
    expectedTranspose.putEdgeValue(N1, N3, E31);
    expectedTranspose.putEdgeValue(N2, N1, E12);
    expectedTranspose.putEdgeValue(N1, N1, E11);
    expectedTranspose.putEdgeValue(N4, N3, E34);

    ValueGraph<Integer, String> transpose = transpose(directedGraph);
    assertThat(transpose).isEqualTo(expectedTranspose);
    assertThat(transpose(transpose)).isSameInstanceAs(directedGraph);
    AbstractGraphTest.validateGraph(transpose.asGraph());

    assertThat(transpose.edgeValueOrDefault(N1, N2, null)).isNull();
    for (Integer node : directedGraph.nodes()) {
      assertThat(directedGraph.inDegree(node)).isSameInstanceAs(transpose.outDegree(node));
      assertThat(directedGraph.outDegree(node)).isSameInstanceAs(transpose.inDegree(node));
    }

    directedGraph.putEdgeValue(N2, N1, E21);
    // View should be updated.
    assertThat(transpose.edgeValueOrDefault(N1, N2, null)).isEqualTo(E21);
    AbstractGraphTest.validateGraph(transpose.asGraph());
  }

  @Test
  public void transpose_undirectedNetwork() {
    MutableNetwork<Integer, String> undirectedGraph = NetworkBuilder.undirected().build();
    undirectedGraph.addEdge(N1, N2, E12);

    assertThat(transpose(undirectedGraph)).isSameInstanceAs(undirectedGraph);
  }

  @Test
  public void transpose_directedNetwork() {
    MutableNetwork<Integer, String> directedGraph =
        NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build();
    directedGraph.addEdge(N1, N3, E13);
    directedGraph.addEdge(N3, N1, E31);
    directedGraph.addEdge(N1, N2, E12);
    directedGraph.addEdge(N1, N2, E12_A);
    directedGraph.addEdge(N1, N1, E11);
    directedGraph.addEdge(N3, N4, E34);

    MutableNetwork<Integer, String> expectedTranspose =
        NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build();
    expectedTranspose.addEdge(N3, N1, E13);
    expectedTranspose.addEdge(N1, N3, E31);
    expectedTranspose.addEdge(N2, N1, E12);
    expectedTranspose.addEdge(N2, N1, E12_A);
    expectedTranspose.addEdge(N1, N1, E11);
    expectedTranspose.addEdge(N4, N3, E34);

    Network<Integer, String> transpose = transpose(directedGraph);
    assertThat(transpose).isEqualTo(expectedTranspose);
    assertThat(transpose(transpose)).isSameInstanceAs(directedGraph);
    AbstractNetworkTest.validateNetwork(transpose);

    assertThat(transpose.edgesConnecting(N1, N2)).isEmpty();
    assertThat(transpose.edgeConnecting(N1, N2).isPresent()).isFalse();
    assertThat(transpose.edgeConnectingOrNull(N1, N2)).isNull();

    for (Integer node : directedGraph.nodes()) {
      assertThat(directedGraph.inDegree(node)).isSameInstanceAs(transpose.outDegree(node));
      assertThat(directedGraph.outDegree(node)).isSameInstanceAs(transpose.inDegree(node));
    }

    directedGraph.addEdge(N2, N1, E21);
    // View should be updated.
    assertThat(transpose.edgesConnecting(N1, N2)).containsExactly(E21);
    assertThat(transpose.edgeConnecting(N1, N2).get()).isEqualTo(E21);
    assertThat(transpose.edgeConnectingOrNull(N1, N2)).isEqualTo(E21);
    AbstractNetworkTest.validateNetwork(transpose);
  }

  @Test
  public void inducedSubgraph_graph() {
    Set<Integer> nodeSubset = ImmutableSet.of(N1, N2, N4);

    MutableGraph<Integer> directedGraph = GraphBuilder.directed().allowsSelfLoops(true).build();
    directedGraph.putEdge(N1, N2);
    directedGraph.putEdge(N2, N1);
    directedGraph.putEdge(N1, N3); // only incident to one node in nodeSubset
    directedGraph.putEdge(N4, N4);
    directedGraph.putEdge(5, 6); // not incident to any node in nodeSubset

    MutableGraph<Integer> expectedSubgraph = GraphBuilder.directed().allowsSelfLoops(true).build();
    expectedSubgraph.putEdge(N1, N2);
    expectedSubgraph.putEdge(N2, N1);
    expectedSubgraph.putEdge(N4, N4);

    assertThat(inducedSubgraph(directedGraph, nodeSubset)).isEqualTo(expectedSubgraph);
  }

  @Test
  public void inducedSubgraph_valueGraph() {
    Set<Integer> nodeSubset = ImmutableSet.of(N1, N2, N4);

    MutableValueGraph<Integer, String> directedGraph =
        ValueGraphBuilder.directed().allowsSelfLoops(true).build();
    directedGraph.putEdgeValue(N1, N2, E12);
    directedGraph.putEdgeValue(N2, N1, E21);
    directedGraph.putEdgeValue(N1, N3, E13); // only incident to one node in nodeSubset
    directedGraph.putEdgeValue(N4, N4, E44);
    directedGraph.putEdgeValue(5, 6, "5-6"); // not incident to any node in nodeSubset

    MutableValueGraph<Integer, String> expectedSubgraph =
        ValueGraphBuilder.directed().allowsSelfLoops(true).build();
    expectedSubgraph.putEdgeValue(N1, N2, E12);
    expectedSubgraph.putEdgeValue(N2, N1, E21);
    expectedSubgraph.putEdgeValue(N4, N4, E44);

    assertThat(inducedSubgraph(directedGraph, nodeSubset)).isEqualTo(expectedSubgraph);
  }

  @Test
  public void inducedSubgraph_network() {
    Set<Integer> nodeSubset = ImmutableSet.of(N1, N2, N4);

    MutableNetwork<Integer, String> directedGraph =
        NetworkBuilder.directed().allowsSelfLoops(true).build();
    directedGraph.addEdge(N1, N2, E12);
    directedGraph.addEdge(N2, N1, E21);
    directedGraph.addEdge(N1, N3, E13); // only incident to one node in nodeSubset
    directedGraph.addEdge(N4, N4, E44);
    directedGraph.addEdge(5, 6, "5-6"); // not incident to any node in nodeSubset

    MutableNetwork<Integer, String> expectedSubgraph =
        NetworkBuilder.directed().allowsSelfLoops(true).build();
    expectedSubgraph.addEdge(N1, N2, E12);
    expectedSubgraph.addEdge(N2, N1, E21);
    expectedSubgraph.addEdge(N4, N4, E44);

    assertThat(inducedSubgraph(directedGraph, nodeSubset)).isEqualTo(expectedSubgraph);
  }

  @Test
  public void inducedSubgraph_nodeNotInGraph() {
    MutableNetwork<Integer, String> undirectedGraph = NetworkBuilder.undirected().build();

    assertThrows(
        IllegalArgumentException.class,
        () -> inducedSubgraph(undirectedGraph, ImmutableSet.of(N1)));
  }

  @Test
  public void copyOf_nullArgument() {
    assertThrows(NullPointerException.class, () -> copyOf((Graph<?>) null));
  }

  @Test
  public void copyOf_directedGraph() {
    Graph<Integer> directedGraph = buildDirectedGraph();

    Graph<Integer> copy = copyOf(directedGraph);
    assertThat(copy).isEqualTo(directedGraph);
  }

  @Test
  public void copyOf_undirectedGraph() {
    Graph<Integer> undirectedGraph = buildUndirectedGraph();

    Graph<Integer> copy = copyOf(undirectedGraph);
    assertThat(copy).isEqualTo(undirectedGraph);
  }

  @Test
  public void copyOf_directedValueGraph() {
    ValueGraph<Integer, String> directedGraph = buildDirectedValueGraph();

    ValueGraph<Integer, String> copy = copyOf(directedGraph);
    assertThat(copy).isEqualTo(directedGraph);
  }

  @Test
  public void copyOf_undirectedValueGraph() {
    ValueGraph<Integer, String> undirectedGraph = buildUndirectedValueGraph();

    ValueGraph<Integer, String> copy = copyOf(undirectedGraph);
    assertThat(copy).isEqualTo(undirectedGraph);
  }

  @Test
  public void copyOf_directedNetwork() {
    Network<Integer, String> directedGraph = buildDirectedNetwork();

    Network<Integer, String> copy = copyOf(directedGraph);
    assertThat(copy).isEqualTo(directedGraph);
  }

  @Test
  public void copyOf_undirectedNetwork() {
    Network<Integer, String> undirectedGraph = buildUndirectedNetwork();

    Network<Integer, String> copy = copyOf(undirectedGraph);
    assertThat(copy).isEqualTo(undirectedGraph);
  }

  // Graph creation tests

  @Test
  public void createDirected() {
    MutableNetwork<Integer, String> directedGraph = NetworkBuilder.directed().build();
    assertThat(directedGraph.nodes()).isEmpty();
    assertThat(directedGraph.edges()).isEmpty();
    assertThat(directedGraph.addEdge(N1, N2, E12)).isTrue();
    assertThat(directedGraph.edgesConnecting(N1, N2)).isEqualTo(ImmutableSet.of(E12));
    assertThat(directedGraph.edgesConnecting(N2, N1)).isEmpty();

    // By default, parallel edges are not allowed.
    IllegalArgumentException e =
        assertThrows(IllegalArgumentException.class, () -> directedGraph.addEdge(N1, N2, E12_A));
    assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE);

    // By default, self-loop edges are not allowed.
    e = assertThrows(IllegalArgumentException.class, () -> directedGraph.addEdge(N1, N1, E11));
    assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP);
  }

  @Test
  public void createUndirected() {
    MutableNetwork<Integer, String> undirectedGraph = NetworkBuilder.undirected().build();
    assertThat(undirectedGraph.nodes()).isEmpty();
    assertThat(undirectedGraph.edges()).isEmpty();
    assertThat(undirectedGraph.addEdge(N1, N2, E12)).isTrue();
    assertThat(undirectedGraph.edgesConnecting(N1, N2)).isEqualTo(ImmutableSet.of(E12));
    assertThat(undirectedGraph.edgesConnecting(N2, N1)).isEqualTo(ImmutableSet.of(E12));

    // By default, parallel edges are not allowed.
    IllegalArgumentException e =
        assertThrows(IllegalArgumentException.class, () -> undirectedGraph.addEdge(N1, N2, E12_A));
    assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE);
    e = assertThrows(IllegalArgumentException.class, () -> undirectedGraph.addEdge(N2, N1, E21));
    assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE);

    // By default, self-loop edges are not allowed.
    e = assertThrows(IllegalArgumentException.class, () -> undirectedGraph.addEdge(N1, N1, E11));
    assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP);
  }

  @Test
  public void createDirected_multigraph() {
    MutableNetwork<Integer, String> directedMultigraph =
        NetworkBuilder.directed().allowsParallelEdges(true).build();
    assertThat(directedMultigraph.addEdge(N1, N2, E12)).isTrue();
    assertThat(directedMultigraph.addEdge(N1, N2, E12_A)).isTrue();
    assertThat(directedMultigraph.edgesConnecting(N1, N2)).isEqualTo(ImmutableSet.of(E12, E12_A));
    assertThat(directedMultigraph.edgesConnecting(N2, N1)).isEmpty();
  }

  @Test
  public void createUndirected_multigraph() {
    MutableNetwork<Integer, String> undirectedMultigraph =
        NetworkBuilder.undirected().allowsParallelEdges(true).build();
    assertThat(undirectedMultigraph.addEdge(N1, N2, E12)).isTrue();
    assertThat(undirectedMultigraph.addEdge(N1, N2, E12_A)).isTrue();
    assertThat(undirectedMultigraph.addEdge(N2, N1, E21)).isTrue();
    assertThat(undirectedMultigraph.edgesConnecting(N1, N2))
        .isEqualTo(ImmutableSet.of(E12, E12_A, E21));
  }

  @Test
  public void createDirected_expectedNodeCount() {
    MutableNetwork<Integer, String> directedGraph =
        NetworkBuilder.directed().expectedNodeCount(NODE_COUNT).build();
    assertThat(directedGraph.addEdge(N1, N2, E12)).isTrue();
    assertThat(directedGraph.edgesConnecting(N1, N2)).isEqualTo(ImmutableSet.of(E12));
    assertThat(directedGraph.edgesConnecting(N2, N1)).isEmpty();
  }

  @Test
  public void createUndirected_expectedNodeCount() {
    MutableNetwork<Integer, String> undirectedGraph =
        NetworkBuilder.undirected().expectedNodeCount(NODE_COUNT).build();
    assertThat(undirectedGraph.addEdge(N1, N2, E12)).isTrue();
    assertThat(undirectedGraph.edgesConnecting(N1, N2)).isEqualTo(ImmutableSet.of(E12));
    assertThat(undirectedGraph.edgesConnecting(N2, N1)).isEqualTo(ImmutableSet.of(E12));
  }

  @Test
  public void builder_expectedNodeCount_negative() {
    IllegalArgumentException e =
        assertThrows(
            IllegalArgumentException.class, () -> NetworkBuilder.directed().expectedNodeCount(-1));
    assertThat(e).hasMessageThat().contains(ERROR_NEGATIVE_COUNT);
  }

  @Test
  public void createDirected_expectedEdgeCount() {
    MutableNetwork<Integer, String> directedGraph =
        NetworkBuilder.directed().expectedEdgeCount(EDGE_COUNT).build();
    assertThat(directedGraph.addEdge(N1, N2, E12)).isTrue();
    assertThat(directedGraph.edgesConnecting(N1, N2)).isEqualTo(ImmutableSet.of(E12));
    assertThat(directedGraph.edgesConnecting(N2, N1)).isEmpty();
  }

  @Test
  public void createUndirected_expectedEdgeCount() {
    MutableNetwork<Integer, String> undirectedGraph =
        NetworkBuilder.undirected().expectedEdgeCount(EDGE_COUNT).build();
    assertThat(undirectedGraph.addEdge(N1, N2, E12)).isTrue();
    assertThat(undirectedGraph.edgesConnecting(N1, N2)).isEqualTo(ImmutableSet.of(E12));
    assertThat(undirectedGraph.edgesConnecting(N2, N1)).isEqualTo(ImmutableSet.of(E12));
  }

  @Test
  public void builder_expectedEdgeCount_negative() {
    IllegalArgumentException e =
        assertThrows(
            IllegalArgumentException.class, () -> NetworkBuilder.directed().expectedEdgeCount(-1));
    assertThat(e).hasMessageThat().contains(ERROR_NEGATIVE_COUNT);
  }

  private static <N> void checkTransitiveClosure(Graph<N> originalGraph, Graph<N> expectedClosure) {
    for (N node : originalGraph.nodes()) {
      assertThat(reachableNodes(originalGraph, node)).isEqualTo(expectedClosure.successors(node));
    }
    assertThat(transitiveClosure(originalGraph)).isEqualTo(expectedClosure);
  }

  private static MutableGraph<Integer> buildDirectedGraph() {
    MutableGraph<Integer> directedGraph = GraphBuilder.directed().allowsSelfLoops(true).build();
    directedGraph.putEdge(N1, N1);
    directedGraph.putEdge(N1, N2);
    directedGraph.putEdge(N2, N1);

    return directedGraph;
  }

  private static MutableGraph<Integer> buildUndirectedGraph() {
    MutableGraph<Integer> undirectedGraph = GraphBuilder.undirected().allowsSelfLoops(true).build();
    undirectedGraph.putEdge(N1, N1);
    undirectedGraph.putEdge(N1, N2);
    undirectedGraph.putEdge(N2, N1);

    return undirectedGraph;
  }

  private static MutableValueGraph<Integer, String> buildDirectedValueGraph() {
    MutableValueGraph<Integer, String> directedGraph =
        ValueGraphBuilder.directed().allowsSelfLoops(true).build();
    directedGraph.putEdgeValue(N1, N1, E11);
    directedGraph.putEdgeValue(N1, N2, E12);
    directedGraph.putEdgeValue(N2, N1, E21);

    return directedGraph;
  }

  private static MutableValueGraph<Integer, String> buildUndirectedValueGraph() {
    MutableValueGraph<Integer, String> undirectedGraph =
        ValueGraphBuilder.undirected().allowsSelfLoops(true).build();
    undirectedGraph.putEdgeValue(N1, N1, E11);
    undirectedGraph.putEdgeValue(N1, N2, E12);
    undirectedGraph.putEdgeValue(N2, N1, E21); // overwrites E12

    return undirectedGraph;
  }

  private static MutableNetwork<Integer, String> buildDirectedNetwork() {
    MutableNetwork<Integer, String> directedGraph =
        NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build();
    directedGraph.addEdge(N1, N1, E11);
    directedGraph.addEdge(N1, N2, E12);
    directedGraph.addEdge(N1, N1, E11_A);
    directedGraph.addEdge(N1, N2, E12_A);
    directedGraph.addEdge(N2, N1, E21);

    return directedGraph;
  }

  private static MutableNetwork<Integer, String> buildUndirectedNetwork() {
    MutableNetwork<Integer, String> undirectedGraph =
        NetworkBuilder.undirected().allowsParallelEdges(true).allowsSelfLoops(true).build();
    undirectedGraph.addEdge(N1, N1, E11);
    undirectedGraph.addEdge(N1, N2, E12);
    undirectedGraph.addEdge(N1, N1, E11_A);
    undirectedGraph.addEdge(N1, N2, E12_A);
    undirectedGraph.addEdge(N2, N1, E21);

    return undirectedGraph;
  }
}
