//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// Implementation Note: Because StaticString is used in the
// implementation of _precondition(), _fatalErrorMessage(), etc., we
// keep it extremely close to the bare metal.  In particular, because
// we store only Builtin types, we are guaranteed that no assertions
// are involved in its construction.  This feature is crucial for
// preventing infinite recursion even in non-asserting cases.

/// A string type designed to represent text that is known at compile time.
///
/// Instances of the `StaticString` type are immutable. `StaticString` provides
/// limited, pointer-based access to its contents, unlike Swift's more
/// commonly used `String` type. A static string can store its value as a
/// pointer to an ASCII code unit sequence, as a pointer to a UTF-8 code unit
/// sequence, or as a single Unicode scalar value.
@_fixed_layout
public struct StaticString
  : _ExpressibleByBuiltinUnicodeScalarLiteral,
    _ExpressibleByBuiltinExtendedGraphemeClusterLiteral,
    _ExpressibleByBuiltinStringLiteral,
    ExpressibleByUnicodeScalarLiteral,
    ExpressibleByExtendedGraphemeClusterLiteral,
    ExpressibleByStringLiteral,
    CustomStringConvertible,
    CustomDebugStringConvertible,
    CustomReflectable {

  /// Either a pointer to the start of UTF-8 data, represented as an integer,
  /// or an integer representation of a single Unicode scalar.
  @usableFromInline
  internal var _startPtrOrData: Builtin.Word

  /// If `_startPtrOrData` is a pointer, contains the length of the UTF-8 data
  /// in bytes.
  @usableFromInline
  internal var _utf8CodeUnitCount: Builtin.Word

  /// Extra flags:
  ///
  /// - bit 0: set to 0 if `_startPtrOrData` is a pointer, or to 1 if it is a
  ///   Unicode scalar.
  ///
  /// - bit 1: set to 1 if `_startPtrOrData` is a pointer and string data is
  ///   ASCII.
  @usableFromInline
  internal var _flags: Builtin.Int8

  /// A pointer to the beginning of the string's UTF-8 encoded representation.
  ///
  /// The static string must store a pointer to either ASCII or UTF-8 code
  /// units. Accessing this property when `hasPointerRepresentation` is
  /// `false` triggers a runtime error.
  @inlinable // FIXME(sil-serialize-all)
  @_transparent
  public var utf8Start: UnsafePointer<UInt8> {
    _precondition(
      hasPointerRepresentation,
      "StaticString should have pointer representation")
    return UnsafePointer(bitPattern: UInt(_startPtrOrData))!
  }

  /// The stored Unicode scalar value.
  ///
  /// The static string must store a single Unicode scalar value. Accessing
  /// this property when `hasPointerRepresentation` is `true` triggers a
  /// runtime error.
  @inlinable // FIXME(sil-serialize-all)
  @_transparent
  public var unicodeScalar: Unicode.Scalar {
    _precondition(
      !hasPointerRepresentation,
      "StaticString should have Unicode scalar representation")
    return Unicode.Scalar(UInt32(UInt(_startPtrOrData)))!
  }

  /// The length in bytes of the static string's ASCII or UTF-8 representation.
  ///
  /// - Warning: If the static string stores a single Unicode scalar value, the
  ///   value of `utf8CodeUnitCount` is unspecified.
  @inlinable // FIXME(sil-serialize-all)
  @_transparent
  public var utf8CodeUnitCount: Int {
    _precondition(
      hasPointerRepresentation,
      "StaticString should have pointer representation")
    return Int(_utf8CodeUnitCount)
  }

  /// A Boolean value indicating whether the static string stores a pointer to
  /// ASCII or UTF-8 code units.
  @inlinable // FIXME(sil-serialize-all)
  @_transparent
  public var hasPointerRepresentation: Bool {
    return (UInt8(_flags) & 0x1) == 0
  }

  /// A Boolean value that is `true` if the static string stores a pointer to
  /// ASCII code units.
  ///
  /// Use this property in conjunction with `hasPointerRepresentation` to
  /// determine whether a static string with pointer representation stores an
  /// ASCII or UTF-8 code unit sequence.
  ///
  /// - Warning: If the static string stores a single Unicode scalar value, the
  ///   value of `isASCII` is unspecified.
  @inlinable // FIXME(sil-serialize-all)
  @_transparent
  public var isASCII: Bool {
    return (UInt8(_flags) & 0x2) != 0
  }

  /// Invokes the given closure with a buffer containing the static string's
  /// UTF-8 code unit sequence.
  ///
  /// This method works regardless of whether the static string stores a
  /// pointer or a single Unicode scalar value.
  ///
  /// The pointer argument to `body` is valid only during the execution of
  /// `withUTF8Buffer(_:)`. Do not store or return the pointer for later use.
  ///
  /// - Parameter body: A closure that takes a buffer pointer to the static
  ///   string's UTF-8 code unit sequence as its sole argument. If the closure
  ///   has a return value, that value is also used as the return value of the
  ///   `withUTF8Buffer(invoke:)` method. The pointer argument is valid only
  ///   for the duration of the method's execution.
  /// - Returns: The return value, if any, of the `body` closure.
  @inlinable // FIXME(sil-serialize-all)
  public func withUTF8Buffer<R>(
    _ body: (UnsafeBufferPointer<UInt8>) -> R) -> R {
    if hasPointerRepresentation {
      return body(UnsafeBufferPointer(
        start: utf8Start, count: utf8CodeUnitCount))
    } else {
      var buffer: UInt64 = 0
      var i = 0
      let sink: (UInt8) -> Void = {
#if _endian(little)
        buffer = buffer | (UInt64($0) << (UInt64(i) * 8))
#else
        buffer = buffer | (UInt64($0) << (UInt64(7-i) * 8))
#endif
        i += 1
      }
      UTF8.encode(unicodeScalar, into: sink)
      return body(UnsafeBufferPointer(
        start: UnsafePointer(Builtin.addressof(&buffer)),
        count: i))
    }
  }

  /// Creates an empty static string.
  @inlinable // FIXME(sil-serialize-all)
  @_transparent
  public init() {
    self = ""
  }

  @inlinable // FIXME(sil-serialize-all)
  @_transparent
  internal init(
    _start: Builtin.RawPointer,
    utf8CodeUnitCount: Builtin.Word,
    isASCII: Builtin.Int1
  ) {
    // We don't go through UnsafePointer here to make things simpler for alias
    // analysis. A higher-level algorithm may be trying to make sure an
    // unrelated buffer is not accessed or freed.
    self._startPtrOrData = Builtin.ptrtoint_Word(_start)
    self._utf8CodeUnitCount = utf8CodeUnitCount
    self._flags = Bool(isASCII)
      ? (0x2 as UInt8)._value
      : (0x0 as UInt8)._value
  }

  @inlinable // FIXME(sil-serialize-all)
  @_transparent
  internal init(
    unicodeScalar: Builtin.Int32
  ) {
    self._startPtrOrData = UInt(UInt32(unicodeScalar))._builtinWordValue
    self._utf8CodeUnitCount = 0._builtinWordValue
    self._flags = Unicode.Scalar(_builtinUnicodeScalarLiteral: unicodeScalar).isASCII
      ? (0x3 as UInt8)._value
      : (0x1 as UInt8)._value
  }

  @inlinable // FIXME(sil-serialize-all)
  @_effects(readonly)
  @_transparent
  public init(_builtinUnicodeScalarLiteral value: Builtin.Int32) {
    self = StaticString(unicodeScalar: value)
  }

  /// Creates an instance initialized to a single Unicode scalar.
  ///
  /// Do not call this initializer directly. It may be used by the compiler
  /// when you initialize a static string with a Unicode scalar.
  @inlinable // FIXME(sil-serialize-all)
  @_effects(readonly)
  @_transparent
  public init(unicodeScalarLiteral value: StaticString) {
    self = value
  }

  @inlinable // FIXME(sil-serialize-all)
  @_effects(readonly)
  @_transparent
  public init(
    _builtinExtendedGraphemeClusterLiteral start: Builtin.RawPointer,
    utf8CodeUnitCount: Builtin.Word,
    isASCII: Builtin.Int1
  ) {
    self = StaticString(
      _builtinStringLiteral: start,
      utf8CodeUnitCount: utf8CodeUnitCount,
      isASCII: isASCII
    )
  }

  /// Creates an instance initialized to a single character that is made up of
  /// one or more Unicode scalar values.
  ///
  /// Do not call this initializer directly. It may be used by the compiler
  /// when you initialize a static string using an extended grapheme cluster.
  @inlinable // FIXME(sil-serialize-all)
  @_effects(readonly)
  @_transparent
  public init(extendedGraphemeClusterLiteral value: StaticString) {
    self = value
  }

  @inlinable // FIXME(sil-serialize-all)
  @_effects(readonly)
  @_transparent
  public init(
    _builtinStringLiteral start: Builtin.RawPointer,
    utf8CodeUnitCount: Builtin.Word,
    isASCII: Builtin.Int1
  ) {
    self = StaticString(
      _start: start,
      utf8CodeUnitCount: utf8CodeUnitCount,
      isASCII: isASCII)
  }

  /// Creates an instance initialized to the value of a string literal.
  ///
  /// Do not call this initializer directly. It may be used by the compiler
  /// when you initialize a static string using a string literal.
  @inlinable // FIXME(sil-serialize-all)
  @_effects(readonly)
  @_transparent
  public init(stringLiteral value: StaticString) {
    self = value
  }

  /// A string representation of the static string.
  @inlinable // FIXME(sil-serialize-all)
  public var description: String {
    return withUTF8Buffer { (buffer) in
      if isASCII {
        return String._fromASCII(buffer)
      } else {
        return String._fromWellFormedUTF8CodeUnitSequence(buffer)
      }
    }
  }

  /// A textual representation of the static string, suitable for debugging.
  @inlinable // FIXME(sil-serialize-all)
  public var debugDescription: String {
    return self.description.debugDescription
  }
}

extension StaticString {
  @inlinable // FIXME(sil-serialize-all)
  public var customMirror: Mirror {
    return Mirror(reflecting: description)
  }
}
