// Copyright (c) 2017, 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:graphs/graphs.dart';
import 'package:test/test.dart';

import 'utils/graph.dart';
import 'utils/utils.dart';

void main() {
  group('strongly connected components', () {
    /// Run [stronglyConnectedComponents] on [g].
    List<List<String>> components(
      Map<String, List<String>?> g, {
      Iterable<String>? startNodes,
    }) {
      final graph = Graph(g);
      return stronglyConnectedComponents(
        startNodes ?? graph.allNodes,
        graph.edges,
      );
    }

    test('empty result for empty graph', () {
      final result = components({});
      expect(result, isEmpty);
    });

    test('single item for single node', () {
      final result = components({'a': []});
      expect(result, [
        ['a'],
      ]);
    });

    test('handles non-cycles', () {
      final result = components({
        'a': ['b'],
        'b': ['c'],
        'c': [],
      });
      expect(result, [
        ['c'],
        ['b'],
        ['a'],
      ]);
    });

    test('handles entire graph as cycle', () {
      final result = components({
        'a': ['b'],
        'b': ['c'],
        'c': ['d'],
        'd': ['a'],
      });
      expect(
        result,
        [allOf(contains('a'), contains('b'), contains('c'), contains('d'))],
      );
    });

    test('includes the first passed root last in a cycle', () {
      // In cases where this is used to find a topological ordering the first
      // value in nodes should always come last.
      final graph = {
        'a': ['b'],
        'b': ['a'],
      };
      final resultFromA = components(graph, startNodes: ['a']);
      final resultFromB = components(graph, startNodes: ['b']);
      expect(resultFromA.single.last, 'a');
      expect(resultFromB.single.last, 'b');
    });

    test('handles cycles in the middle', () {
      final result = components({
        'a': ['b', 'c'],
        'b': ['c', 'd'],
        'c': ['b', 'd'],
        'd': [],
      });
      expect(result, [
        ['d'],
        allOf(contains('b'), contains('c')),
        ['a'],
      ]);
    });

    test('handles self cycles', () {
      final result = components({
        'a': ['b'],
        'b': ['b'],
      });
      expect(result, [
        ['b'],
        ['a'],
      ]);
    });

    test('valid topological ordering for disjoint subgraphs', () {
      final result = components({
        'a': ['b', 'c'],
        'b': ['b1', 'b2'],
        'c': ['c1', 'c2'],
        'b1': [],
        'b2': [],
        'c1': [],
        'c2': [],
      });

      expect(
        result,
        containsAllInOrder([
          ['c1'],
          ['c'],
          ['a'],
        ]),
      );
      expect(
        result,
        containsAllInOrder([
          ['c2'],
          ['c'],
          ['a'],
        ]),
      );
      expect(
        result,
        containsAllInOrder([
          ['b1'],
          ['b'],
          ['a'],
        ]),
      );
      expect(
        result,
        containsAllInOrder([
          ['b2'],
          ['b'],
          ['a'],
        ]),
      );
    });

    test('handles getting null for edges', () {
      final result = components({
        'a': ['b'],
        'b': null,
      });
      expect(result, [
        ['b'],
        ['a'],
      ]);
    });
  });

  group('custom hashCode and equals', () {
    /// Run [stronglyConnectedComponents] on [g].
    List<List<String>> components(
      Map<String, List<String>?> g, {
      Iterable<String>? startNodes,
    }) {
      final graph = BadGraph(g);

      startNodes ??= graph.allNodes.map((n) => n.value);

      return stronglyConnectedComponents<X>(
        startNodes.map(X.new),
        graph.edges,
        equals: xEquals,
        hashCode: xHashCode,
      ).map((list) => list.map((x) => x.value).toList()).toList();
    }

    test('empty result for empty graph', () {
      final result = components({});
      expect(result, isEmpty);
    });

    test('single item for single node', () {
      final result = components({'a': []});
      expect(result, [
        ['a'],
      ]);
    });

    test('handles non-cycles', () {
      final result = components({
        'a': ['b'],
        'b': ['c'],
        'c': [],
      });
      expect(result, [
        ['c'],
        ['b'],
        ['a'],
      ]);
    });

    test('handles entire graph as cycle', () {
      final result = components({
        'a': ['b'],
        'b': ['c'],
        'c': ['a'],
      });
      expect(result, [allOf(contains('a'), contains('b'), contains('c'))]);
    });

    test('includes the first passed root last in a cycle', () {
      // In cases where this is used to find a topological ordering the first
      // value in nodes should always come last.
      final graph = {
        'a': ['b'],
        'b': ['a'],
      };
      final resultFromA = components(graph, startNodes: ['a']);
      final resultFromB = components(graph, startNodes: ['b']);
      expect(resultFromA.single.last, 'a');
      expect(resultFromB.single.last, 'b');
    });

    test('handles cycles in the middle', () {
      final result = components({
        'a': ['b', 'c'],
        'b': ['c', 'd'],
        'c': ['b', 'd'],
        'd': [],
      });
      expect(result, [
        ['d'],
        allOf(contains('b'), contains('c')),
        ['a'],
      ]);
    });

    test('handles self cycles', () {
      final result = components({
        'a': ['b'],
        'b': ['b'],
      });
      expect(result, [
        ['b'],
        ['a'],
      ]);
    });

    test('valid topological ordering for disjoint subgraphs', () {
      final result = components({
        'a': ['b', 'c'],
        'b': ['b1', 'b2'],
        'c': ['c1', 'c2'],
        'b1': [],
        'b2': [],
        'c1': [],
        'c2': [],
      });

      expect(
        result,
        containsAllInOrder([
          ['c1'],
          ['c'],
          ['a'],
        ]),
      );
      expect(
        result,
        containsAllInOrder([
          ['c2'],
          ['c'],
          ['a'],
        ]),
      );
      expect(
        result,
        containsAllInOrder([
          ['b1'],
          ['b'],
          ['a'],
        ]),
      );
      expect(
        result,
        containsAllInOrder([
          ['b2'],
          ['b'],
          ['a'],
        ]),
      );
    });

    test('handles getting null for edges', () {
      final result = components({
        'a': ['b'],
        'b': null,
      });
      expect(result, [
        ['b'],
        ['a'],
      ]);
    });
  });
}
