//===--- ArrayBridge.swift - Tests of Array casting and bridging ----------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 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
//
//===----------------------------------------------------------------------===//

// RUN: %empty-directory(%t)
//
// RUN: %gyb %s -o %t/ArrayBridge.swift
// RUN: %target-clang %S/Inputs/ArrayBridge/ArrayBridge.m -c -o %t/ArrayBridgeObjC.o -g
// RUN:  %line-directive %t/ArrayBridge.swift -- %target-build-swift %t/ArrayBridge.swift -I %S/Inputs/ArrayBridge/ -Xlinker %t/ArrayBridgeObjC.o -o %t/ArrayBridge -swift-version 4.2 --

// RUN: %target-codesign %t/ArrayBridge
// RUN: %target-run %t/ArrayBridge
// REQUIRES: executable_test
// REQUIRES: objc_interop

// Temporarily disabled on arm64 (rdar://128522114)
// UNSUPPORTED: CPU=arm64

// And on older OS (rdar://132941962)
// UNSUPPORTED: use_os_stdlib

import Foundation
import ArrayBridgeObjC
import StdlibUnittest
let tests = TestSuite("ArrayBridge")

var trackedCount = 0
var nextBaseSerialNumber = 0

@objc protocol Fooable {
  func foo()
}

@objc protocol Barable {
  func bar()
}

@objc protocol Bazable {
  func baz()
}

/// A type that will be bridged verbatim to Objective-C
class Base : NSObject, Fooable {
  func foo() { }

  required init(_ value: Int) {
    trackedCount += 1
    nextBaseSerialNumber += 1
    serialNumber = nextBaseSerialNumber
    self.value = value
  }

  deinit {
    assert(serialNumber > 0, "double destruction!")
    trackedCount -= 1
    serialNumber = -serialNumber
  }

  override func isEqual(_ other: Any?) -> Bool {
    return (other as? Base)?.value == self.value
  }

  var value: Int
  var serialNumber: Int
}

class Subclass : Base, Barable {
  func bar() { }
}

var bridgeFromOperationCount = 0
var bridgeToOperationCount = 0

/// A value type that's bridged using _ObjectiveCBridgeable
struct BridgeableValue : _ObjectiveCBridgeable, Equatable {
  func _bridgeToObjectiveC() -> Subclass {
    bridgeToOperationCount += 1
    return Subclass(trak.value)
  }

  static func _forceBridgeFromObjectiveC(
    _ x: Subclass,
    result: inout BridgeableValue?
  ) {
    assert(x.value >= 0, "not bridged")
    bridgeFromOperationCount += 1
    result = BridgeableValue(x.value)
  }

  static func _conditionallyBridgeFromObjectiveC(
    _ x: Subclass,
    result: inout BridgeableValue?
  ) -> Bool {
    if x.value >= 0 {
      result = BridgeableValue(x.value)
      return true
    }

    result = nil
    return false
  }

  static func _unconditionallyBridgeFromObjectiveC(_ source: Subclass?)
      -> BridgeableValue {
    var result: BridgeableValue?
    _forceBridgeFromObjectiveC(source!, result: &result)
    return result!
  }

  init(_ value: Int) {
    self.trak = Base(value)
  }

  static func resetStats() {
    bridgeFromOperationCount = 0
    bridgeToOperationCount = 0
  }

  var trak: Base
}

func == (lhs: BridgeableValue, rhs: BridgeableValue) -> Bool {
  return lhs.trak.value == rhs.trak.value
}

// A class used to test various Objective-C thunks.
class Thunks : NSObject {
  @objc func createSubclass(_ value: Int) -> AnyObject {
    return Subclass(value)
  }

  @objc func acceptSubclassArray(
    _ x: [Subclass],
    expecting expected: NSArray
  ) {
    expectEqualSequence(
      expected.lazy.map { ObjectIdentifier($0 as AnyObject) },
      x.lazy.map { ObjectIdentifier($0 as AnyObject) }
    )
  }

