// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import "package:test/test.dart";

import "package:collection/collection.dart";

void main() {
  group("mapMap()", () {
    test("with an empty map returns an empty map", () {
      expect(
          mapMap({},
              key: expectAsync2((_, __) {}, count: 0),
              value: expectAsync2((_, __) {}, count: 0)),
          isEmpty);
    });

    test("with no callbacks, returns a copy of the map", () {
      var map = {"foo": 1, "bar": 2};
      var result = mapMap(map);
      expect(result, equals({"foo": 1, "bar": 2}));

      // The resulting map should be a copy.
      result["foo"] = 3;
      expect(map, equals({"foo": 1, "bar": 2}));
    });

    test("maps the map's keys", () {
      expect(mapMap({"foo": 1, "bar": 2}, key: (key, value) => key[value]),
          equals({"o": 1, "r": 2}));
    });

    test("maps the map's values", () {
      expect(mapMap({"foo": 1, "bar": 2}, value: (key, value) => key[value]),
          equals({"foo": "o", "bar": "r"}));
    });

    test("maps both the map's keys and values", () {
      expect(
          mapMap({"foo": 1, "bar": 2},
              key: (key, value) => "$key$value",
              value: (key, value) => key[value]),
          equals({"foo1": "o", "bar2": "r"}));
    });
  });

  group("mergeMaps()", () {
    test("with empty maps returns an empty map", () {
      expect(mergeMaps({}, {}, value: expectAsync2((_, __) {}, count: 0)),
          isEmpty);
    });

    test("returns a map with all values in both input maps", () {
      expect(mergeMaps({"foo": 1, "bar": 2}, {"baz": 3, "qux": 4}),
          equals({"foo": 1, "bar": 2, "baz": 3, "qux": 4}));
    });

    test("the second map's values win by default", () {
      expect(mergeMaps({"foo": 1, "bar": 2}, {"bar": 3, "baz": 4}),
          equals({"foo": 1, "bar": 3, "baz": 4}));
    });

    test("uses the callback to merge values", () {
      expect(
          mergeMaps({"foo": 1, "bar": 2}, {"bar": 3, "baz": 4},
              value: (value1, value2) => value1 + value2),
          equals({"foo": 1, "bar": 5, "baz": 4}));
    });
  });

  group("groupBy()", () {
    test("returns an empty map for an empty iterable", () {
      expect(groupBy([], expectAsync1((_) {}, count: 0)), isEmpty);
    });

    test("groups elements by the function's return value", () {
      expect(
          groupBy(["foo", "bar", "baz", "bop", "qux"], (string) => string[1]),
          equals({
            "o": ["foo", "bop"],
            "a": ["bar", "baz"],
            "u": ["qux"]
          }));
    });
  });

  group("minBy()", () {
    test("returns null for an empty iterable", () {
      expect(
          minBy([], expectAsync1((_) {}, count: 0),
              compare: expectAsync2((_, __) {}, count: 0)),
          isNull);
    });

    test(
        "returns the element for which the ordering function returns the "
        "smallest value", () {
      expect(
          minBy([
            {"foo": 3},
            {"foo": 5},
            {"foo": 4},
            {"foo": 1},
            {"foo": 2}
          ], (map) => map["foo"]),
          equals({"foo": 1}));
    });

    test("uses a custom comparator if provided", () {
      expect(
          minBy<Map<String, int>, Map<String, int>>([
            {"foo": 3},
            {"foo": 5},
            {"foo": 4},
            {"foo": 1},
            {"foo": 2}
          ], (map) => map,
              compare: (map1, map2) => map1["foo"].compareTo(map2["foo"])),
          equals({"foo": 1}));
    });
  });

  group("maxBy()", () {
    test("returns null for an empty iterable", () {
      expect(
          maxBy([], expectAsync1((_) {}, count: 0),
              compare: expectAsync2((_, __) {}, count: 0)),
          isNull);
    });

    test(
        "returns the element for which the ordering function returns the "
        "largest value", () {
      expect(
          maxBy([
            {"foo": 3},
            {"foo": 5},
            {"foo": 4},
            {"foo": 1},
            {"foo": 2}
          ], (map) => map["foo"]),
          equals({"foo": 5}));
    });

    test("uses a custom comparator if provided", () {
      expect(
          maxBy<Map<String, int>, Map<String, int>>([
            {"foo": 3},
            {"foo": 5},
            {"foo": 4},
            {"foo": 1},
            {"foo": 2}
          ], (map) => map,
              compare: (map1, map2) => map1["foo"].compareTo(map2["foo"])),
          equals({"foo": 5}));
    });
  });

  group("transitiveClosure()", () {
    test("returns an empty map for an empty graph", () {
      expect(transitiveClosure({}), isEmpty);
    });

    test("returns the input when there are no transitive connections", () {
      expect(
          transitiveClosure({
            "foo": ["bar"],
            "bar": [],
            "bang": ["qux", "zap"],
            "qux": [],
            "zap": []
          }),
          equals({
            "foo": ["bar"],
            "bar": [],
            "bang": ["qux", "zap"],
            "qux": [],
            "zap": []
          }));
    });

    test("flattens transitive connections", () {
      expect(
          transitiveClosure({
            "qux": [],
            "bar": ["baz"],
            "baz": ["qux"],
            "foo": ["bar"]
          }),
          equals({
            "foo": ["bar", "baz", "qux"],
            "bar": ["baz", "qux"],
            "baz": ["qux"],
            "qux": []
          }));
    });

    test("handles loops", () {
      expect(
          transitiveClosure({
            "foo": ["bar"],
            "bar": ["baz"],
            "baz": ["foo"]
          }),
          equals({
            "foo": ["bar", "baz", "foo"],
            "bar": ["baz", "foo", "bar"],
            "baz": ["foo", "bar", "baz"]
          }));
    });
  });

  group("stronglyConnectedComponents()", () {
    test("returns an empty list for an empty graph", () {
      expect(stronglyConnectedComponents({}), isEmpty);
    });

    test("returns one set for a singleton graph", () {
      expect(
          stronglyConnectedComponents({"a": []}),
          equals([
            new Set.from(["a"])
          ]));
    });

    test("returns two sets for a two-element tree", () {
      expect(
          stronglyConnectedComponents({
            "a": ["b"],
            "b": []
          }),
          equals([
            new Set.from(["a"]),
            new Set.from(["b"])
          ]));
    });

    test("returns one set for a two-element loop", () {
      expect(
          stronglyConnectedComponents({
            "a": ["b"],
            "b": ["a"]
          }),
          equals([
            new Set.from(["a", "b"])
          ]));
    });

    test("returns individual vertices for a tree", () {
      expect(
          stronglyConnectedComponents({
            "foo": ["bar"],
            "bar": ["baz", "bang"],
            "baz": ["qux"],
            "bang": ["zap"],
            "qux": [],
            "zap": []
          }),
          equals([
            // This is expected to return *a* topological ordering, but this isn't
            // the only valid one. If the function implementation changes in the
            // future, this test may need to be updated.
            new Set.from(["foo"]),
            new Set.from(["bar"]),
            new Set.from(["bang"]),
            new Set.from(["zap"]),
            new Set.from(["baz"]),
            new Set.from(["qux"])
          ]));
    });

    test("returns a single set for a fully cyclic graph", () {
      expect(
          stronglyConnectedComponents({
            "foo": ["bar"],
            "bar": ["baz"],
            "baz": ["bang"],
            "bang": ["foo"]
          }),
          equals([
            new Set.from(["foo", "bar", "baz", "bang"])
          ]));
    });

    test("returns separate sets for each strongly connected component", () {
      // https://en.wikipedia.org/wiki/Strongly_connected_component#/media/File:Scc.png
      expect(
          stronglyConnectedComponents({
            "a": ["b"],
            "b": ["c", "e", "f"],
            "c": ["d", "g"],
            "d": ["c", "h"],
            "e": ["a", "f"],
            "f": ["g"],
            "g": ["f"],
            "h": ["g", "d"]
          }),
          equals([
            // This is expected to return *a* topological ordering, but this isn't
            // the only valid one. If the function implementation changes in the
            // future, this test may need to be updated.
            new Set.from(["a", "b", "e"]),
            new Set.from(["c", "d", "h"]),
            new Set.from(["f", "g"]),
          ]));
    });

    test("always returns components in topological order", () {
      expect(
          stronglyConnectedComponents({
            "bar": ["baz", "bang"],
            "zap": [],
            "baz": ["qux"],
            "qux": [],
            "foo": ["bar"],
            "bang": ["zap"]
          }),
          equals([
            // This is expected to return *a* topological ordering, but this isn't
            // the only valid one. If the function implementation changes in the
            // future, this test may need to be updated.
            new Set.from(["foo"]),
            new Set.from(["bar"]),
            new Set.from(["bang"]),
            new Set.from(["zap"]),
            new Set.from(["baz"]),
            new Set.from(["qux"])
          ]));
    });
  });
}
