//===--- ValidUTF8Buffer.swift - Bounded Collection of Valid UTF-8 --------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//
//
//  Stores valid UTF8 inside an unsigned integer.
//
//  Actually this basic type could be used to store any UInt8s that cannot be
//  0xFF
//
//===----------------------------------------------------------------------===//
@frozen
public struct _ValidUTF8Buffer {
  public typealias Element = Unicode.UTF8.CodeUnit

  @usableFromInline
  internal var _biasedBits: UInt32

  @inlinable
  internal init(_biasedBits: UInt32) {
    self._biasedBits = _biasedBits
  }

  @inlinable
  internal init(_containing e: Element) {
    _internalInvariant(
      e != 192 && e != 193 && !(245...255).contains(e), "invalid UTF8 byte")
    _biasedBits = UInt32(truncatingIfNeeded: e &+ 1)
  }
}

extension _ValidUTF8Buffer: Sequence {
  public typealias SubSequence = Slice<_ValidUTF8Buffer>

  @frozen
  public struct Iterator: IteratorProtocol, Sequence {
    @inlinable
    public init(_ x: _ValidUTF8Buffer) { _biasedBits = x._biasedBits }

    @inlinable
    public mutating func next() -> Element? {
      if _biasedBits == 0 { return nil }
      defer { _biasedBits >>= 8 }
      return Element(truncatingIfNeeded: _biasedBits) &- 1
    }
    @usableFromInline
    internal var _biasedBits: UInt32
  }

  @inlinable
  public func makeIterator() -> Iterator {
    return Iterator(self)
  }
}

extension _ValidUTF8Buffer: Collection {
  @frozen
  public struct Index: Comparable {
    @usableFromInline
    internal var _biasedBits: UInt32

    @inlinable
    internal init(_biasedBits: UInt32) { self._biasedBits = _biasedBits }

    @inlinable
    public static func == (lhs: Index, rhs: Index) -> Bool {
      return lhs._biasedBits == rhs._biasedBits
    }
    @inlinable
    public static func < (lhs: Index, rhs: Index) -> Bool {
      return lhs._biasedBits > rhs._biasedBits
    }
  }

  @inlinable
  public var startIndex: Index {
    return Index(_biasedBits: _biasedBits)
  }

  @inlinable
  public var endIndex: Index {
    return Index(_biasedBits: 0)
  }

  @inlinable
  public var count: Int {
    return UInt32.bitWidth &>> 3 &- _biasedBits.leadingZeroBitCount &>> 3
  }

  @inlinable
  public var isEmpty: Bool {
    return _biasedBits == 0
  }

  @inlinable
  public func index(after i: Index) -> Index {
    _debugPrecondition(i._biasedBits != 0)
    return Index(_biasedBits: i._biasedBits >> 8)
  }

  @inlinable
  public subscript(i: Index) -> Element {
    return Element(truncatingIfNeeded: i._biasedBits) &- 1
  }
}

extension _ValidUTF8Buffer: BidirectionalCollection {
  @inlinable
  public func index(before i: Index) -> Index {
    let offset = _ValidUTF8Buffer(_biasedBits: i._biasedBits).count
    _debugPrecondition(offset != 0)
    return Index(_biasedBits: _biasedBits &>> (offset &<< 3 - 8))
  }
}

extension _ValidUTF8Buffer: RandomAccessCollection {
  public typealias Indices = DefaultIndices<_ValidUTF8Buffer>

  @inlinable
  @inline(__always)
  public func distance(from i: Index, to j: Index) -> Int {
    _debugPrecondition(_isValid(i))
    _debugPrecondition(_isValid(j))
    return (
      i._biasedBits.leadingZeroBitCount - j._biasedBits.leadingZeroBitCount
    ) &>> 3
  }

  @inlinable
  @inline(__always)
  public func index(_ i: Index, offsetBy n: Int) -> Index {
    let startOffset = distance(from: startIndex, to: i)
    let newOffset = startOffset + n
    _debugPrecondition(newOffset >= 0)
    _debugPrecondition(newOffset <= count)
    return Index(_biasedBits: _biasedBits._fullShiftRight(newOffset &<< 3))
  }
}

extension _ValidUTF8Buffer: RangeReplaceableCollection {
  @inlinable
  public init() {
    _biasedBits = 0
  }

  @inlinable
  public var capacity: Int {
    return _ValidUTF8Buffer.capacity
  }

  @inlinable
  public static var capacity: Int {
    return UInt32.bitWidth / Element.bitWidth
  }

  @inlinable
  @inline(__always)
  public mutating func append(_ e: Element) {
    _debugPrecondition(count + 1 <= capacity)
    _internalInvariant(
      e != 192 && e != 193 && !(245...255).contains(e), "invalid UTF8 byte")
    _biasedBits |= UInt32(e &+ 1) &<< (count &<< 3)
  }

  @inlinable
  @inline(__always)
  @discardableResult
  public mutating func removeFirst() -> Element {
    _debugPrecondition(!isEmpty)
    let result = Element(truncatingIfNeeded: _biasedBits) &- 1
    _biasedBits = _biasedBits._fullShiftRight(8)
    return result
  }

  @inlinable
  internal func _isValid(_ i: Index) -> Bool {
    return i == endIndex || indices.contains(i)
  }

  @inlinable
  @inline(__always)
  public mutating func replaceSubrange<C: Collection>(
    _ target: Range<Index>, with replacement: C
  ) where C.Element == Element {
    _debugPrecondition(_isValid(target.lowerBound))
    _debugPrecondition(_isValid(target.upperBound))
    var r = _ValidUTF8Buffer()
    for x in self[..<target.lowerBound] { r.append(x) }
    for x in replacement                { r.append(x) }
    for x in self[target.upperBound...] { r.append(x) }
    self = r
  }
}

extension _ValidUTF8Buffer {
  @inlinable
  @inline(__always)
  public mutating func append(contentsOf other: _ValidUTF8Buffer) {
    _debugPrecondition(count + other.count <= capacity)
    _biasedBits |= UInt32(
      truncatingIfNeeded: other._biasedBits) &<< (count &<< 3)
  }
}

extension _ValidUTF8Buffer {
  @inlinable
  public static var encodedReplacementCharacter: _ValidUTF8Buffer {
    return _ValidUTF8Buffer(_biasedBits: 0xBD_BF_EF &+ 0x01_01_01)
  }

  @inlinable
  internal var _bytes: (bytes: UInt64, count: Int) {
    let count = self.count
    let mask: UInt64 = 1 &<< (UInt64(truncatingIfNeeded: count) &<< 3) &- 1
    let unbiased = UInt64(truncatingIfNeeded: _biasedBits) &- 0x0101010101010101
    return (unbiased & mask, count)
  }
}