  @objc func produceSubclassArray(
    _ expectations: NSMutableArray
  ) -> [Subclass] {
    var array: [Subclass] = []
    for i in 0..<5 {
      array.append(Subclass(i))
      expectations.add(array[i])
    }
    return array
  }

  @objc func checkProducedSubclassArray(
    _ produced: NSArray, expecting expected: NSArray
  ) {
    expectEqualSequence(
      expected.lazy.map { ObjectIdentifier($0 as AnyObject) },
      produced.lazy.map { ObjectIdentifier($0 as AnyObject) }
    )
  }

  @objc func acceptBridgeableValueArray(_ raw: NSArray) {
    let x = raw as! [BridgeableValue]
    expectEqualSequence(
      raw.lazy.map { $0 as! BridgeableValue },
      x
    )
  }

  @objc func produceBridgeableValueArray() -> NSArray {
    var array: [BridgeableValue] = []
    for i in 0..<5 {
      array.append(BridgeableValue(i))
    }
    return array as NSArray
  }

  @objc func checkProducedBridgeableValueArray(_ produced: NSArray) {
    expectEqualSequence(
      0..<5,
      produced.lazy.map { ($0 as! Subclass).value })
  }
}


//===--- Bridged Verbatim -------------------------------------------------===//
// Base is "bridged verbatim"
//===----------------------------------------------------------------------===//

tests.test("testBridgedVerbatim") {
  nextBaseSerialNumber = 0
  let bases: [Base] = [Base(100), Base(200), Base(300)]

  //===--- Implicit conversion to/from NSArray ------------------------------===//

  let basesConvertedToNSArray = bases as NSArray
  let firstBase = basesConvertedToNSArray.object(at: 0) as! Base
  expectEqual(100, firstBase.value)
  expectEqual(1, firstBase.serialNumber)

  // Verify that NSArray class methods are inherited by a Swift bridging class.
  let className = String(reflecting: type(of: basesConvertedToNSArray))
  expectTrue(className.hasPrefix("Swift._ContiguousArrayStorage"))
  expectTrue(type(of: basesConvertedToNSArray).supportsSecureCoding)

  //===--- Up- and Down-casts -----------------------------------------------===//
  var subclass: [Subclass] = [Subclass(11), Subclass(22)]
  let subclass0 = subclass

  // upcast is implicit
  let subclassAsBases: [Base] = subclass
  expectEqual(subclass.count, subclassAsBases.count)
  for (x, y) in zip(subclass, subclassAsBases) {
    expectTrue(x === y)
  }

  // Arrays are logically distinct after upcast
  subclass[0] = Subclass(33)

  expectEqual([Subclass(33), Subclass(22)], subclass)
  expectEqual([Subclass(11), Subclass(22)], subclassAsBases)

  expectEqual(subclass0, subclassAsBases as! [Subclass])

}

% for Any in 'Any', 'AnyObject':

