// RUN: %target-run-simple-swift | %FileCheck %s
// REQUIRES: executable_test

// rdar://problem/27616753
// XFAIL: *

// REQUIRES: objc_interop

import Foundation

@objc protocol SwiftObjCProto {}

class SwiftSuperPort : Port { }

class SwiftSubPort : SwiftSuperPort { }

class SwiftSuper { }
class SwiftSub : SwiftSuper { }

extension Port : SwiftObjCProto {}

var obj : AnyObject

func genericCast<T>(_ x: AnyObject, _: T.Type) -> T? {
  return x as? T
}
func genericCastObjCBound<T: NSObject>(_ x: AnyObject, _: T.Type) -> T? {
  return x as? T
}

// Test instance of Swift subclass of Objective-C class

obj = SwiftSubPort()
_ = obj as! SwiftSubPort
_ = obj as! SwiftSuperPort
_ = (obj as? Port)
_ = (obj as? NSObject)!
if (obj as? SwiftSubPort) == nil    { abort() }
if (obj as? SwiftSuperPort) ==  nil { abort() }
if (obj as? Port) == nil            { abort() }
if (obj as? NSObject) == nil        { abort() }
if (obj as? NSArray) != nil         { abort() }
if (obj as? SwiftSub) != nil        { abort() }
if (obj as? SwiftSuper) != nil      { abort() }

obj = SwiftSuperPort()
_ = obj as! SwiftSuperPort
_ = obj as! Port
_ = obj as! NSObject
if (obj as? SwiftSubPort) != nil   { abort() }
if (obj as? SwiftSuperPort) == nil { abort() }
if (obj as? Port) == nil           { abort() }
if (obj as? NSObject) == nil       { abort() }
if (obj as? NSArray) != nil        { abort() }
if (obj as? SwiftSub) != nil       { abort() }
if (obj as? SwiftSuper) != nil     { abort() }

// Test instance of Objective-C class that has Swift subclass

obj = Port()
_ = obj as! Port
_ = obj as! NSObject
if (obj as? SwiftSubPort) != nil    { abort() }
if (obj as? SwiftSuperPort) !=  nil { abort() }
if (obj as? Port) == nil            { abort() }
if (obj as? NSObject) == nil        { abort() }
if (obj as? NSArray) != nil         { abort() }
if (obj as? SwiftSub) != nil        { abort() }
if (obj as? SwiftSuper) != nil      { abort() }
if (obj as? SwiftObjCProto) == nil  { abort() }

obj = Port()
_ = genericCast(obj, Port.self)!
_ = genericCast(obj, NSObject.self)!
if genericCast(obj, SwiftSubPort.self) != nil   { abort() }
if genericCast(obj, SwiftSuperPort.self) != nil { abort() }
if genericCast(obj, Port.self) == nil           { abort() }
if genericCast(obj, NSObject.self) == nil       { abort() }
if genericCast(obj, NSArray.self) != nil        { abort() }
if genericCast(obj, SwiftSub.self) != nil       { abort() }
if genericCast(obj, SwiftSuper.self) != nil     { abort() }

_ = genericCastObjCBound(obj, Port.self)!
_ = genericCastObjCBound(obj, NSObject.self)!
if genericCastObjCBound(obj, SwiftSubPort.self) != nil   { abort() }
if genericCastObjCBound(obj, SwiftSuperPort.self) != nil { abort() }
if genericCastObjCBound(obj, Port.self) == nil           { abort() }
if genericCastObjCBound(obj, NSObject.self) == nil       { abort() }
if genericCastObjCBound(obj, NSArray.self) != nil        { abort() }

obj = NSObject()
_ = obj as! NSObject
if  (obj as? SwiftSubPort) != nil   { abort() }
if  (obj as? SwiftSuperPort) != nil { abort() }
if  (obj as? Port) != nil           { abort() }
if  (obj as? NSObject) == nil       { abort() }
if  (obj as? NSCopying) != nil      { abort() }
if  (obj as? NSArray) != nil        { abort() }
if  (obj as? SwiftSub) != nil       { abort() }
if  (obj as? SwiftSuper) != nil     { abort() }
if  (obj as? SwiftObjCProto) != nil { abort() }

// Test instance of a tagged pointer type

