//===--- DiscontiguousSlice.swift -----------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2020 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

/// A collection wrapper that provides access to the elements of a collection,
/// indexed by a set of indices.
@available(SwiftStdlib 6.0, *)
public struct DiscontiguousSlice<Base: Collection> {
  internal var _base: Base
  
  /// The set of subranges that are available through this discontiguous slice.
  public let subranges: RangeSet<Base.Index>

  /// The collection that the indexed collection wraps.
  public var base: Base {
    return _base
  }

  @usableFromInline
  internal init(_base: Base, subranges: RangeSet<Base.Index>) {
    self._base = _base
    self.subranges = subranges
  }
}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice: Sendable
where Base: Sendable, Base.Index: Sendable {}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice: Equatable where Base.Element: Equatable {
  public static func ==(lhs: Self, rhs: Self) -> Bool {
    lhs.elementsEqual(rhs)
  }
}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice: Hashable where Base.Element: Hashable {
  public func hash(into hasher: inout Hasher) {
    hasher.combine(count) // delimiter; do not remove
    for element in self {
      hasher.combine(element)
    }
  }
}

@available(SwiftStdlib 6.0, *)
@_unavailableInEmbedded
extension DiscontiguousSlice: CustomStringConvertible {
  public var description: String {
    _makeCollectionDescription()
  }
}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice {
  /// A position in a `DiscontiguousSlice`.
  public struct Index {
    /// The index of the range that contains `base`.
    internal let _rangeOffset: Int
    
    /// The position of this index in the base collection.
    public let base: Base.Index

    internal init(_rangeOffset: Int, base: Base.Index) {
      self._rangeOffset = _rangeOffset
      self.base = base
    }
  }
}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice.Index: Equatable {
  public static func == (left: Self, right: Self) -> Bool {
    left.base == right.base
  }
}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice.Index: Hashable where Base.Index: Hashable {
  public func hash(into hasher: inout Hasher) {
    hasher.combine(base)
  }
}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice.Index: Comparable {
  public static func < (left: Self, right: Self) -> Bool {
    left.base < right.base
  }
}