tests.test("Another/${Any}") {
  // Create an ordinary NSArray, not a native one
  let nsArrayOfBase: NSArray = NSArray(object: Base(42))

  // NSArray can be unconditionally cast to [${Any}]...

  let nsArrayOfBaseConvertedToAnyArray = nsArrayOfBase
  % if Any == 'Any':
  // FIXME: nsArrayOfBase as [Any] doesn't
  // typecheck.
  as [AnyObject]
  % end
  as [${Any}]

  // Capture the representation of the first element
  let base42: ObjectIdentifier
  do {
    let b = nsArrayOfBase.object(at: 0) as! Base
    expectEqual(42, b.value)
    base42 = ObjectIdentifier(b)
  }

  // ...with the same elements
  expectEqual(
    base42,
    ObjectIdentifier(nsArrayOfBaseConvertedToAnyArray[0] as! Base))

  let subclassInBaseBuffer: [Base] = [Subclass(44), Subclass(55)]
  let subclass2 = subclassInBaseBuffer

  // Explicit downcast-ability is based on element type, not buffer type
  expectNotNil(subclassInBaseBuffer as? [Subclass])

  // We can up-cast to array of Any
  let subclassAsAnyArray: [${Any}] = subclassInBaseBuffer
  expectEqual(subclass2, subclassAsAnyArray.map { $0 as! Base })

  let downcastBackToBase = subclassAsAnyArray as? [Base]
  expectNotNil(downcastBackToBase)

  if let downcastBackToSubclass = expectNotNil(subclassAsAnyArray as? [Subclass]) {
    expectEqual(subclass2, downcastBackToSubclass)
  }

  if let downcastToProtocols = expectNotNil(subclassAsAnyArray as? [Fooable]) {
    expectEqual(subclass2, downcastToProtocols.map { $0 as! Subclass })
  }

  if let downcastToProtocols = expectNotNil(subclassAsAnyArray as? [Barable]) {
    expectEqual(subclass2, downcastToProtocols.map { $0 as! Subclass })
  }

  if let downcastToProtocols = expectNotNil(subclassAsAnyArray as? [Barable & Fooable]) {
    expectEqual(subclass2, downcastToProtocols.map { $0 as! Subclass })
  }

  expectNil(subclassAsAnyArray as? [Barable & Bazable])
}