obj = NSNumber(value: 1234567)
_ = obj as! NSNumber
_ = obj as! NSValue
_ = obj as! NSObject
_ = obj as! NSCopying
if  (obj as? SwiftSubPort) != nil   { abort() }
if  (obj as? SwiftSuperPort) != nil { abort() }
if  (obj as? NSNumber) == nil       { abort() }
if  (obj as? NSValue) == nil        { abort() }
if  (obj as? NSObject) == nil       { abort() }
if  (obj as? NSCopying) == nil      { abort() }
if  (obj as? NSArray) != nil        { abort() }
if  (obj as? SwiftSub) != nil       { abort() }
if  (obj as? SwiftSuper) != nil     { abort() }

// Test instance of a Swift class with no Objective-C inheritance

obj = SwiftSub()
_ = obj as! SwiftSub
_ = obj as! SwiftSuper
if  (obj as? SwiftSubPort) != nil   { abort() }
if  (obj as? SwiftSuperPort) != nil { abort() }
if  (obj as? NSObject) != nil       { abort() }
if  (obj as? NSArray) != nil        { abort() }
if  (obj as? SwiftSub) == nil       { abort() }
if  (obj as? SwiftSuper) == nil     { abort() }

obj = SwiftSuper()
_ = obj as! SwiftSuper
if  (obj as? SwiftSubPort) != nil   { abort() }
if  (obj as? SwiftSuperPort) != nil { abort() }
if  (obj as? NSObject) != nil       { abort() }
if  (obj as? NSArray) != nil        { abort() }
if  (obj as? SwiftSub) != nil       { abort() }
if  (obj as? SwiftSuper) == nil     { abort() }

// Test optional and non-optional bridged conversions
var ao: AnyObject = "s"
ao as! String
ao is String

var auo: AnyObject! = "s"
var s: String = auo as! String

var auoo: AnyObject? = "s"
auoo! as? String

// Test bridged casts.
// CHECK: Downcast to hello
obj = NSString(string: "hello")
if let str = obj as? String {
  print("Downcast to \(str)")
} else {
  print("Not a string?")
}

// Forced cast using context
// CHECK-NEXT: Forced to string hello
let forcedStr: String = obj as! String
print("Forced to string \(forcedStr)")

// CHECK-NEXT: Downcast to Swift
var objOpt: AnyObject? = NSString(string: "Swift")
if let str = objOpt as? String {
  print("Downcast to \(str)")
} else {
  print("Not a string?")
}

// Forced cast using context
// CHECK-NEXT: Forced to string Swift
let forcedStrOpt: String = objOpt as! String
print("Forced to string \(forcedStrOpt)")

// CHECK-NEXT: Downcast to world
var objImplicitOpt: AnyObject! = NSString(string: "world")
if let str = objImplicitOpt as? String {
  print("Downcast to \(str)")
} else {
  print("Not a string?")
}

// Forced cast using context
// CHECK-NEXT: Forced to string world
let forcedStrImplicitOpt: String = objImplicitOpt as! String
print("Forced to string \(forcedStrImplicitOpt)")

// CHECK-NEXT: Downcast correctly failed due to nil
objOpt = nil
if let str = objOpt as? String {
  print("Downcast should not succeed for nil")
} else {
  print("Downcast correctly failed due to nil")
}

// CHECK-NEXT: Downcast correctly failed due to nil
objImplicitOpt = nil
if let str = objImplicitOpt as? String {
  print("Downcast should not succeed for nil")
} else {
  print("Downcast correctly failed due to nil")
}

// Test bridged "isa" checks.
// CHECK: It's a string!
obj = NSString(string: "hello")
if obj is String {
  print("It's a string!")
} else {
  print("Not a string?")
}

// CHECK-NEXT: It's a string!
objOpt = NSString(string: "Swift")
if objOpt is String {
  print("It's a string!")
} else {
  print("Not a string?")
}

// CHECK-NEXT: It's a string!
objImplicitOpt = NSString(string: "world")
if objImplicitOpt is String {
  print("It's a string!")
} else {
  print("Not a string?")
}

// CHECK-NEXT: Isa correctly failed due to nil
objOpt = nil
if objOpt is String {
  print("Isa should not succeed for nil")
} else {
  print("Isa correctly failed due to nil")
}

// CHECK-NEXT: Isa correctly failed due to nil
objImplicitOpt = nil
if objImplicitOpt is String {
  print("Isa should not succeed for nil")
} else {
  print("Isa correctly failed due to nil")
}

let words = ["Hello", "Swift", "World"]

