//===--- DebuggerSupport.swift --------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

import SwiftShims

// Macros are disabled when Swift is built without swift-syntax.
#if $Macros && hasAttribute(attached)

/// Converts description definitions to a debugger Type Summary.
///
/// This macro converts compatible description implementations written in Swift
/// to an LLDB format known as a Type Summary. A Type Summary is LLDB's
/// equivalent to `debugDescription`, with the distinction that it does not
/// execute code inside the debugged process. By avoiding code execution,
/// descriptions can be produced faster, without potential side effects, and
/// shown in situations where code execution is not performed, such as the
/// variable list of an IDE.
///
/// Consider this an example. This `Team` struct has a `debugDescription` which
/// summarizes some key details, such as the team's name. The debugger only
/// computes this string on demand - typically via the `po` command. By applying
/// the `DebugDescription` macro, a matching Type Summary is constructed. This
/// allows the user to show a string like "Rams [11-2]", without executing
/// `debugDescription`. This improves the usability, performance, and
/// reliability of the debugging experience.
///
///     @DebugDescription
///     struct Team: CustomDebugStringConvertible {
///        var name: String
///        var wins, losses: Int
///
///        var debugDescription: String {
///            "\(name) [\(wins)-\(losses)]"
///        }
///     }
///
/// The `DebugDescription` macro supports both `debugDescription`, `description`,
/// as well as a third option: a property named `lldbDescription`. The first
/// two are implemented when conforming to the `CustomDebugStringConvertible`
/// and `CustomStringConvertible` protocols. The additional `lldbDescription`
/// property is useful when both `debugDescription` and `description` are
/// implemented, but don't meet the requirements of the `DebugDescription`
/// macro. If `lldbDescription` is implemented, `DebugDescription` choose it
/// over `debugDescription` and `description`. Likewise, `debugDescription` is
/// preferred over `description`.
///
/// ### Description Requirements
///
/// The description implementation has the following requirements:
///
/// * The body of the description implementation must be a single string
///   expression. String concatenation is not supported, use string interpolation
///   instead.
/// * String interpolation can reference stored properties only, functions calls
///   and other arbitrary computation are not supported. Of note, conditional
///   logic and computed properties are not supported.
/// * Overloaded string interpolation cannot be used.
@attached(member)
@attached(memberAttribute)
public macro DebugDescription() =
  #externalMacro(module: "SwiftMacros", type: "DebugDescriptionMacro")

/// Internal-only macro. See `@DebugDescription`.
@attached(peer, names: named(_lldb_summary))
public macro _DebugDescriptionProperty(_ debugIdentifier: String, _ computedProperties: [String]) =
  #externalMacro(module: "SwiftMacros", type: "_DebugDescriptionPropertyMacro")

#endif

#if SWIFT_ENABLE_REFLECTION

@frozen // namespace
public enum _DebuggerSupport {
  private enum CollectionStatus {
    case notACollection
    case collectionOfElements
    case collectionOfPairs
    case element
    case pair
    case elementOfPair
  
    internal var isCollection: Bool {
      return self != .notACollection
    }
  
    internal func getChildStatus(child: Mirror) -> CollectionStatus {
      let disposition = child.displayStyle
    
      if disposition == .collection { return .collectionOfElements }
      if disposition == .dictionary { return .collectionOfPairs }
      if disposition == .set { return .collectionOfElements }
    
      if self == .collectionOfElements { return .element }
      if self == .collectionOfPairs { return .pair }
      if self == .pair { return .elementOfPair }
    
      return .notACollection
    }
  }

  private static func isClass(_ value: Any) -> Bool {
    return type(of: value) is AnyClass
  }
  
  private static func checkValue<T>(
    _ value: Any,
    ifClass: (AnyObject) -> T,
    otherwise: () -> T
  ) -> T {
    if isClass(value) {
      return ifClass(_unsafeDowncastToAnyObject(fromAny: value))
    }
    return otherwise()
  }

  private static func asObjectIdentifier(_ value: Any) -> ObjectIdentifier? {
    return checkValue(value,
      ifClass: { return ObjectIdentifier($0) },
      otherwise: { return nil })
  }

  private static func asObjectAddress(_ value: Any) -> String {
    let address = checkValue(value,
      ifClass: { return unsafe unsafeBitCast($0, to: Int.self) },
      otherwise: { return 0 })
    return String(address, radix: 16, uppercase: false)
  }