//===--- Explicitly Bridged -----------------------------------------------===//
// BridgeableValue conforms to _ObjectiveCBridgeable
//===----------------------------------------------------------------------===//
tests.test("testExplicitlyBridged/${Any}") {

  let bridgeableValues = [BridgeableValue(42), BridgeableValue(17)]

  let bridgeableValuesAsNSArray = bridgeableValues as NSArray
  expectEqual(2, bridgeableValuesAsNSArray.count)
  expectEqual(42, (bridgeableValuesAsNSArray[0] as! Subclass).value)
  expectEqual(17, (bridgeableValuesAsNSArray[1] as! Subclass).value)

  // Make sure we can bridge back.
  let roundTrippedValues = Swift._forceBridgeFromObjectiveC(
    bridgeableValuesAsNSArray, [BridgeableValue].self)

  // Expect equal values...
  expectEqualSequence(bridgeableValues, roundTrippedValues)
  // ...with the same identity for some reason? FIXME: understand why.
  expectFalse(
    zip(bridgeableValues, roundTrippedValues).contains { $0.trak !== $1.trak })

  // Make a real Cocoa NSArray of these...
  let cocoaBridgeableValues = NSArray(
    array: bridgeableValuesAsNSArray
    %if Any == 'Any':
    // FIXME: should just be "as [Any]" but the typechecker doesn't allow it
    // yet.
    as [AnyObject] as [Any] as [AnyObject]
    %else:
    as [${Any}]
    %end
  )

  // ...and bridge *that* back
  let bridgedBackSwifts = Swift._forceBridgeFromObjectiveC(
    cocoaBridgeableValues, [BridgeableValue].self)

  // Expect equal, but distinctly created instances
  expectEqualSequence(bridgeableValues, bridgedBackSwifts)
  expectFalse(
    zip(bridgedBackSwifts, roundTrippedValues).contains { $0.trak === $1.trak })
  expectFalse(
    zip(bridgedBackSwifts, bridgeableValues).contains { $0.trak === $1.trak })

  let expectedSubclasses = [Subclass(42), Subclass(17)]
  let expectedBases = expectedSubclasses.lazy.map { $0 as Base }

  // Up-casts.
  let bridgeableValuesAsSubclasses = bridgeableValues as [Subclass]
  expectEqualSequence(expectedSubclasses, bridgeableValuesAsSubclasses)

  let bridgeableValuesAsBases = bridgeableValues as [Base]
  expectEqualSequence(expectedBases, bridgeableValuesAsBases)

  let bridgeableValuesAsAnys = bridgeableValues as [Any]
  expectEqualSequence(
    expectedBases,
    bridgeableValuesAsAnys.lazy.map { $0 as! Base })

  // Downcasts of non-verbatim bridged value types to objects.
  do {
    let downcasted = bridgeableValues as [Subclass]
    expectEqualSequence(expectedSubclasses, downcasted)
  }

  do {
    let downcasted = bridgeableValues as [Base]
    expectEqualSequence(expectedBases, downcasted)
  }

  do {
    let downcasted = bridgeableValues as [${Any}]
    expectEqualSequence(expectedBases, downcasted.map { $0 as! Base })
  }

  // Downcasts of up-casted arrays.
  if let downcasted = expectNotNil(
    bridgeableValuesAsAnys as? [Subclass]
  ) {
    expectEqualSequence(expectedSubclasses, downcasted)
  }

  if let downcasted = bridgeableValuesAsAnys as? [Base] {
    expectEqualSequence(expectedBases, downcasted)
  }

  // Downcast of Cocoa array to an array of classes.
  let wrappedCocoaBridgeableValues = cocoaBridgeableValues
  %if Any == 'Any':
  // FIXME: should just be "as [Any]" but typechecker doesn't allow it
  // yet.
  as [AnyObject]
  %end
  as [${Any}]

  if let downcasted = wrappedCocoaBridgeableValues as? [Subclass] {
    expectEqualSequence(expectedSubclasses, downcasted)
  }

  // Downcast of Cocoa array to an array of values.
  if let downcasted = wrappedCocoaBridgeableValues as? [BridgeableValue] {
    expectEqualSequence(bridgeableValues, downcasted)
  }

  // Downcast of Cocoa array to an array of strings (which should fail)
  expectNil(wrappedCocoaBridgeableValues as? [String])

  // Downcast from an implicitly unwrapped optional array of Anys.
  var wrappedCocoaBridgeableValuesIUO: [${Any}]! = wrappedCocoaBridgeableValues
  if let downcasted = wrappedCocoaBridgeableValuesIUO as? [BridgeableValue] {
    expectEqualSequence(bridgeableValues, downcasted)
  }

  // Downcast from a nil implicitly unwrapped optional array of Anys.
  wrappedCocoaBridgeableValuesIUO = nil
  expectNil(wrappedCocoaBridgeableValuesIUO as? [BridgeableValue])

  // Downcast from an optional array of Anys.
  var wrappedCocoaBridgeableValuesOpt: [${Any}]? = wrappedCocoaBridgeableValues
  if let downcasted = wrappedCocoaBridgeableValuesOpt as? [BridgeableValue] {
    expectEqualSequence(bridgeableValues, downcasted)
  }

  // Downcast from a nil optional array of Anys.
  wrappedCocoaBridgeableValuesOpt = nil
  expectNil(wrappedCocoaBridgeableValuesOpt as? [BridgeableValue])
}
% end

tests.test("testThunks") {
  testSubclass(Thunks())
  testBridgeableValue(Thunks())
}

tests.test("testHKTFilter") {
  let base = ["hello", "world"]
  let result = testHKTFilter(base) as! NSArray as! [String]
  expectEqual(result, ["hello"])
}

tests.test("testRoundTrip") {
  class Test : NSObject {
    @objc dynamic func call(_ array: NSArray) -> NSArray {

      let result = array as! [BridgeableValue]
      expectEqual(0, bridgeFromOperationCount)
      expectEqual(0, bridgeToOperationCount)

      // Clear out the stats before returning array
      BridgeableValue.resetStats()
      return result as NSArray
    }
  }

  let test = Test()

  let array = [
    BridgeableValue(10), BridgeableValue(20), BridgeableValue(30),
    BridgeableValue(40), BridgeableValue(50) ]

  BridgeableValue.resetStats()
  _ = test.call(array as NSArray)

  expectEqual(0, bridgeFromOperationCount)
  expectEqual(0, bridgeToOperationCount)
}

//===--- Non-bridging -----------------------------------------------------===//
// X is not bridged to Objective-C
//===----------------------------------------------------------------------===//

struct X {}

