// 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 'dart:collection';

import 'unmodifiable_wrappers.dart';

/// A single set that provides a view of the union over a set of sets.
///
/// Since this is just a view, it reflects all changes in the underlying sets.
///
/// If an element is in multiple sets and the outer set is ordered, the version
/// in the earliest inner set is preferred. Component sets are assumed to use
/// `==` and `hashCode` for equality.
class UnionSet<E> extends SetBase<E> with UnmodifiableSetMixin<E> {
  /// The set of sets that this provides a view of.
  final Set<Set<E>> _sets;

  /// Whether the sets in [_sets] are guaranteed to be disjoint.
  final bool _disjoint;

  /// Creates a new set that's a view of the union of all sets in [sets].
  ///
  /// If any sets in [sets] change, this [UnionSet] reflects that change. If a
  /// new set is added to [sets], this [UnionSet] reflects that as well.
  ///
  /// If [disjoint] is `true`, then all component sets must be disjoint. That
  /// is, that they contain no elements in common. This makes many operations
  /// including [length] more efficient. If the component sets turn out not to
  /// be disjoint, some operations may behave inconsistently.
  UnionSet(Set<Set<E>> sets, {bool disjoint = false})
      : _sets = sets,
        _disjoint = disjoint;

  /// Creates a new set that's a view of the union of all sets in [sets].
  ///
  /// If any sets in [sets] change, this [UnionSet] reflects that change.
  /// However, unlike [UnionSet.new], this creates a copy of its parameter, so
  /// changes in [sets] aren't reflected in this [UnionSet].
  ///
  /// If [disjoint] is `true`, then all component sets must be disjoint. That
  /// is, that they contain no elements in common. This makes many operations
  /// including [length] more efficient. If the component sets turn out not to
  /// be disjoint, some operations may behave inconsistently.
  UnionSet.from(Iterable<Set<E>> sets, {bool disjoint = false})
      : this(sets.toSet(), disjoint: disjoint);

  @override
  int get length => _disjoint
      ? _sets.fold(0, (length, set) => length + set.length)
      : _iterable.length;

  @override
  Iterator<E> get iterator => _iterable.iterator;

  /// An iterable over the contents of all [_sets].
  ///
  /// If this is not a [_disjoint] union an extra set is used to deduplicate
  /// values.
  Iterable<E> get _iterable {
    var allElements = _sets.expand((set) => set);
    return _disjoint ? allElements : allElements.where(<E>{}.add);
  }

  @override
  bool contains(Object? element) => _sets.any((set) => set.contains(element));

  @override
  E? lookup(Object? element) {
    for (var set in _sets) {
      var result = set.lookup(element);
      if (result != null || set.contains(null)) return result;
    }
    return null;
  }

  @override
  Set<E> toSet() => <E>{for (var set in _sets) ...set};
}