// CHECK-NEXT: Object-to-bridged-array cast produced ["Hello", "Swift", "World"]
obj = words as AnyObject
if let strArr = obj as? [String] {
  print("Object-to-bridged-array cast produced \(strArr)")
} else {
  print("Object-to-bridged-array cast failed")
}

// Check downcast from the bridged type itself.
// CHECK-NEXT: NSArray-to-bridged-array cast produced ["Hello", "Swift", "World"]
var nsarr = words as NSArray
if let strArr = nsarr as? [String] {
  print("NSArray-to-bridged-array cast produced \(strArr)")
} else {
  print("NSArray-to-bridged-array cast failed")
}

// CHECK-NEXT: NSArray?-to-bridged-array cast produced ["Hello", "Swift", "World"]
var nsarrOpt = words as NSArray?
if let strArr = nsarrOpt as? [String] {
  print("NSArray?-to-bridged-array cast produced \(strArr)")
} else {
  print("NSArray?-to-bridged-array cast failed")
}

// CHECK-NEXT: NSArray!-to-bridged-array cast produced ["Hello", "Swift", "World"]
var nsarrImplicitOpt = words as NSArray!
if let strArr = nsarrImplicitOpt as? [String] {
  print("NSArray!-to-bridged-array cast produced \(strArr)")
} else {
  print("NSArray!-to-bridged-array cast failed")
}

// Check downcast from a superclass of the bridged type.
// CHECK-NEXT: NSObject-to-bridged-array cast produced ["Hello", "Swift", "World"]
var nsobj: NSObject = nsarr
if let strArr = nsobj as? [String] {
  print("NSObject-to-bridged-array cast produced \(strArr)")
} else {
  print("NSObject-to-bridged-array cast failed")
}

// CHECK-NEXT: NSArray is [String]
if nsarr is [String] {
  print("NSArray is [String]")
} else {
  print("NSArray is not a [String]")
}

// CHECK-NEXT: NSArray? is [String]
if nsarrOpt is [String] {
  print("NSArray? is [String]")
} else {
  print("NSArray? is not a [String]")
}

// CHECK-NEXT: NSArray! is [String]
if nsarrImplicitOpt is [String] {
  print("NSArray! is [String]")
} else {
  print("NSArray! is not a [String]")
}

// CHECK-NEXT: NSObject is [String]
if nsobj is [String] {
  print("NSObject is [String]")
} else {
  print("NSObject is not a [String]")
}

// Forced downcast based on context.
// CHECK-NEXT: Forced to string array ["Hello", "Swift", "World"]
var forcedStrArray: [String] = obj as! [String]
print("Forced to string array \(forcedStrArray)")

// CHECK-NEXT: Forced NSArray to string array ["Hello", "Swift", "World"]
forcedStrArray = nsarr as! [String]
print("Forced NSArray to string array \(forcedStrArray)")

// CHECK-NEXT: Forced NSArray? to string array ["Hello", "Swift", "World"]
forcedStrArray = nsarrOpt as! [String]
print("Forced NSArray? to string array \(forcedStrArray)")

// CHECK-NEXT: Forced NSArray! to string array ["Hello", "Swift", "World"]
forcedStrArray = nsarrImplicitOpt as! [String]
print("Forced NSArray! to string array \(forcedStrArray)")

// CHECK-NEXT: Object-to-array cast produced [Hello, Swift, World]
if let strArr = obj as? [NSString] {
  print("Object-to-array cast produced \(strArr)")
} else {
  print("Object-to-array cast failed")
}

// CHECK-NEXT: Object-to-bridged-array cast failed due to bridge mismatch
if let strArr = obj as? [Int] {
  print("Object-to-bridged-array cast should not have succeeded")
} else {
  print("Object-to-bridged-array cast failed due to bridge mismatch")
}

// CHECK-NEXT: Array of strings is not an array of ints
if obj is [Int] {
  print("Array of strings should not be an array of ints!")
} else {
  print("Array of strings is not an array of ints")
}

// Implicitly unwrapped optional of object to array casts.
// CHECK-NEXT: Object-to-bridged-array cast produced ["Hello", "Swift", "World"]
objOpt = words as AnyObject?
if let strArr = objOpt as? [String] {
  print("Object-to-bridged-array cast produced \(strArr)")
} else {
  print("Object-to-bridged-array cast failed")
}

// Forced downcast based on context.
// CHECK-NEXT: Forced to string array ["Hello", "Swift", "World"]
let forcedStrArrayOpt: [String] = objOpt as! [String]
print("Forced to string array \(forcedStrArrayOpt)")