@available(SwiftStdlib 6.0, *)
@_unavailableInEmbedded
extension DiscontiguousSlice.Index: CustomStringConvertible {
  public var description: String {
    "<base: \(String(reflecting: base)), rangeOffset: \(_rangeOffset)>"
  }
}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice.Index: Sendable where Base.Index: Sendable {}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice: Sequence {
  public typealias Element = Base.Element
  public typealias Iterator = IndexingIterator<Self>

  public func _customContainsEquatableElement(_ element: Element) -> Bool? {
    _customIndexOfEquatableElement(element).map { $0 != nil }
  }

  public __consuming func _copyToContiguousArray() -> ContiguousArray<Element> {
    var result: ContiguousArray<Element> = []
    for range in subranges.ranges {
      result.append(contentsOf: base[range])
    }
    return result
  }
}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice: Collection {
  public typealias SubSequence = Self
  public typealias Indices = DefaultIndices<Self>
  
  public var startIndex: Index {
    subranges.isEmpty
      ? endIndex
      : Index(_rangeOffset: 0, base: subranges.ranges[0].lowerBound)
  }
  
  public var endIndex: Index {
    Index(_rangeOffset: subranges.ranges.endIndex, base: _base.endIndex)
  }
  
  public var count: Int {
    var c = 0
    for range in subranges.ranges {
      c += base[range].count
    }
    return c
  }

  public var isEmpty: Bool {
    subranges.isEmpty
  }

  public func distance(from start: Index, to end: Index) -> Int {
    let ranges = subranges.ranges[start._rangeOffset ... end._rangeOffset]
    guard ranges.count > 1 else {
      return _base[ranges[0]].distance(from: start.base, to: end.base)
    }
    let firstRange = ranges.first!
    let lastRange = ranges.last!
    let head = _base[firstRange].distance(
      from: start.base, to: firstRange.upperBound)
    let tail = _base[lastRange].distance(
      from: lastRange.lowerBound, to: end.base)
    let middle = ranges[1 ..< (ranges.endIndex - 1)].reduce(0) {
      $0 + _base[$1].count
    }
    return head + middle + tail
  }
  
  public func index(after i: Index) -> Index {
    // Note: index validation performed by the underlying collections only
    let currentRange = subranges.ranges[i._rangeOffset]
    let nextIndex = _base[currentRange].index(after: i.base)
    if nextIndex < currentRange.upperBound {
      return Index(_rangeOffset: i._rangeOffset, base: nextIndex)
    }
    
    let nextOffset = i._rangeOffset + 1
    guard nextOffset < subranges.ranges.endIndex else {
      return endIndex
    }

    return Index(
      _rangeOffset: nextOffset, base: subranges.ranges[nextOffset].lowerBound)
  }

  public subscript(i: Index) -> Base.Element {
    // Note: index validation performed by the base collection only
    _base[subranges.ranges[i._rangeOffset]][i.base]
  }
  
  public subscript(bounds: Range<Index>) -> DiscontiguousSlice<Base> {
    let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base
    let baseSlice = base[baseBounds]
    let baseAdjustedBounds = baseSlice.startIndex ..< baseSlice.endIndex
    let subset = subranges.intersection(RangeSet(baseAdjustedBounds))
    return DiscontiguousSlice<Base>(_base: base, subranges: subset)
  }

  @usableFromInline
  internal func _index(of baseIndex: Base.Index) -> Index? {
    let rangeOffset = subranges.ranges
      ._partitioningIndex { $0.upperBound >= baseIndex }
    let subrange = subranges.ranges[rangeOffset]
    guard subrange.contains(baseIndex) else {
      return nil
    }
    return Index(_rangeOffset: rangeOffset, base: baseIndex)
  }

  public func _customIndexOfEquatableElement(_ element: Element) -> Index?? {
    var definite = true
    for (i, range) in subranges.ranges.enumerated() {
      guard let baseResult = _base[range]
        ._customIndexOfEquatableElement(element) else {
        definite = false
        continue
      }
      guard let baseIndex = baseResult else {
        continue
      }
      return Index(_rangeOffset: i, base: baseIndex)
    }
    if definite {
      return .some(nil)
    } else {
      return nil
    }
  }

  public func _customLastIndexOfEquatableElement(
    _ element: Element
  ) -> Index?? {
    var definite = true
    for (i, range) in subranges.ranges.enumerated().reversed() {
      guard let baseResult = _base[range]
        ._customLastIndexOfEquatableElement(element) else {
        definite = false
        continue
      }
      guard let baseIndex = baseResult else {
        continue
      }
      return Index(_rangeOffset: i, base: baseIndex)
    }
    if definite {
      return .some(nil)
    } else {
      return nil
    }
  }

  public func _failEarlyRangeCheck(
    _ index: Index, bounds: Range<Index>
  ) {
    let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base
    let offsetBounds = bounds.lowerBound._rangeOffset ..<
      bounds.upperBound._rangeOffset
    _base._failEarlyRangeCheck(index.base, bounds: baseBounds)
    _precondition(offsetBounds.contains(index._rangeOffset))
    if index._rangeOffset == endIndex._rangeOffset {
      _precondition(index.base == _base.endIndex)
    } else {
      _precondition(subranges.ranges[index._rangeOffset].contains(index.base))
    }
  }

  public func _failEarlyRangeCheck(_ index: Index, bounds: ClosedRange<Index>) {
    let baseBounds = bounds.lowerBound.base ... bounds.upperBound.base
    let offsetBounds = bounds.lowerBound._rangeOffset ...
      bounds.upperBound._rangeOffset
    _base._failEarlyRangeCheck(index.base, bounds: baseBounds)
    _precondition(offsetBounds.contains(index._rangeOffset))
    if index._rangeOffset == endIndex._rangeOffset {
      _precondition(index.base == _base.endIndex)
    } else {
      _precondition(subranges.ranges[index._rangeOffset].contains(index.base))
    }
  }

  public func _failEarlyRangeCheck(_ range: Range<Index>, bounds: Range<Index>) {
    let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base
    let baseRange = range.lowerBound.base ..< range.upperBound.base
    let offsetBounds = bounds.lowerBound._rangeOffset ..<
      bounds.upperBound._rangeOffset
    let offsetRange = range.lowerBound._rangeOffset ..<
      range.upperBound._rangeOffset

    _base._failEarlyRangeCheck(baseRange, bounds: baseBounds)
    _precondition(offsetBounds.contains(offsetRange.lowerBound))
    _precondition(offsetRange.upperBound <= offsetBounds.upperBound)

    if offsetRange.lowerBound == endIndex._rangeOffset {
      _precondition(baseRange.lowerBound == _base.endIndex)
    } else {
      _precondition(
        subranges.ranges[offsetRange.lowerBound].contains(baseRange.lowerBound))
    }

    if offsetRange.upperBound == endIndex._rangeOffset {
      _precondition(baseRange.upperBound == _base.endIndex)
    } else {
      _precondition(
        subranges.ranges[offsetRange.upperBound].contains(baseRange.upperBound))
    }
  }
}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice: BidirectionalCollection
  where Base: BidirectionalCollection
{
  public func index(before i: Index) -> Index {
    _precondition(i > startIndex, "Can't move index before startIndex")

    if i.base == base.endIndex ||
      i.base == subranges.ranges[i._rangeOffset].lowerBound {
      // Go to next subrange
      let nextOffset = i._rangeOffset - 1
      let nextRange = subranges.ranges[nextOffset]
      let nextBase = base[nextRange].index(before: nextRange.upperBound)
      return Index(_rangeOffset: nextOffset, base: nextBase)
    } else {
      // Move within current subrange
      let currentRange = subranges.ranges[i._rangeOffset]
      let nextBase = base[currentRange].index(before: i.base)
      return Index(_rangeOffset: i._rangeOffset, base: nextBase)
    }
  }
}