  private static func asStringRepresentation(
    value: Any?,
    mirror: Mirror,
    count: Int
  ) -> String? {
    switch mirror.displayStyle {
    case .optional? where count > 0:
        return "\(mirror.subjectType)"
    case .optional?:
      return value.map(String.init(reflecting:))
    case .collection?, .dictionary?, .set?, .tuple?:
      return count == 1 ? "1 element" : "\(count) elements"
    case .`struct`?, .`enum`?, nil:
      switch value {
      case let x as CustomDebugStringConvertible:
        return x.debugDescription
      case let x as CustomStringConvertible:
        return x.description
      case _ where count > 0:
        return "\(mirror.subjectType)"
      default:
        return value.map(String.init(reflecting:))
      }
    case .`class`?, .foreignReference?:
      switch value {
      case let x as CustomDebugStringConvertible:
        return x.debugDescription
      case let x as CustomStringConvertible:
        return x.description
      case let x?:
        // for a Class with no custom summary, mimic the Foundation default
        return "<\(type(of: x)): 0x\(asObjectAddress(x))>"
      default:
        // but if I can't provide a value, just use the type anyway
        return "\(mirror.subjectType)"
      }
    }
  }

  private static func ivarCount(mirror: Mirror) -> Int {
    let ivars = mirror.superclassMirror.map(ivarCount) ?? 0
    return ivars + mirror.children.count
  }

  private static func shouldExpand(
    mirror: Mirror,
    collectionStatus: CollectionStatus,
    isRoot: Bool
  ) -> Bool {
    if isRoot || collectionStatus.isCollection { return true }
    if !mirror.children.isEmpty { return true }
    if mirror.displayStyle == .`class` { return true }
    if let sc = mirror.superclassMirror { return ivarCount(mirror: sc) > 0 }
    return true
  }

  private static func printForDebuggerImpl<StreamType: TextOutputStream>(
    value: Any?,
    mirror: Mirror,
    name: String?,
    indent: Int,
    maxDepth: Int,
    isRoot: Bool,
    parentCollectionStatus: CollectionStatus,
    refsAlreadySeen: inout Set<ObjectIdentifier>,
    maxItemCounter: inout Int,
    target: inout StreamType
  ) {    
    guard maxItemCounter > 0 else { return }

    guard shouldExpand(mirror: mirror,
                       collectionStatus: parentCollectionStatus,
                       isRoot: isRoot) 
    else { return }

    maxItemCounter -= 1
  
    print(String(repeating: " ", count: indent), terminator: "", to: &target)

    // 1. Do not expand classes, unless they conform to CustomReflectable.
    // 2. Do not expand value types that conform to CustomStringConvertible
    //    or CustomDebugStringConvertible, unless the type also conform to
    //    CustomReflectable.
    let isNonClass = mirror.displayStyle != .`class`
    let isStringConvertible: Bool
    let isCustomReflectable: Bool
    if let value = value {
      isCustomReflectable = value is CustomReflectable
      isStringConvertible =
        value is CustomStringConvertible || value is CustomDebugStringConvertible
    } else {
      isCustomReflectable = true
      isStringConvertible = false
    }
    let willExpand = (isNonClass && !isStringConvertible) || isCustomReflectable

    let count = mirror.children.count
    let bullet = isRoot && (count == 0 || !willExpand) ? ""
      : count == 0    ? "- "
      : maxDepth <= 0 ? "▹ " : "▿ "
    print(bullet, terminator: "", to: &target)
  
    let collectionStatus = parentCollectionStatus.getChildStatus(child: mirror)
  
    if let name = name {
      print("\(name) : ", terminator: "", to: &target)
    }

    if isRoot, let value = value as? String {
      // We don't want to use string's debug desciprtion for 'po' because it
      // escapes the string and prints it raw (e.g. prints "\n" instead of
      // actually printing a newline), but only if its the root value. Otherwise,
      // continue using the debug description.
      print(value, terminator: "", to: &target)
    } else if let str = asStringRepresentation(value: value, mirror: mirror, count: count) {
      print(str, terminator: "", to: &target)
    }
  
    if (maxDepth <= 0) || !willExpand {
      print("", to: &target)
      return
    }

    if let valueIdentifier = value.flatMap(asObjectIdentifier) {
      if refsAlreadySeen.contains(valueIdentifier) {
        print(" { ... }", to: &target)
        return
      } else {
        refsAlreadySeen.insert(valueIdentifier)
      }
    }

    print("", to: &target)
  
    var printedElements = 0
  
    if let sc = mirror.superclassMirror {
      printForDebuggerImpl(
        value: nil,
        mirror: sc,
        name: "super",
        indent: indent + 2,
        maxDepth: maxDepth - 1,
        isRoot: false,
        parentCollectionStatus: .notACollection,
        refsAlreadySeen: &refsAlreadySeen,
        maxItemCounter: &maxItemCounter,
        target: &target)
    }
  
    for (optionalName,child) in mirror.children {
      let childName = optionalName ?? "\(printedElements)"
      if maxItemCounter <= 0 {
        print(String(repeating: " ", count: indent+4), terminator: "", to: &target)
        let remainder = count - printedElements
        print("(\(remainder)", terminator: "", to: &target)
        if printedElements > 0 {
          print(" more", terminator: "", to: &target)
        }
        print(remainder == 1 ? " child)" : " children)", to: &target)
        return
      }
    
      printForDebuggerImpl(
        value: child,
        mirror: Mirror(reflecting: child),
        name: childName,
        indent: indent + 2,
        maxDepth: maxDepth - 1,
        isRoot: false,
        parentCollectionStatus: collectionStatus,
        refsAlreadySeen: &refsAlreadySeen,
        maxItemCounter: &maxItemCounter,
        target: &target)
      printedElements += 1
    }
  }

