/**
   * Tests that adding neighbors that are already contained in the routing via addNeighbor does not
   * affect the table size or any existing routes.
   */
  @Test
  public void testReAddNeighbor() throws Exception {
    RandomRoutingTable rt = new BasicRandomRoutingTable();

    // create a set of neighbors and add them to the table
    List<TrustGraphNodeId> neighbors = createNeighbors(100);
    rt.addNeighbors(neighbors);

    // snapshot the state of the table for later comparison
    RandomRoutingTable.Snapshot snapshot1 = rt.snapshot();
    Map<TrustGraphNodeId, TrustGraphNodeId> routes1 = snapshot1.getRoutes();
    assertTrue(routes1.size() == neighbors.size());
    assertTrue(BasicRandomRoutingTable.isValidSnapshot(snapshot1));

    // re-add the neighbors in random order
    LinkedList<TrustGraphNodeId> shuffled = new LinkedList<TrustGraphNodeId>(neighbors);
    Collections.shuffle(shuffled);
    for (TrustGraphNodeId n : shuffled) {
      rt.addNeighbor(n);

      /* snapshot the table after adding and compare to
       * the previous state.  It should be completely
       * unchanged since all neighbors were already in
       * the table.
       */
      RandomRoutingTable.Snapshot snapshot2 = rt.snapshot();
      assertTrue(snapshotsAreEquivalent(snapshot1, snapshot2));
    }
  }
  /** Tests that getNextHop and snapshot agree. */
  @Test
  public void testSnapshotVsGetNextHop() throws Exception {

    RandomRoutingTable rt = new BasicRandomRoutingTable();

    // add a group of neighbors to the routing table
    List<TrustGraphNodeId> neighbors = createNeighbors(1000);
    rt.addNeighbors(neighbors);

    // capture a snapshot of the routing
    RandomRoutingTable.Snapshot snapshot = rt.snapshot();
    Map<TrustGraphNodeId, TrustGraphNodeId> routes = snapshot.getRoutes();
    assertTrue(routes.size() == neighbors.size());
    assertTrue(BasicRandomRoutingTable.isValidSnapshot(snapshot));

    /* make sure that each mapping in the snapshot matches up with
     * the result of getNextHop and vice versa.
     */
    for (Map.Entry<TrustGraphNodeId, TrustGraphNodeId> e : routes.entrySet()) {
      assertTrue(rt.getNextHop(e.getKey()).equals(e.getValue()));
    }
    for (TrustGraphNodeId n : neighbors) {
      assertTrue(rt.getNextHop(n).equals(routes.get(n)));
    }
  }
  /** Test that the contains method reflects the set of neighbors added to a BasicRoutingTable. */
  @Test
  public void testContains() throws Exception {
    RandomRoutingTable rt = new BasicRandomRoutingTable();

    // create a set of neighbors and add them to the table
    List<TrustGraphNodeId> neighbors = createNeighbors(1000);
    rt.addNeighbors(neighbors);

    // some neighbors that won't be added
    List<TrustGraphNodeId> notAdded = createNeighbors(3);

    /* test that contains() is true for all neighbors in the
     * list that was added.
     */
    for (TrustGraphNodeId n : neighbors) {
      assertTrue(rt.contains(n));
    }

    /* test that contains() is false for some neighors that were
     * not added
     */
    for (TrustGraphNodeId n : notAdded) {
      assertFalse(rt.contains(n));
    }
  }
  /** Tests that the single add neighbor method addNeighbor creates a valid set of routes. */
  @Test
  public void testAddNeighborBasic() throws Exception {
    RandomRoutingTable rt = new BasicRandomRoutingTable();

    /* create a large group of neigbors and add them
     * individually.
     */
    Collection<TrustGraphNodeId> neighbors = createNeighbors(1000);
    for (TrustGraphNodeId n : neighbors) {
      rt.addNeighbor(n);
    }

    // there should be a route for every neighbor added
    for (TrustGraphNodeId n : neighbors) {
      assertTrue(rt.contains(n));
    }

    // every neighbor should be routed to someone else
    for (TrustGraphNodeId n : neighbors) {
      TrustGraphNodeId next = rt.getNextHop(n);
      assertTrue(next != null && !next.equals(n));
    }

    // A snapshot of the resulting routing table should validate
    RandomRoutingTable.Snapshot snapshot = rt.snapshot();
    Map<TrustGraphNodeId, TrustGraphNodeId> routes = snapshot.getRoutes();
    assertTrue(routes.size() == neighbors.size());
    assertTrue(BasicRandomRoutingTable.isValidSnapshot(snapshot));

    // every neighbor should be in the snapshot
    for (TrustGraphNodeId n : neighbors) {
      assertTrue(routes.containsKey(n));
      assertTrue(routes.containsValue(n));
    }
  }
  /** Tests that routes are created correctly when snapshots are loaded. */
  @Test
  public void testSnapshotLoad() throws Exception {

    // create a set of neighbors and add them to the table
    List<TrustGraphNodeId> neighbors = createNeighbors(1000);

    Map<TrustGraphNodeId, TrustGraphNodeId> routesIn =
        new HashMap<TrustGraphNodeId, TrustGraphNodeId>();

    for (int i = 0; i < neighbors.size(); i++) {
      // map each neigbor to the next, circularly
      routesIn.put(neighbors.get(i), neighbors.get((i + 1) % neighbors.size()));
    }

    // create a routing table using the constructed snapshot
    RandomRoutingTable.Snapshot snapshotIn =
        new BasicRandomRoutingTable.Snapshot(routesIn, new ArrayList(routesIn.keySet()));
    RandomRoutingTable rt = new BasicRandomRoutingTable(snapshotIn);
    Map<TrustGraphNodeId, TrustGraphNodeId> snapshotOut = rt.snapshot().getRoutes();

    /* verify each route that we created appears in the routing
     * according to getNextHop and a snapshot.
     */
    for (Map.Entry<TrustGraphNodeId, TrustGraphNodeId> e : routesIn.entrySet()) {
      TrustGraphNodeId key = e.getKey();
      TrustGraphNodeId value = e.getValue();
      // getNextHop(key) == value
      assertTrue(rt.getNextHop(key).equals(value));
      // snapshot[key] == value
      assertTrue(snapshotOut.get(key).equals(value));
    }
  }
  /** Tests the size() method of BasicRandomRoutingTable */
  @Test
  public void testSize() throws Exception {
    RandomRoutingTable rt = new BasicRandomRoutingTable();

    // add a random number of nodes between 100 and 400
    int number = 100 + new Random().nextInt(400);
    List<TrustGraphNodeId> neighbors = createNeighbors(number);
    assertTrue(number == neighbors.size());

    /* check that the routing table size is the same as the
     * number of neighbors added
     */
    rt.addNeighbors(neighbors);
    assertTrue(number == rt.size());
    assertTrue(rt.size() == rt.snapshot().getRoutes().size());
  }
  /**
   * Test that getOrderedNeighbors reflects the set of neighbors added to a BasicRandomRoutingTable.
   */
  @Test
  public void testGetOrderedNeighborsBasic() throws Exception {
    RandomRoutingTable rt = new BasicRandomRoutingTable();

    // create a set of neighbors and add them to the table
    List<TrustGraphNodeId> neighbors = createNeighbors(1000);
    rt.addNeighbors(neighbors);

    // get all the neighors
    List<TrustGraphNodeId> allNeighbors = rt.getOrderedNeighbors();

    assertTrue(allNeighbors.size() == neighbors.size());
    // test that all added neighors appear in the allNeighbors list
    for (TrustGraphNodeId n : neighbors) {
      assertTrue(allNeighbors.contains(n));
    }
  }
  /** Tests that adding neighbors via the single add method fixes self mappings. */
  @Test
  public void testAddNeighborFixesSelfMapping() {
    RandomRoutingTable rt = new BasicRandomRoutingTable();
    RandomRoutingTable.Snapshot snapshot;
    Map<TrustGraphNodeId, TrustGraphNodeId> routes;

    // create a set of neighbors to add
    List<TrustGraphNodeId> neighbors = createNeighbors(4);

    // add the first neighbor, it should just contain
    // the first neighbor mapped to itself
    TrustGraphNodeId first = neighbors.get(0);
    rt.addNeighbor(first);
    assertTrue(rt.getNextHop(first).equals(first));
    snapshot = rt.snapshot();
    routes = snapshot.getRoutes();
    assertTrue(routes.size() == 1);
    assertTrue(BasicRandomRoutingTable.isValidSnapshot(snapshot));

    // add the other neighbors.  This should immediately fix
    // the self routing.
    for (int i = 1; i < neighbors.size(); i++) {
      rt.addNeighbor(neighbors.get(i));
      assertTrue(!rt.getNextHop(first).equals(first));
      snapshot = rt.snapshot();
      routes = snapshot.getRoutes();
      assertTrue(routes.size() == 1 + i);
      assertTrue(BasicRandomRoutingTable.isValidSnapshot(snapshot));
    }
  }
  /**
   * Tests that using the bulk addNeighbors function does not disrupt more than one previous route.
   */
  @Test
  public void testAddNeighborsPreservesRoutes() throws Exception {

    RandomRoutingTable rt = new BasicRandomRoutingTable();

    /* create a group of neighbors and add them to the
     * routing table.  A snapshot of the routing is created
     * so that it can be compared to the routing table
     * after adding additional neighbors.
     */
    Collection<TrustGraphNodeId> originalNeighbors = createNeighbors(500);
    rt.addNeighbors(originalNeighbors);
    RandomRoutingTable.Snapshot snapshot = rt.snapshot();
    Map<TrustGraphNodeId, TrustGraphNodeId> routes = snapshot.getRoutes();

    /* count tracks how many neighbors have been added
     * to the routing so far.
     */
    int count = originalNeighbors.size();

    /* for a few randomly chosen sizes, bulk add
     * neighbors and check the properties
     * of the routing.
     */
    Random rng = new Random();
    for (int i = 0; i < 10; i++) {
      int newCount = 100 + rng.nextInt(400);
      Collection<TrustGraphNodeId> newNeighbors = createNeighbors(newCount);
      rt.addNeighbors(newNeighbors);

      /* check that at most one route has changed by counting
       * the number of routes that are the same as the last
       * run.
       */
      int sameCount = 0;
      for (Map.Entry<TrustGraphNodeId, TrustGraphNodeId> e : routes.entrySet()) {
        if (rt.getNextHop(e.getKey()).equals(e.getValue())) {
          sameCount += 1;
        }
      }
      assertTrue(routes.size() - sameCount <= 1);

      // check that all the new neighors are also routed
      for (TrustGraphNodeId n : newNeighbors) {
        assertTrue(rt.contains(n));
      }

      /* take a new snapshot and ensure that it
       * has the correct size and is valid
       */
      snapshot = rt.snapshot();
      routes = snapshot.getRoutes();
      count += newCount;
      assertTrue(routes.size() == count);
      assertTrue(BasicRandomRoutingTable.isValidSnapshot(snapshot));
    }
  }
  /**
   * Tests that the output of snapshot can be used to construct a routing table and that the routing
   * is preserved.
   */
  @Test
  public void testSnapshotReload() throws Exception {
    RandomRoutingTable rt1 = new BasicRandomRoutingTable();

    // create a set of neighbors and add them to the table
    List<TrustGraphNodeId> neighbors = createNeighbors(1000);
    rt1.addNeighbors(neighbors);

    RandomRoutingTable.Snapshot snapshotIn = rt1.snapshot();

    // create a routing table using the snapshot
    RandomRoutingTable rt2 = new BasicRandomRoutingTable(snapshotIn);
    RandomRoutingTable.Snapshot snapshotOut = rt2.snapshot();

    /* verify each route that we created appears in the routing
     * according to getNextHop and a snapshot.
     */
    for (Map.Entry<TrustGraphNodeId, TrustGraphNodeId> e : snapshotIn.getRoutes().entrySet()) {
      TrustGraphNodeId key = e.getKey();
      TrustGraphNodeId value = e.getValue();
      // getNextHop(key) == value
      assertTrue(rt2.getNextHop(key).equals(value));
      // snapshot[key] == value
      assertTrue(snapshotOut.getRoutes().get(key).equals(value));
    }
  }
  /** simple test of the validity of the snapshot method. */
  @Test
  public void testSnapshot() throws Exception {
    RandomRoutingTable rt = new BasicRandomRoutingTable();

    // create a set of neighbors and add them to the table
    List<TrustGraphNodeId> neighbors = createNeighbors(1000);
    rt.addNeighbors(neighbors);

    RandomRoutingTable.Snapshot snapshot = rt.snapshot();
    Map<TrustGraphNodeId, TrustGraphNodeId> routes = snapshot.getRoutes();

    // make sure it meets the table's own validity criteria
    assertTrue(BasicRandomRoutingTable.isValidSnapshot(snapshot));

    /* make sure everything we added is in the snapshot
     * and only those things are present
     */
    assertTrue(routes.size() == neighbors.size());
    for (TrustGraphNodeId n : neighbors) {
      assertTrue(routes.containsKey(n));
      assertTrue(routes.containsValue(n));
    }
  }
  /** Tests that a call to addNeighbors fixes any self-mapped neighbors. */
  @Test
  public void testAddNeighborsFixesSelfMapping() throws Exception {

    /* this tests runs with a few different low size lists being passed
     * to the addNeighbors method to make sure that degenerate cases
     * like a list of size 1 are handled correctly.
     */
    for (int neighborCount = 1; neighborCount < 5; neighborCount++) {

      // create a new routing table
      RandomRoutingTable rt = new BasicRandomRoutingTable();
      RandomRoutingTable.Snapshot snapshot;
      Map<TrustGraphNodeId, TrustGraphNodeId> routes;

      // create a lone neighbor and a list of other neighbors
      TrustGraphNodeId loner = createNeighbors(1).get(0);
      Collection<TrustGraphNodeId> neighbors = createNeighbors(neighborCount);

      // add the single neighbor and make sure it is mapped to itself
      rt.addNeighbor(loner);
      snapshot = rt.snapshot();
      routes = snapshot.getRoutes();
      assertTrue(routes.size() == 1);
      assertTrue(routes.get(loner).equals(loner));

      /* add the rest of the neighbors and make sure that the loner
       * is no longer mapped to iself and that otherwise the
       * routing is valid.
       */
      rt.addNeighbors(neighbors);
      snapshot = rt.snapshot();
      routes = snapshot.getRoutes();
      assertTrue(routes.size() == 1 + neighbors.size());
      assertTrue(!routes.get(loner).equals(loner));
      assertTrue(BasicRandomRoutingTable.isValidSnapshot(snapshot));
    }
  }
  /**
   * tests that calling RemoveNeighbors removes only the intended neighbors and preserves valid
   * routing.
   */
  @Test
  public void testRemoveNeighbors() throws Exception {
    RandomRoutingTable rt = new BasicRandomRoutingTable();

    // create a set of neighbors to test with
    List<TrustGraphNodeId> neighbors = createNeighbors(120);
    rt.addNeighbors(neighbors);

    // make sure everyone is in the table to start with
    for (TrustGraphNodeId n : neighbors) {
      assertTrue(rt.contains(n));
    }

    /* remove the 120 neighbors in random order, in increasingly large
     * chunks from 1 to 15
     */
    LinkedList<TrustGraphNodeId> shuffled = new LinkedList(neighbors);
    Collections.shuffle(shuffled);
    for (int i = 1; i <= 15; i++) {
      /* pop i things from the shuffled list into the list remove,
       * then remove them in a bulk operation.
       */
      LinkedList<TrustGraphNodeId> remove = new LinkedList();
      for (int j = 0; j < i; j++) {
        remove.push(shuffled.pop());
      }
      rt.removeNeighbors(remove);

      // make sure they're gone.
      for (TrustGraphNodeId n : remove) {
        assertFalse(rt.contains(n));
      }

      // make sure everything else is still there.
      for (TrustGraphNodeId n : shuffled) {
        assertTrue(rt.contains(n));
      }

      // make sure the routing is still valid in total.
      assertTrue(BasicRandomRoutingTable.isValidSnapshot(rt.snapshot()));
    }

    // make sure everything was eventually removed
    assertTrue(rt.isEmpty());
  }
  /**
   * Tests that different seqential calls to the AddNeighbor with the same inputs produce different
   * routings, ie "random"
   */
  @Test
  public void testAddNeighborIsDynamic() {
    // create two routing tables
    RandomRoutingTable rt1 = new BasicRandomRoutingTable();
    RandomRoutingTable rt2 = new BasicRandomRoutingTable();

    // create a set of neighbors to add
    List<TrustGraphNodeId> neighbors = createNeighbors(1000);
    RandomRoutingTable.Snapshot snapshot1;
    RandomRoutingTable.Snapshot snapshot2;
    Map<TrustGraphNodeId, TrustGraphNodeId> routes1;
    Map<TrustGraphNodeId, TrustGraphNodeId> routes2;

    /* add the same thing to both tables in the same order,
     * and snapshot them
     */
    for (TrustGraphNodeId n : neighbors) {
      rt1.addNeighbor(n);
    }
    for (TrustGraphNodeId n : neighbors) {
      rt2.addNeighbor(n);
    }
    snapshot1 = rt1.snapshot();
    snapshot2 = rt2.snapshot();
    routes1 = snapshot1.getRoutes();
    routes2 = snapshot2.getRoutes();

    // the snapshots should both be valid, and
    // contain the same neighbors, but different routes (whp)
    assertTrue(BasicRandomRoutingTable.isValidSnapshot(snapshot1));
    assertTrue(BasicRandomRoutingTable.isValidSnapshot(snapshot2));
    for (TrustGraphNodeId n : neighbors) {
      assertTrue(routes1.containsKey(n));
      assertTrue(routes2.containsKey(n));
    }

    /* test for at least one differing route
     * (should succeed with very high probability)
     */
    boolean mappingIsDifferent = false;
    for (TrustGraphNodeId n : neighbors) {
      // if the routing for this neighbor in
      // snapshot1 is different than snapshot2, the
      // test is a success.
      if (!routes1.get(n).equals(routes2.get(n))) {
        mappingIsDifferent = true;
        break;
      }
    }
    assertTrue(mappingIsDifferent);
  }
  /**
   * Tests that removeNeighbor only removes the intended neighbor from the routing and preserves a
   * valid routing.
   */
  @Test
  public void testRemoveNeighbor() throws Exception {
    RandomRoutingTable rt = new BasicRandomRoutingTable();

    // create a set of neighbors to test with
    List<TrustGraphNodeId> neighbors = createNeighbors(100);
    rt.addNeighbors(neighbors);

    // make sure everyone is in the table to start with
    for (TrustGraphNodeId n : neighbors) {
      assertTrue(rt.contains(n));
    }

    /* remove the neighbors in random order. Check that the
     * routing remains valid, the expected item has been removed
     * and that all other items are still present.
     */
    LinkedList<TrustGraphNodeId> shuffled = new LinkedList(neighbors);
    Collections.shuffle(shuffled);
    for (int i = 0; i < neighbors.size(); i++) {
      // select a neighbor to remove and remove it
      TrustGraphNodeId remove = shuffled.pop();
      rt.removeNeighbor(remove);

      // make sure it's gone
      assertFalse(rt.contains(remove));

      // make sure everything else is still there.
      for (TrustGraphNodeId n : shuffled) {
        assertTrue(rt.contains(n));
      }

      // make sure the routing is still valid in total.
      assertTrue(BasicRandomRoutingTable.isValidSnapshot(rt.snapshot()));
    }

    // make sure everything was eventually removed
    assertTrue(rt.isEmpty());
  }
  /** Tests the isEmpty() method of BasicRandomRoutingTable */
  @Test
  public void testIsEmpty() throws Exception {
    RandomRoutingTable rt = new BasicRandomRoutingTable();

    // initially emtpy
    assertTrue(rt.isEmpty());

    // add a neighbor -> no longer empty
    TrustGraphNodeId neighbor = createNeighbors(1).get(0);
    rt.addNeighbor(neighbor);
    assertTrue(!rt.isEmpty());

    // remove the neighbor -> empty again
    rt.removeNeighbor(neighbor);
    assertTrue(rt.isEmpty());
  }
  /** Tests the clear() method */
  @Test
  public void testClear() throws Exception {
    RandomRoutingTable rt = new BasicRandomRoutingTable();

    // create a set of neighbors and add them to the table
    List<TrustGraphNodeId> neighbors = createNeighbors(100);
    rt.addNeighbors(neighbors);

    // verify that they're all there
    for (TrustGraphNodeId n : neighbors) {
      assertTrue(rt.contains(n));
    }

    // clear the table
    rt.clear();

    // verify that they're all not there
    for (TrustGraphNodeId n : neighbors) {
      assertFalse(rt.contains(n));
    }
    assertTrue(rt.isEmpty());
  }
  /** Tests adding and removing neighbors in random order repeatedly emptying out the table. */
  @Test
  public void testAddRemoveEmpty() throws Exception {
    RandomRoutingTable rt = new BasicRandomRoutingTable();

    for (int i = 0; i < 10; i++) {
      // create i neighbors
      LinkedList<TrustGraphNodeId> neighbors = new LinkedList<TrustGraphNodeId>(createNeighbors(i));

      // add them, then remove in order
      for (TrustGraphNodeId n : neighbors) {
        rt.addNeighbor(n);
      }
      for (TrustGraphNodeId n : neighbors) {
        rt.removeNeighbor(n);
        assertTrue(BasicRandomRoutingTable.isValidSnapshot(rt.snapshot()));
      }
      assertTrue(rt.isEmpty());

      // add them and remove them in reverse order
      for (TrustGraphNodeId n : neighbors) {
        rt.addNeighbor(n);
      }
      Collections.reverse(neighbors);
      for (TrustGraphNodeId n : neighbors) {
        rt.removeNeighbor(n);
        assertTrue(BasicRandomRoutingTable.isValidSnapshot(rt.snapshot()));
      }
      assertTrue(rt.isEmpty());

      // add them and remove them in random order
      for (TrustGraphNodeId n : neighbors) {
        rt.addNeighbor(n);
      }
      Collections.shuffle(neighbors);
      for (TrustGraphNodeId n : neighbors) {
        rt.removeNeighbor(n);
        assertTrue(BasicRandomRoutingTable.isValidSnapshot(rt.snapshot()));
      }
      assertTrue(rt.isEmpty());
    }
  }