// Copyright (c) 2018, 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 'dart:collection';

/// Returns the shortest path from [start] to [target] given the directed
/// edges of a graph provided by [edges].
///
/// If [start] `==` [target], an empty [List] is returned and [edges] is never
/// called.
///
/// If [equals] is provided, it is used to compare nodes in the graph. If
/// [equals] is omitted, the node's own [Object.==] is used instead.
///
/// Similarly, if [hashCode] is provided, it is used to produce a hash value
/// for nodes to efficiently calculate the return value. If it is omitted, the
/// key's own [Object.hashCode] is used.
///
/// If you supply one of [equals] or [hashCode], you should generally also to
/// supply the other.
Iterable<T>? shortestPath<T extends Object>(
  T start,
  T target,
  Iterable<T> Function(T) edges, {
  bool Function(T, T)? equals,
  int Function(T)? hashCode,
}) =>
    _shortestPaths<T>(
      start,
      edges,
      target: target,
      equals: equals,
      hashCode: hashCode,
    )[target];

/// Returns a [Map] of the shortest paths from [start] to all of the nodes in
/// the directed graph defined by [edges].
///
/// All return values will contain the key [start] with an empty [List] value.
///
/// [start] and all values returned by [edges] must not be `null`.
/// If asserts are enabled, an [AssertionError] is raised if these conditions
/// are not met. If asserts are not enabled, violations result in undefined
/// behavior.
///
/// If [equals] is provided, it is used to compare nodes in the graph. If
/// [equals] is omitted, the node's own [Object.==] is used instead.
///
/// Similarly, if [hashCode] is provided, it is used to produce a hash value
/// for nodes to efficiently calculate the return value. If it is omitted, the
/// key's own [Object.hashCode] is used.
///
/// If you supply one of [equals] or [hashCode], you should generally also to
/// supply the other.
Map<T, Iterable<T>> shortestPaths<T extends Object>(
  T start,
  Iterable<T> Function(T) edges, {
  bool Function(T, T)? equals,
  int Function(T)? hashCode,
}) =>
    _shortestPaths<T>(
      start,
      edges,
      equals: equals,
      hashCode: hashCode,
    );

Map<T, Iterable<T>> _shortestPaths<T extends Object>(
  T start,
  Iterable<T> Function(T) edges, {
  T? target,
  bool Function(T, T)? equals,
  int Function(T)? hashCode,
}) {
  final distances = HashMap<T, _Tail<T>>(equals: equals, hashCode: hashCode);
  distances[start] = _Tail<T>();

  final nonNullEquals = equals ??= _defaultEquals;
  final isTarget =
      target == null ? _neverTarget : (T node) => nonNullEquals(node, target);
  if (isTarget(start)) {
    return distances;
  }

  final toVisit = ListQueue<T>()..add(start);

  while (toVisit.isNotEmpty) {
    final current = toVisit.removeFirst();
    final currentPath = distances[current]!;

    for (var edge in edges(current)) {
      final existingPath = distances[edge];

      if (existingPath == null) {
        distances[edge] = currentPath.append(edge);
        if (isTarget(edge)) {
          return distances;
        }
        toVisit.add(edge);
      }
    }
  }

  return distances;
}

bool _defaultEquals(Object a, Object b) => a == b;
bool _neverTarget(Object _) => false;

/// An immutable iterable that can efficiently return a copy with a value
/// appended.
///
/// This implementation has an efficient [length] property.
///
/// Note that grabbing an [iterator] for the first time is O(n) in time and
/// space because it copies all the values to a new list and uses that
/// iterator in order to avoid stack overflows for large paths. This copy is
/// cached for subsequent calls.
class _Tail<T extends Object> extends Iterable<T> {
  final T? tail;
  final _Tail<T>? head;
  @override
  final int length;
  _Tail()
      : tail = null,
        head = null,
        length = 0;
  _Tail._(this.tail, this.head, this.length);
  _Tail<T> append(T value) => _Tail._(value, this, length + 1);

  @override
  Iterator<T> get iterator => _asIterable.iterator;

  late final _asIterable = () {
    _Tail<T>? next = this;
    final reversed = List.generate(length, (_) {
      final val = next!.tail;
      next = next!.head;
      return val as T;
    });
    return reversed.reversed;
  }();
}
