import '../core/parser.dart';
import '../parser/misc/epsilon.dart';
import '../shared/types.dart';
import 'internal/cycle_set.dart';
import 'internal/first_set.dart';
import 'internal/follow_set.dart';
import 'internal/path.dart';
import 'iterable.dart';

/// Helper to reflect on properties of a grammar.
class Analyzer {
  /// Constructs an analyzer on the parser graph starting at [root].
  Analyzer(this.root);

  /// The start parser of analysis.
  final Parser root;

  /// Returns a set of all parsers reachable from [root].
  Iterable<Parser> get parsers => _parsers;

  late final Set<Parser> _parsers = allParser(root).toSet();

  /// Returns a set of all deep children reachable from [parser].
  ///
  /// The returned set does only include the [parser] itself, if it is
  /// recursively calling itself.
  Set<Parser> allChildren(Parser parser) {
    assert(parsers.contains(parser), 'parser is not part of the analyzer');
    return _allChildren.putIfAbsent(
        parser,
        () => parser.children.fold(
            <Parser>{}, (result, child) => result..addAll(allParser(child))));
  }

  late final Map<Parser, Set<Parser>> _allChildren = {};

  /// Returns the shortest path from [source] that satisfies the given
  /// [predicate], if any.
  ParserPath? findPath(Parser source, Predicate<ParserPath> predicate) {
    ParserPath? path;
    for (final current in findAllPaths(source, predicate)) {
      if (path == null || current.length < path.length) {
        path = current;
      }
    }
    return path;
  }

  /// Returns the shortest path from [source] to [target], if any.
  ParserPath? findPathTo(Parser source, Parser target) {
    assert(parsers.contains(target), 'target is not part of the analyzer');
    return findPath(source, (path) => path.target == target);
  }

  /// Returns all paths starting at [source] that satisfy the given [predicate].
  Iterable<ParserPath> findAllPaths(
      Parser source, Predicate<ParserPath> predicate) {
    assert(parsers.contains(source), 'source is not part of the analyzer');
    return depthFirstSearch(ParserPath([source], []), predicate);
  }

  /// Returns all paths starting at [source] that end in [target].
  Iterable<ParserPath> findAllPathsTo(Parser source, Parser target) {
    assert(parsers.contains(target), 'target is not part of the analyzer');
    return findAllPaths(source, (path) => path.target == target);
  }

  /// Returns `true` if [parser] is transitively nullable, that is it can
  /// successfully parse nothing.
  bool isNullable(Parser parser) => _firstSets[parser]!.contains(sentinel);

  /// Returns the first-set of [parser].
  ///
  /// The first-set of a parser is the set of terminal parsers which can appear
  /// as the first element of any chain of parsers derivable from [parser].
  /// Includes [sentinel], if the set is nullable.
  Iterable<Parser> firstSet(Parser parser) => _firstSets[parser]!;

  late final Map<Parser, Set<Parser>> _firstSets =
      computeFirstSets(parsers: _parsers, sentinel: sentinel);

  /// Returns the follow-set of a [parser].
  ///
  /// The follow-set of a parser is the list of terminal parsers that can
  /// appear immediately after [parser]. Includes [sentinel], if the parse can
  /// complete when starting at [root].
  Iterable<Parser> followSet(Parser parser) => _followSet[parser]!;

  late final Map<Parser, Set<Parser>> _followSet = computeFollowSets(
      root: root, parsers: _parsers, firstSets: _firstSets, sentinel: sentinel);

  /// Returns the cycle-set of a [parser].
  Iterable<Parser> cycleSet(Parser parser) => _cycleSet[parser]!;

  late final Map<Parser, List<Parser>> _cycleSet =
      computeCycleSets(parsers: _parsers, firstSets: _firstSets);

  /// A unique parser used as a marker in [firstSet] and [followSet]
  /// computations.
  static final sentinel = EpsilonParser<void>(null);
}