@available(SwiftStdlib 6.0, *)
extension DiscontiguousSlice where Base: MutableCollection {
  /// Accesses the element at the specified position.
  ///
  /// For example, you can replace an element of an array by using its
  /// subscript.
  ///
  ///     var streets = ["Adams", "Bryant", "Channing", "Douglas", "Evarts"]
  ///     streets[1] = "Butler"
  ///     print(streets[1])
  ///     // Prints "Butler"
  ///
  /// You can subscript a collection with any valid index other than the
  /// collection's end index. The end index refers to the position one
  /// past the last element of a collection, so it doesn't correspond with an
  /// element.
  ///
  /// - Parameter i: The position of the element to access. `i`
  ///   must be a valid index of the collection that is not equal to the
  ///   `endIndex` property.
  ///
  /// - Complexity: O(1)
  public subscript(i: Index) -> Base.Element {
    get {
      _base[subranges.ranges[i._rangeOffset]][i.base]
    }
    set {
      _base[subranges.ranges[i._rangeOffset]][i.base] = newValue
    }
  }
}

// MARK: Accessing DiscontiguousSlices

extension Collection {
  /// Accesses a view of this collection with the elements at the given
  /// indices.
  ///
  /// - Parameter subranges: The indices of the elements to retrieve from this
  ///   collection.
  /// - Returns: A collection of the elements at the positions in `subranges`.
  ///
  /// - Complexity: O(1)
  @available(SwiftStdlib 6.0, *)
  public subscript(subranges: RangeSet<Index>) -> DiscontiguousSlice<Self> {
    DiscontiguousSlice(_base: self, subranges: subranges)
  }
}


extension Collection {
  /// Returns a collection of the elements in this collection that are not
  /// represented by the given range set.
  ///
  /// For example, this code sample finds the indices of all the vowel
  /// characters in the string, and then retrieves a collection that omits
  /// those characters.
  ///
  ///     let str = "The rain in Spain stays mainly in the plain."
  ///     let vowels: Set<Character> = ["a", "e", "i", "o", "u"]
  ///     let vowelIndices = str.indices(where: { vowels.contains($0) })
  ///
  ///     let disemvoweled = str.removingSubranges(vowelIndices)
  ///     print(String(disemvoweled))
  ///     // Prints "Th rn n Spn stys mnly n th pln."
  ///
  /// - Parameter subranges: A range set representing the indices of the
  ///   elements to remove.
  /// - Returns: A collection of the elements that are not in `subranges`.
  ///
  /// - Complexity: O(*n*), where *n* is the length of the collection.
  @available(SwiftStdlib 6.0, *)
  public func removingSubranges(
    _ subranges: RangeSet<Index>
  ) -> DiscontiguousSlice<Self> {
    let inversion = subranges._inverted(within: self)
    return self[inversion]
  }
}
