// RUN: %empty-directory(%t)
//
// RUN: %target-clang %S/Inputs/Mirror/Mirror.mm -c -o %t/Mirror.mm.o -g
// RUN: %target-build-swift -parse-stdlib %s -module-name Reflection -I %S/Inputs/Mirror/ -Xlinker %t/Mirror.mm.o -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %{python} %S/../Inputs/timeout.py 360 %target-run %t/a.out %S/Inputs/shuffle.jpg | %FileCheck %s
// FIXME: timeout wrapper is necessary because the ASan test runs for hours

// REQUIRES: executable_test
// REQUIRES: objc_interop

//
// DO NOT add more tests to this file.  Add them to test/1_stdlib/Runtime.swift.
//


import Swift
import Foundation
import simd

#if canImport(AppKit)
import AppKit

typealias OSImage = NSImage
typealias OSColor = NSColor
typealias OSBezierPath = NSBezierPath
#elseif canImport(UIKit)
import UIKit

typealias OSImage = UIImage
typealias OSColor = UIColor
typealias OSBezierPath = UIBezierPath
#else
#error("Platform does not support UIKit or AppKit!")
#endif

// Check ObjC mirror implementation.
// CHECK-LABEL: ObjC:
print("ObjC:")
// CHECK-NEXT:  <NSObject: {{0x[0-9a-f]+}}>
dump(NSObject())

// CHECK-LABEL: ObjC subclass:
print("ObjC subclass:")
// CHECK-NEXT: woozle wuzzle
dump("woozle wuzzle" as NSString)

// Test a mixed Swift-ObjC hierarchy.
class NSGood : NSObject {
  let x: Int = 22
}
class NSBetter : NSGood {
  let y: String = "333"
}

// CHECK-LABEL: Swift ObjC subclass:
// CHECK-NEXT:    <Reflection.NSBetter: {{0x[0-9a-f]+}}> #0
// CHECK-NEXT:      super: Reflection.NSGood
// CHECK-NEXT:        super: NSObject
print("Swift ObjC subclass:")
dump(NSBetter())

// CHECK-LABEL: ObjC quick look objects:
print("ObjC quick look objects:")

// CHECK-LABEL: ObjC enums:
print("ObjC enums:")

// CHECK-NEXT: We cannot reflect NSComparisonResult yet
print("We cannot reflect \(ComparisonResult.orderedAscending) yet")

// Don't crash when introspecting framework types such as NSURL.
// <rdar://problem/16592777>
// CHECK-LABEL: NSURL:
// CHECK-NEXT:    file:///Volumes/
// CHECK-NEXT:    - super: NSObject
print("NSURL:")
dump(NSURL(fileURLWithPath: "/Volumes", isDirectory: true))

// -- Check that quick look Cocoa objects get binned correctly to their
//    associated enum tag.

// CHECK-NEXT: got the expected quick look text
switch PlaygroundQuickLook(reflecting: "woozle wuzzle" as NSString) {
case .text("woozle wuzzle"):
  print("got the expected quick look text")
case let x:
  print("NSString: got something else: \(x)")
}

// CHECK-NEXT: foobar
let somesubclassofnsstring = ("foo" + "bar") as NSString
switch PlaygroundQuickLook(reflecting: somesubclassofnsstring) {
  case .text(let text): print(text)
  case let x: print("not the expected quicklook: \(x)")
}

// CHECK-NEXT: got the expected quick look attributed string
let astr = NSAttributedString(string: "yizzle pizzle")
switch PlaygroundQuickLook(reflecting: astr) {
case .attributedString(let astr2 as NSAttributedString)
where astr == astr2:
  print("got the expected quick look attributed string")
case let x:
  print("NSAttributedString: got something else: \(x)")
}

// CHECK-NEXT: got the expected quick look int
switch PlaygroundQuickLook(reflecting: Int.max as NSNumber) {
case .int(+Int64(Int.max)):
  print("got the expected quick look int")
case let x:
  print("NSNumber(Int.max): got something else: \(x)")
}

// CHECK-NEXT: got the expected quick look uint
switch PlaygroundQuickLook(reflecting: NSNumber(value: UInt64.max)) {
case .uInt(UInt64.max):
  print("got the expected quick look uint")
case let x:
  print("NSNumber(Int64.max): got something else: \(x)")
}

// CHECK-NEXT: got the expected quick look double
switch PlaygroundQuickLook(reflecting: 22.5 as NSNumber) {
case .double(22.5):
  print("got the expected quick look double")
case let x:
  print("NSNumber(22.5): got something else: \(x)")
}

// CHECK-NEXT: got the expected quick look float
switch PlaygroundQuickLook(reflecting: Float32(1.25)) {
case .float(1.25):
  print("got the expected quick look float")
case let x:
  print("NSNumber(Float32(1.25)): got something else: \(x)")
}

// CHECK-NEXT: got the expected quick look image
// CHECK-NEXT: got the expected quick look color
// CHECK-NEXT: got the expected quick look bezier path

