// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -o %t/a.out4 -swift-version 4 && %target-codesign %t/a.out4 && %target-run %t/a.out4

// REQUIRES: executable_test
// REQUIRES: reflection

import StdlibUnittest

//===--- MyString ---------------------------------------------------------===//
/// A simple StringProtocol with a wacky .description that proves
/// LosslessStringConvertible is not infecting ordinary constructions by using
/// .description as the content of a copied string.
struct MyString {
  var base: String
}

extension MyString : BidirectionalCollection {
  typealias Iterator = String.Iterator
  typealias Index = String.Index
  typealias SubSequence = MyString
  func makeIterator() -> Iterator { return base.makeIterator() }
  var startIndex: String.Index { return base.startIndex }
  var endIndex: String.Index { return base.startIndex }
  subscript(i: Index) -> Character { return base[i] }
  subscript(indices: Range<Index>) -> MyString {
    return MyString(base: String(self.base[indices]))
  }
  func index(after i: Index) -> Index { return base.index(after: i) }
  func index(before i: Index) -> Index { return base.index(before: i) }
  func index(_ i: Index, offsetBy n: Int) -> Index {
    return base.index(i, offsetBy: n)
  }
  func distance(from i: Index, to j: Index) -> Int {
    return base.distance(from: i, to: j)
  }
}

extension MyString : RangeReplaceableCollection {
  init() { base = "" }
  mutating func append<S: Sequence>(contentsOf s: S)
  where S.Element == Character {
    base.append(contentsOf: s)
  }
  mutating func replaceSubrange<C: Collection>(_ r: Range<Index>, with c: C)
  where C.Element == Character {
    base.replaceSubrange(r, with: c)
  }
}

extension MyString : CustomStringConvertible {
  var description: String { return "***MyString***" }
}

extension MyString : TextOutputStream {
  public mutating func write(_ other: String) {
    append(contentsOf: other)
  }
}

extension MyString : TextOutputStreamable {
  public func write<Target : TextOutputStream>(to target: inout Target) {
    target.write(base)
  }
}

extension MyString : ExpressibleByUnicodeScalarLiteral {
  public init(unicodeScalarLiteral value: String) {
    base = .init(unicodeScalarLiteral: value)
  }
}
extension MyString : ExpressibleByExtendedGraphemeClusterLiteral {
  public init(extendedGraphemeClusterLiteral value: String) {
    base = .init(extendedGraphemeClusterLiteral: value)
  }
}

extension MyString : ExpressibleByStringLiteral {
  public init(stringLiteral value: String) {
    base = .init(stringLiteral: value)
  }
}

extension MyString : CustomReflectable {
  public var customMirror: Mirror {
    return base.customMirror
  }
}

extension MyString : CustomPlaygroundQuickLookable {
  public var customPlaygroundQuickLook: PlaygroundQuickLook {
    return base.customPlaygroundQuickLook
  }
}

extension MyString : CustomDebugStringConvertible {
  public var debugDescription: String {
    return "(***MyString***)"
  }
}

extension MyString : Equatable {
  public static func ==(lhs: MyString, rhs: MyString) -> Bool {
    return lhs.base == rhs.base
  }
}

extension MyString : Comparable {
  public static func <(lhs: MyString, rhs: MyString) -> Bool {
    return lhs.base < rhs.base
  }
}

extension MyString : Hashable {
  public var hashValue : Int {
    return base.hashValue
  }
}

extension MyString {
  public func hasPrefix(_ prefix: String) -> Bool {
    return self.base.hasPrefix(prefix)
  }

  public func hasSuffix(_ suffix: String) -> Bool {
    return self.base.hasSuffix(suffix)
  }
}

extension MyString : StringProtocol {
  var utf8: String.UTF8View { return base.utf8 }
  var utf16: String.UTF16View { return base.utf16 }
  var unicodeScalars: String.UnicodeScalarView { return base.unicodeScalars }
  var characters: String.CharacterView { return base.characters }
  func lowercased() -> String {
    return base.lowercased()
  }
  func uppercased() -> String {
    return base.uppercased()
  }

  init<C: Collection, Encoding: Unicode.Encoding>(
    decoding codeUnits: C, as sourceEncoding: Encoding.Type
  )
  where C.Iterator.Element == Encoding.CodeUnit {
    base = .init(decoding: codeUnits, as: sourceEncoding)
  }

  init(cString nullTerminatedUTF8: UnsafePointer<CChar>) {
    base = .init(cString: nullTerminatedUTF8)
  }
  