  /// Returns a string representation of `value` for display by the `po` LLDB
  /// command.
  ///
  /// When the value conforms to `CustomDebugStringConvertible`, its
  /// `debugDescription` is returned. Otherwise, if it conforms to
  /// `CustomStringConvertible`, its `description` is returned. When it conforms
  /// to neither, a mirror-based tree of the value's contents is produced,
  /// similar to `dump(_:)`.
  ///
  /// Types that conform to `CustomReflectable` in addition to one of the string
  /// convertible protocols will display both the custom description and their
  /// mirrored contents.
  public static func stringForPrintObject(_ value: Any) -> String {
    var maxItemCounter = Int.max
    var refs = Set<ObjectIdentifier>()
    var target = ""

    printForDebuggerImpl(
      value: value,
      mirror: Mirror(reflecting: value),
      name: nil,
      indent: 0,
      maxDepth: maxItemCounter,
      isRoot: true,
      parentCollectionStatus: .notACollection,
      refsAlreadySeen: &refs,
      maxItemCounter: &maxItemCounter,
      target: &target)

    return target
  }

  // Print an object or value without the caller having a concrete type.
  //
  // For simplicity of data handling in LLDB avoids using an enum return type,
  // using (Bool, String) instead of Optional<String>.
  @available(SwiftStdlib 6.3, *)
  public static func stringForPrintObject(_ pointer: UnsafeRawPointer?, mangledTypeName: String) -> (Bool, String) {
    guard let pointer = unsafe pointer else {
      return (false, "invalid pointer")
    }

    guard let type =
      unsafe _getTypeByMangledNameInContext(
        mangledTypeName,
        UInt(mangledTypeName.count),
        genericContext: nil,
        genericArguments: nil)
      else {
        return (false, "type not found for mangled name: \(mangledTypeName)")
      }

    func loadPointer<T>(type: T.Type) -> Any {
      if type is AnyObject.Type {
        unsafe unsafeBitCast(pointer, to: T.self)
      } else {
        unsafe pointer.load(as: T.self)
      }
    }

    let anyValue = _openExistential(type, do: loadPointer)
    return (true, stringForPrintObject(anyValue))
  }
}

public func _stringForPrintObject(_ value: Any) -> String {
  return _DebuggerSupport.stringForPrintObject(value)
}

#endif // SWIFT_ENABLE_REFLECTION

public func _debuggerTestingCheckExpect(_: String, _: String) { }

@_alwaysEmitIntoClient @_transparent
internal func _withHeapObject<R>(
  of object: AnyObject,
  _ body: (UnsafeMutableRawPointer) -> R
) -> R {
  defer { _fixLifetime(object) }
  let unmanaged = unsafe Unmanaged.passUnretained(object)
  return unsafe body(unmanaged.toOpaque())
}

@_extern(c, "swift_retainCount") @usableFromInline
internal func _swift_retainCount(_: UnsafeMutableRawPointer) -> Int
@_extern(c, "swift_unownedRetainCount") @usableFromInline
internal func _swift_unownedRetainCount(_: UnsafeMutableRawPointer) -> Int
@_extern(c, "swift_weakRetainCount") @usableFromInline
internal func _swift_weakRetainCount(_: UnsafeMutableRawPointer) -> Int

// Utilities to get refcount(s) of class objects.
@_alwaysEmitIntoClient
public func _getRetainCount(_ object: AnyObject) -> UInt {
  let count = unsafe _withHeapObject(of: object) { unsafe _swift_retainCount($0) }
  return UInt(bitPattern: count)
}

@_alwaysEmitIntoClient
public func _getUnownedRetainCount(_ object: AnyObject) -> UInt {
  let count = unsafe _withHeapObject(of: object) { unsafe _swift_unownedRetainCount($0) }
  return UInt(bitPattern: count)
}

@_alwaysEmitIntoClient
public func _getWeakRetainCount(_ object: AnyObject) -> UInt {
  let count = unsafe _withHeapObject(of: object) { unsafe _swift_weakRetainCount($0) }
  return UInt(bitPattern: count)
}