// CHECK-NEXT: Object-to-array cast produced [Hello, Swift, World]
if let strArr = objOpt as? [NSString] {
  print("Object-to-array cast produced \(strArr)")
} else {
  print("Object-to-array cast failed")
}

// CHECK: Object-to-bridged-array cast failed due to bridge mismatch
if let intArr = objOpt as? [Int] {
  print("Object-to-bridged-array cast should not have succeeded")
} else {
  print("Object-to-bridged-array cast failed due to bridge mismatch")
}

// CHECK: Object-to-bridged-array cast failed due to nil
objOpt = nil
if let strArr = objOpt as? [String] {
  print("Cast from nil succeeded?")
} else {
  print("Object-to-bridged-array cast failed due to nil")
}

// Optional of object to array casts.
// CHECK-NEXT: Object-to-bridged-array cast produced ["Hello", "Swift", "World"]
objImplicitOpt = words as AnyObject!
if let strArr = objImplicitOpt as? [String] {
  print("Object-to-bridged-array cast produced \(strArr)")
} else {
  print("Object-to-bridged-array cast failed")
}

// Forced downcast based on context.
// CHECK-NEXT: Forced to string array ["Hello", "Swift", "World"]
let forcedStrArrayImplicitOpt: [String] = objImplicitOpt as! [String]
print("Forced to string array \(forcedStrArrayImplicitOpt)")

// CHECK-NEXT: Object-to-array cast produced [Hello, Swift, World]
if let strArr = objImplicitOpt as? [NSString] {
  print("Object-to-array cast produced \(strArr)")
} else {
  print("Object-to-array cast failed")
}

// CHECK: Object-to-bridged-array cast failed due to bridge mismatch
if let intArr = objImplicitOpt as? [Int] {
  print("Object-to-bridged-array cast should not have succeeded")
} else {
  print("Object-to-bridged-array cast failed due to bridge mismatch")
}

// CHECK: Object-to-bridged-array cast failed due to nil
objImplicitOpt = nil
if let strArr = objImplicitOpt as? [String] {
  print("Cast from nil succeeded?")
} else {
  print("Object-to-bridged-array cast failed due to nil")
}

// Casting an array of numbers to different numbers.
// CHECK: Numbers-as-doubles cast produces [3.9375, 2.71828, 0.0]
obj = ([3.9375, 2.71828, 0] as [Double]) as AnyObject
if let doubleArr = obj as? [Double] {
  print(MemoryLayout<Double>.size)
  print("Numbers-as-doubles cast produces \(doubleArr)")
} else {
  print("Numbers-as-doubles failed")
}

// CHECK: Numbers-as-floats cast produces [3.9375, 2.71828{{.*}}, 0.0]
if let floatArr = obj as? [Float] {
  print(MemoryLayout<Float>.size)
  print("Numbers-as-floats cast produces \(floatArr)")
} else {
  print("Numbers-as-floats failed")
}

// CHECK: Numbers-as-ints cast produces [3, 2, 0]
if let intArr = obj as? [Int] {
  print("Numbers-as-ints cast produces \(intArr)")
} else {
  print("Numbers-as-ints failed")
}

// CHECK: Numbers-as-bools cast produces [true, true, false]
if let boolArr = obj as? [Bool] {
  print("Numbers-as-bools cast produces \(boolArr)")
} else {
  print("Numbers-as-bools failed")
}

class Base : NSObject { 
  override var description: String {
    return "Base"
  }
}
class Derived : Base { 
  override var description: String {
    return "Derived"
  }
}

// CHECK: Array-of-base cast produces [Derived, Derived, Base]
obj = [Derived(), Derived(), Base()]
if let baseArr = obj as? [Base] {
  print("Array-of-base cast produces \(baseArr)")
} else {
  print("Not an array of base")
}

// CHECK: Not an array of derived
if let derivedArr = obj as? [Derived] {
  print("Array-of-derived cast produces \(derivedArr)")
} else {
  print("Not an array of derived")
}

// CHECK: Dictionary-of-base-base cast produces
obj = [Derived() : Derived(), Derived() : Base(), Derived() : Derived() ] as AnyObject
if let baseDict = obj as? Dictionary<Base, Base> {
  print("Dictionary-of-base-base cast produces \(baseDict)")
} else {
  print("Not a dictionary of base/base")
}