  init<Encoding: Unicode.Encoding>(
    decodingCString nullTerminatedCodeUnits: UnsafePointer<Encoding.CodeUnit>,
    as sourceEncoding: Encoding.Type) {
    base = .init(decodingCString: nullTerminatedCodeUnits, as: sourceEncoding)
  }
  
  func withCString<Result>(
    _ body: (UnsafePointer<CChar>) throws -> Result) rethrows -> Result {
    return try base.withCString(body)
  }

  func withCString<Result, Encoding: Unicode.Encoding>(
    encodedAs targetEncoding: Encoding.Type,
    _ body: (UnsafePointer<Encoding.CodeUnit>) throws -> Result
  ) rethrows -> Result {
    return try base.withCString(encodedAs: targetEncoding, body)
  }
}
//===----------------------------------------------------------------------===//

public typealias ExpectedConcreteSlice = Substring
public typealias ExpectedStringFromString = String
let swift = 4

var Tests = TestSuite("StringCompatibility")

Tests.test("String/Range/Slice/ExpectedType/\(swift)") {
  var s = "hello world"
  var sub = s[s.startIndex ..< s.endIndex]
  var subsub = sub[s.startIndex ..< s.endIndex]

  expectType(String.self, &s)
  expectType(ExpectedConcreteSlice.self, &sub)
  expectType(ExpectedConcreteSlice.self, &subsub)
}

Tests.test("String/ClosedRange/Slice/ExpectedType/\(swift)") {
  var s = "hello world"
  let lastIndex = s.index(before:s.endIndex)
  var sub = s[s.startIndex ... lastIndex]
  var subsub = sub[s.startIndex ... lastIndex]

  expectType(String.self, &s)
  expectType(ExpectedConcreteSlice.self, &sub)
  expectType(ExpectedConcreteSlice.self, &subsub)
}

Tests.test("Substring/Range/Slice/ExpectedType/\(swift)") {
  let s = "hello world" as Substring
  var sub = s[s.startIndex ..< s.endIndex]
  var subsub = sub[s.startIndex ..< s.endIndex]

  // slicing a String in Swift 3 produces a String
  // but slicing a Substring should still produce a Substring
  expectType(Substring.self, &sub)
  expectType(Substring.self, &subsub)
}

Tests.test("Substring/ClosedRange/Slice/ExpectedType/\(swift)") {
  let s = "hello world" as Substring
  let lastIndex = s.index(before:s.endIndex)
  var sub = s[s.startIndex ... lastIndex]
  var subsub = sub[s.startIndex ... lastIndex]

  expectType(ExpectedConcreteSlice.self, &sub)
  expectType(ExpectedConcreteSlice.self, &subsub)
}

Tests.test("RangeReplaceable.init/generic/\(swift)") {
  func check<
    T: RangeReplaceableCollection, S: Collection
  >(_: T.Type, from source: S)
  where T.Element : Equatable, T.Element == S.Element
  {
    var r = T(source)
    expectType(T.self, &r)
    expectEqualSequence(Array(source), Array(r))
  }

  check(String.self, from: "a" as String)
  check(Substring.self, from: "b" as String)
  // FIXME: Why isn't this working?
  // check(MyString.self, from: "c" as String)
  
  check(String.self, from: "d" as Substring)
  check(Substring.self, from: "e" as Substring)
  // FIXME: Why isn't this working?
  // check(MyString.self, from: "f" as Substring)

  // FIXME: Why isn't this working?
  // check(String.self, from: "g" as MyString)
  // check(Substring.self, from: "h" as MyString)
  check(MyString.self, from: "i" as MyString)
}

Tests.test("String.init(someString)/default type/\(swift)") {
  var s = String("" as String)
  expectType(ExpectedStringFromString.self, &s)
}

Tests.test("Substring.init(someString)/default type/\(swift)") {
  var s = Substring("" as Substring)
  expectType(Substring.self, &s)
}

Tests.test("LosslessStringConvertible/generic/\(swift)") {
  func f<T : LosslessStringConvertible>(_ x: T.Type) {
    _ = T("")! // unwrapping optional should work in generic context
  }
  f(String.self)
}

public typealias ExpectedUTF8ViewSlice = String.UTF8View.SubSequence

Tests.test("UTF8ViewSlicing") {
  let s = "Hello, String.UTF8View slicing world!".utf8
  var slice = s[s.startIndex..<s.endIndex]
  expectType(ExpectedUTF8ViewSlice.self, &slice)
  _ = s[s.startIndex..<s.endIndex] as String.UTF8View.SubSequence
}

runAllTests()