tests.test("testMutableArray") {
  let m = NSMutableArray(array: ["fu", "bar", "buzz"])
  let a = m as NSArray as! [NSString]

  // Create distinct array storage with a copy of the elements in a
  let aCopy = Array(a)

  // Modify the original mutable array
  m.add("goop")

  // Check that our [NSString] is unaffected
  expectEqualSequence(aCopy, a)
}

tests.test("rdar://problem/27905230") {
  // Casting an NSArray to Array<Any> would trap because of an erroneous
  // precondition.
  let dict = RDar27905230.mutableDictionaryOfMutableLists()!
  let arr = dict["list"]!
  expectEqual(arr[0] as! NSNull, NSNull())
  expectEqual(arr[1] as! String, "")
  expectEqual(arr[2] as! Int, 1)
  expectEqual(arr[3] as! Bool, true)
  expectEqual((arr[4] as! NSValue).rangeValue.location, 0)
  expectEqual((arr[4] as! NSValue).rangeValue.length, 1)
  expectEqual(arr[5] as! Date, Date(timeIntervalSince1970: 0))
}

tests.test("verbatimBridged/Base/withUnsafeBufferPointer") {
  // https://github.com/apple/swift/issues/57014
  // This tests a bad precondition that was fixed in
  // https://github.com/apple/swift/pull/37960
  guard #available(SwiftStdlib 5.5, *) else { return }
  let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
  let b = a as! [Base]
  let success: Bool = b.withUnsafeBufferPointer { buffer in
    expectEqual(buffer.count, 4)
    guard buffer.count == 4 else { return false }
    expectEqual(buffer[0].value, 0)
    expectEqual(buffer[1].value, 1)
    expectEqual(buffer[2].value, 2)
    expectEqual(buffer[3].value, 3)
    return true
  }
  expectTrue(success)
}

tests.test("verbatimBridged/AnyObject/withUnsafeBufferPointer") {
  // https://github.com/apple/swift/issues/57014
  // This tests a bad precondition that was fixed in
  // https://github.com/apple/swift/pull/37960
  guard #available(SwiftStdlib 5.5, *) else { return }
  let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
  let b = a as [AnyObject]
  let success: Bool = b.withUnsafeBufferPointer { buffer in
    expectEqual(buffer.count, 4)
    guard buffer.count == 4 else { return false }
    expectEqual((buffer[0] as? Base)?.value, 0)
    expectEqual((buffer[1] as? Base)?.value, 1)
    expectEqual((buffer[2] as? Base)?.value, 2)
    expectEqual((buffer[3] as? Base)?.value, 3)
    return true
  }
  expectTrue(success)
}

tests.test("verbatimBridged/Base/withUnsafeMutableBufferPointer") {
  let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
  var b = a as! [Base]
  let success: Bool = b.withUnsafeMutableBufferPointer { buffer in
    expectEqual(buffer.count, 4)
    guard buffer.count == 4 else { return false }
    expectEqual(buffer[0].value, 0)
    expectEqual(buffer[1].value, 1)
    expectEqual(buffer[2].value, 2)
    expectEqual(buffer[3].value, 3)
    buffer[0] = Base(4)
    return true
  }
  expectTrue(success)
  expectEqual(b[0].value, 4)
}

tests.test("verbatimBridged/AnyObject/withUnsafeMutableBufferPointer") {
  let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
  var b = a as [AnyObject]
  let success: Bool = b.withUnsafeMutableBufferPointer { buffer in
    expectEqual(buffer.count, 4)
    guard buffer.count == 4 else { return false }
    expectEqual((buffer[0] as? Base)?.value, 0)
    expectEqual((buffer[1] as? Base)?.value, 1)
    expectEqual((buffer[2] as? Base)?.value, 2)
    expectEqual((buffer[3] as? Base)?.value, 3)
    buffer[0] = Base(4)
    return true
  }
  expectTrue(success)
  expectEqual((b[0] as? Base)?.value, 4)
}

runAllTests()
