//===----------------- OSLogNSObjectType.swift ----------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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
//
//===----------------------------------------------------------------------===//

// This file defines extensions for interpolating NSObject into an OSLogMesage.
// It defines `appendInterpolation` function for NSObject type. It also defines
// extensions for generating an os_log format string for NSObjects (using the
// format specifier %@) and for serializing NSObject into the argument buffer
// passed to os_log ABIs.
//
// The `appendInterpolation` function defined in this file accept privacy
// options along with the interpolated expression as shown below:
//
//         "\(x, privacy: .public\)"
import ObjectiveC

extension OSLogInterpolation {

  /// Defines interpolation for expressions of type NSObject.
  ///
  /// Do not call this function directly. It will be called automatically when interpolating
  /// a value of type `NSObject` in the string interpolations passed to the log APIs.
  ///
  /// - Parameters:
  ///   - argumentObject: The interpolated expression of type NSObject, which is autoclosured.
  ///   - privacy: A privacy qualifier which is either private or public. It is auto-inferred by default.
  @_semantics("constant_evaluable")
  @inlinable
  @_optimize(none)
  @_semantics("oslog.requires_constant_arguments")
  public mutating func appendInterpolation(
    _ argumentObject: @autoclosure @escaping () -> NSObject,
    privacy: OSLogPrivacy = .auto
  ) {
    guard argumentCount < maxOSLogArgumentCount else { return }

    formatString += getNSObjectFormatSpecifier(privacy)
    // If the privacy has a mask, append the mask argument, which is a constant payload.
    if privacy.hasMask {
      appendMaskArgument(privacy)
    }
    addNSObjectHeaders(privacy)
    arguments.append(argumentObject)
    argumentCount += 1
    objectArgumentCount += 1
  }

  /// Update preamble and append argument headers based on the parameters of
  /// the interpolation.
  @_semantics("constant_evaluable")
  @inlinable
  @_optimize(none)
  internal mutating func addNSObjectHeaders(_ privacy: OSLogPrivacy) {
    // Append argument header.
    let header = getArgumentHeader(privacy: privacy, type: .object)
    arguments.append(header)

    // Append number of bytes needed to serialize the argument.
    let byteCount = pointerSizeInBytes()
    arguments.append(UInt8(byteCount))

    // Increment total byte size by the number of bytes needed for this
    // argument, which is the sum of the byte size of the argument and
    // two bytes needed for the headers.
    totalBytesForSerializingArguments += byteCount + 2

    preamble = getUpdatedPreamble(privacy: privacy, isScalar: false)
  }

  /// Construct an os_log format specifier from the given parameters.
  /// This function must be constant evaluable and all its arguments
  /// must be known at compile time.
  @inlinable
  @_semantics("constant_evaluable")
  @_effects(readonly)
  @_optimize(none)
  internal func getNSObjectFormatSpecifier(_ privacy: OSLogPrivacy) -> String {
    var specifier = "%"
    if let privacySpecifier = privacy.privacySpecifier {
      specifier += "{"
      specifier += privacySpecifier
      specifier += "}"
    }
    specifier += "@"
    return specifier
  }
}

extension OSLogArguments {
  /// Append an (autoclosured) interpolated expression of type NSObject, passed to
  /// `OSLogMessage.appendInterpolation`, to the array of closures tracked
  /// by this instance.
  @_semantics("constant_evaluable")
  @inlinable
  @_optimize(none)
  internal mutating func append(_ value: @escaping () -> NSObject) {
    argumentClosures.append({ (position, objectArguments, _) in
      serialize(value(), at: &position, storingObjectsIn: &objectArguments)
    })
  }
}

/// Serialize an NSObject pointer at the buffer location pointed by
/// `bufferPosition`.
@_alwaysEmitIntoClient
@inline(__always)
internal func serialize(
  _ object: NSObject,
  at bufferPosition: inout ByteBufferPointer,
  storingObjectsIn objectArguments: inout ObjectStorage<NSObject>
) {
  let byteCount = pointerSizeInBytes();
  let dest =
    UnsafeMutableRawBufferPointer(start: bufferPosition, count: byteCount)
  // Get the address of this NSObject as an UnsafeRawPointer.
  let objectAddress = Unmanaged.passUnretained(object).toOpaque()
  // Copy the address into the destination buffer. Note that the input NSObject
  // is kept alive until the os_log ABI call completes by storing in the
  // objectArguments.
  withUnsafeBytes(of: objectAddress) { dest.copyMemory(from: $0) }
  bufferPosition += byteCount
  // This object could be newly created by the auto-closure. Therefore, make
  // sure it is alive until the log call completes.
  initializeAndAdvance(&objectArguments, to: object)
}