// CHECK: Dictionary-of-derived-base cast produces
if let baseDict = obj as? Dictionary<Derived, Base> {
  print("Dictionary-of-derived-base cast produces \(baseDict)")
} else {
  print("Not a dictionary of derived/base")
}

// CHECK: Not a dictionary of derived/derived
if let dict = obj as? Dictionary<Derived, Derived> {
  print("Dictionary-of-derived-derived cast produces \(dict)")
} else {
  print("Not a dictionary of derived/derived")
}

let strArray: AnyObject = ["hello", "world"]
let intArray: AnyObject = [1, 2, 3]
let dictArray: AnyObject = [["hello" : 1, "world" : 2], 
                            ["swift" : 1, "speedy" : 2]]

// CHECK: Dictionary<String, AnyObject> is
obj = ["a" : strArray, "b" : intArray, "c": dictArray]
if let dict = obj as? Dictionary<String, [AnyObject]> {
  print("Dictionary<String, AnyObject> is \(dict)")
} else {
  print("Not a Dictionary<String, AnyObject>")
}

// CHECK: Not a Dictionary<String, String>
if let dict = obj as? Dictionary<String, [String]> {
  print("Dictionary<String, String> is \(dict)")
} else {
  print("Not a Dictionary<String, String>")
}

// CHECK: Not a Dictionary<String, Int>
if let dict = obj as? Dictionary<String, [Int]> {
  print("Dictionary<String, Int> is \(dict)")
} else {
  print("Not a Dictionary<String, Int>")
}

// CHECK: [Dictionary<String, Int>] is 
obj = dictArray
if let array = obj as? [Dictionary<String, Int>] {
  print("[Dictionary<String, Int>] is \(array)")
} else {
  print("Not a [Dictionary<String, Int>]")
}

// CHECK: Not a [Dictionary<String, String>]
if let array = obj as? [Dictionary<String, String>] {
  print("[Dictionary<String, String>] is \(array)")
} else {
  print("Not a [Dictionary<String, String>]")
}

// CHECK: Dictionary<String, [Dictionary<String, Int>]> is ["a": [
obj = ["a" : dictArray]
if let dict = obj as? Dictionary<String, [Dictionary<String, Int>]> {
  print("Dictionary<String, [Dictionary<String, Int>]> is \(dict)")
} else {
  print("Not a Dictionary<String, [Dictionary<String, Int>]>")
}

// CHECK: Not a Dictionary<String, [Dictionary<String, String>]>
if let dict = obj as? Dictionary<String, [Dictionary<String, String>]> {
  print("Dictionary<String, [Dictionary<String, String>]> is \(dict)")
} else {
  print("Not a Dictionary<String, [Dictionary<String, String>]>")
}

// CHECK: [Dictionary<String, [Dictionary<String, Int>]>] is
obj = [obj, obj, obj]
if let array = obj as? [Dictionary<String, [Dictionary<String, Int>]>] {
  print("[Dictionary<String, [Dictionary<String, Int>]>] is \(array)")
} else {
  print("Not a [Dictionary<String, [Dictionary<String, Int>]>]")
}

// CHECK: Not a Dictionary<String, [Dictionary<String, String>]>[]
if let array = obj as? Dictionary<String, [Dictionary<String, String>]> {
  print("Dictionary<String, [Dictionary<String, String>]>[] is \(array)")
} else {
  print("Not a Dictionary<String, [Dictionary<String, String>]>[]")
}

// Helper function that downcasts 
func downcastToStringArrayOptOpt(_ obj: AnyObject???!) {
  if let strArrOptOpt = obj as? [String]?? {
    if let strArrOpt = strArrOptOpt {
      if let strArr = strArrOpt {
        print("some(some(some(\(strArr))))")
      } else {
        print("some(some(none))")
      }
    } else {
      print("some(none)")
    }
  } else {
    print("none")
  }
}

// CHECK: {{^}}some(some(some(["a", "b", "c"]))){{$}}
var objOptOpt: AnyObject?? = .some(.some(["a", "b", "c"]))
downcastToStringArrayOptOpt(objOptOpt)

// CHECK: {{^}}none{{$}}
objOptOpt = .some(.some([1 : "hello", 2 : "swift", 3 : "world"]))
downcastToStringArrayOptOpt(objOptOpt)

// CHECK: {{^}}none{{$}}
objOptOpt = .some(.some([1, 2, 3]))
downcastToStringArrayOptOpt(objOptOpt)

print("ok")  // CHECK: ok