let image = OSImage(contentsOfFile:CommandLine.arguments[1])!
switch PlaygroundQuickLook(reflecting: image) {
case .image(let image2 as OSImage) where image === image2:
  print("got the expected quick look image")
case let x:
  print("OSImage: got something else: \(x)")
}

let color = OSColor.black
switch PlaygroundQuickLook(reflecting: color) {
case .color(let color2 as OSColor) where color === color2:
  print("got the expected quick look color")
case let x:
  print("OSColor: got something else: \(x)")
}

let path = OSBezierPath()
switch PlaygroundQuickLook(reflecting: path) {
case .bezierPath(let path2 as OSBezierPath) where path === path2:
  print("got the expected quick look bezier path")
case let x:
  print("OSBezierPath: got something else: \(x)")
}

// CHECK-LABEL: Reflecting NSArray:
// CHECK-NEXT: [ 1 2 3 4 5 ]
print("Reflecting NSArray:")
let intNSArray : NSArray = [1 as NSNumber,2 as NSNumber,3 as NSNumber,4 as NSNumber,5 as NSNumber]
let arrayMirror = Mirror(reflecting: intNSArray)
var buffer = "[ "
for i in arrayMirror.children {
  buffer += "\(i.1) "
}
buffer += "]"
print(buffer)

// CHECK-LABEL: Reflecting NSSet:
// CHECK-NEXT: NSSet reflection working fine
print("Reflecting NSSet:")
let numset = NSSet(objects: 1,2,3,4)
let numsetMirror = Mirror(reflecting: numset)
var numsetNumbers = Set<Int>()
for i in numsetMirror.children {
  if let number = i.1 as? Int {
    numsetNumbers.insert(number)
  }
}
if numsetNumbers == Set([1, 2, 3, 4]) {
  print("NSSet reflection working fine")
} else {
  print("NSSet reflection broken: here are the numbers we got: \(numsetNumbers)")
}

// CHECK-NEXT: (3.0, 6.0)
// CHECK-NEXT:   x: 3.0
// CHECK-NEXT:   y: 6.0
dump(CGPoint(x: 3,y: 6))
// CHECK-NEXT: (30.0, 60.0)
// CHECK-NEXT:   width: 30.0
// CHECK-NEXT:   height: 60.0
dump(CGSize(width: 30, height: 60))
// CHECK-NEXT: (50.0, 60.0, 100.0, 150.0)
// CHECK-NEXT:  origin: (50.0, 60.0)
// CHECK-NEXT:    x: 50.0
// CHECK-NEXT:    y: 60.0
// CHECK-NEXT:  size: (100.0, 150.0)
// CHECK-NEXT:    width: 100.0
// CHECK-NEXT:    height: 150.0
dump(CGRect(x: 50, y: 60, width: 100, height: 150))

// rdar://problem/18513769 -- Make sure that QuickLookObject lookup correctly
// manages memory.

@objc class CanaryBase {
  deinit {
    print("\(type(of: self)) overboard")
  }

  required init() { }
}

var CanaryHandle = false

class IsDebugQLO : CanaryBase, CustomStringConvertible {
  @objc var description: String {
    return "I'm a QLO"
  }
}

class HasDebugQLO : CanaryBase {
  @objc var debugQuickLookObject: AnyObject {
    return IsDebugQLO()
  }
}

class HasNumberQLO : CanaryBase {
  @objc var debugQuickLookObject: AnyObject {
    let number = NSNumber(value: 97210)
    return number
  }
}

class HasAttributedQLO : CanaryBase {
  @objc var debugQuickLookObject: AnyObject {
    let str = NSAttributedString(string: "attributed string")
    objc_setAssociatedObject(str, &CanaryHandle, CanaryBase(),
                             .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    return str
  }
}

class HasStringQLO : CanaryBase {
  @objc var debugQuickLookObject: AnyObject {
    let str = NSString(string: "plain string")
    objc_setAssociatedObject(str, &CanaryHandle, CanaryBase(),
                             .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    return str
  }
}

func testQLO<T : CanaryBase>(_ type: T.Type) {
  autoreleasepool {
    _ = PlaygroundQuickLook(reflecting: type.init())
  }
}

testQLO(IsDebugQLO.self)
// CHECK-NEXT: IsDebugQLO overboard

testQLO(HasDebugQLO.self)
// CHECK-NEXT: HasDebugQLO overboard
// CHECK-NEXT: IsDebugQLO overboard

testQLO(HasNumberQLO.self)
// CHECK-NEXT: HasNumberQLO overboard
// TODO: tagged numbers are immortal, so we can't reliably check for
//   cleanup here

testQLO(HasAttributedQLO.self)
// CHECK-NEXT: HasAttributedQLO overboard
// CHECK-NEXT: CanaryBase overboard

testQLO(HasStringQLO.self)
// CHECK-NEXT: HasStringQLO overboard
// CHECK-NEXT: CanaryBase overboard

let x = float4(0)
print("float4 has \(Mirror(reflecting: x).children.count) children")
// CHECK-NEXT: float4 has 1 children

// CHECK-LABEL: and now our song is done
print("and now our song is done")

