// 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.

// ignore_for_file: deprecated_member_use_from_same_package

import 'package:collection/collection.dart';
import 'package:test/test.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<String, int, String, int>(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<String, int, dynamic, int>({'foo': 1, 'bar': 2},
              key: (dynamic key, dynamic value) => key[value]),
          equals({'o': 1, 'r': 2}));
    });

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

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

  group('mergeMaps()', () {
    test('with empty maps returns an empty map', () {
      expect(
          mergeMaps({}, {},
              value: expectAsync2((dynamic _, dynamic __) {}, 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: (dynamic value1, dynamic value2) => value1 + value2),
          equals({'foo': 1, 'bar': 5, 'baz': 4}));
    });
  });

  group('lastBy()', () {
    test('returns an empty map for an empty iterable', () {
      expect(
        lastBy([], (_) => fail('Must not be called for empty input')),
        isEmpty,
      );
    });

    test("keeps the latest element for the function's return value", () {
      expect(
          lastBy(['foo', 'bar', 'baz', 'bop', 'qux'],
              (String string) => string[1]),
          equals({
            'o': 'bop',
            'a': 'baz',
            'u': 'qux',
          }));
    });
  });

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

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

  group('minBy()', () {
    test('returns null for an empty iterable', () {
      expect(
          minBy([], expectAsync1((dynamic _) {}, count: 0),
              compare: expectAsync2((dynamic _, dynamic __) => -1, 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}
          ], (dynamic 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((dynamic _) {}, count: 0),
              compare: expectAsync2((dynamic _, dynamic __) => 0, 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}
          ], (dynamic 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([
            {'a'}
          ]));
    });

    test('returns two sets for a two-element tree', () {
      expect(
          stronglyConnectedComponents({
            'a': ['b'],
            'b': []
          }),
          equals([
            {'a'},
            {'b'}
          ]));
    });

    test('returns one set for a two-element loop', () {
      expect(
          stronglyConnectedComponents({
            'a': ['b'],
            'b': ['a']
          }),
          equals([
            {'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.
          {'foo'},
          {'bar'},
          {'bang'},
          {'zap'},
          {'baz'},
          {'qux'}
        ]),
      );
    });

    test('returns a single set for a fully cyclic graph', () {
      expect(
          stronglyConnectedComponents({
            'foo': ['bar'],
            'bar': ['baz'],
            'baz': ['bang'],
            'bang': ['foo']
          }),
          equals([
            {'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.
          {'a', 'b', 'e'},
          {'c', 'd', 'h'},
          {'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.
          {'foo'},
          {'bar'},
          {'bang'},
          {'zap'},
          {'baz'},
          {'qux'}
        ]),
      );
    });
  });
}
