//===--- Utils.swift - Utility functions ----------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023-2025 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
//
//===----------------------------------------------------------------------===//
//
// Utility functions that are used in the swift-backtrace program.
//
//===----------------------------------------------------------------------===//

#if os(Windows)
import CRT
import WinSDK
#elseif canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#endif

import Swift

internal import BacktracingImpl.Runtime

typealias CrashInfo = swift.runtime.backtrace.CrashInfo

#if os(Linux)
typealias thread = swift.runtime.backtrace.thread
#endif

extension String {
  init<T: BinaryInteger>(_ value: T,
                         width: Int,
                         radix: Int = 10,
                         uppercase: Bool = false) {
    let digits = String(value, radix: radix, uppercase: uppercase)
    if digits.count >= width {
      self = digits
      return
    }

    let padding = String(repeating: "0",
                         count: width - digits.count)
    self = padding + digits
  }
}

internal func hex<T: FixedWidthInteger>(_ value: T,
                                        withPrefix: Bool = true) -> String {
  let formatted = String(value, width: value.bitWidth / 4, radix: 16)

  if withPrefix {
    return "0x" + formatted
  } else {
    return formatted
  }
}

internal func hex(_ bytes: [UInt8]) -> String {
  return bytes.map{ hex($0, withPrefix: false) }.joined(separator: "")
}

internal func parseUInt64<S: StringProtocol>(_ s: S) -> UInt64? {
  if s.hasPrefix("0x") {
    return UInt64(s.dropFirst(2), radix: 16)
  } else if s.hasPrefix("0b") {
    return UInt64(s.dropFirst(2), radix: 2)
  } else if s.hasPrefix("0o") {
    return UInt64(s.dropFirst(2), radix: 8)
  } else {
    return UInt64(s, radix: 10)
  }
}

#if os(macOS) || os(Linux)

struct PosixError: Error {
  var errno: Int32

  var description: String {
    return String(cString: strerror(self.errno))
  }
}

internal func recursiveRemoveContents(_ dir: String) throws {
  guard let dirp = opendir(dir) else {
    throw PosixError(errno: errno)
  }
  defer {
    closedir(dirp)
  }
  while let dp = readdir(dirp) {
    let name: String =
      withUnsafePointer(to: &dp.pointee.d_name) {
#if os(anyAppleOS)
        let len = Int(dp.pointee.d_namlen)
#else
        let len = Int(strlen($0))
#endif
        return String(decoding: UnsafeRawBufferPointer(start: $0,
                                                       count: len),
                      as: UTF8.self)
      }
    if name == "." || name == ".." {
      continue
    }
    let fullPath = "\(dir)/\(name)"
    if dp.pointee.d_type == DT_DIR {
      try recursiveRemove(fullPath)
    } else {
      if unlink(fullPath) != 0 {
        throw PosixError(errno: errno)
      }
    }
  }
}

/// Remove a directory and its contents.
internal func recursiveRemove(_ dir: String) throws {
  try recursiveRemoveContents(dir)

  if rmdir(dir) != 0 {
    throw PosixError(errno: errno)
  }
}

/// Run a closure, passing in the name of a temporary directory that will
/// (optionally) be deleted automatically when the closure returns.
///
/// Parameters:
///
/// - pattern:       A string with some number of trailing 'X's giving the name
///                  to use for the directory; the 'X's will be replaced with a
///                  unique combination of alphanumeric characters.
/// - shouldDelete:  If `true` (the default), the directory and its contents
///                  will be removed automatically when the closure returns.
/// - body:          The closure to execute.
///
/// Returns:
///
/// This function returns whatever the closure returns.
internal func withTemporaryDirectory<R>(
  pattern: String, shouldDelete: Bool = true,
  body: (String) throws -> R
) throws -> R {
  var buf = Array<UInt8>(pattern.utf8)
  buf.append(0)

  guard let dir = buf.withUnsafeMutableBufferPointer({
    if let ptr = mkdtemp($0.baseAddress!) {
      return String(cString: ptr)
    }
    return nil
  }) else {
    throw PosixError(errno: errno)
  }

  defer {
    if shouldDelete {
      try? recursiveRemove(dir)
    }
  }

  return try body(dir)
}

/// Start a program with the specified arguments
internal func spawn(_ path: String, args: [String]) throws {
  var cargs = args.map{ strdup($0) }
  cargs.append(nil)
  let result = cargs.withUnsafeBufferPointer{
    posix_spawn(nil, path, nil, nil, $0.baseAddress!, nil)
  }
  for arg in cargs {
    free(arg)
  }
  if result != 0 {
    throw PosixError(errno: result)
  }
}

#endif // os(macOS)

/// Test if the specified path is a directory
internal func isDir(_ path: String) -> Bool {
  #if os(Windows)
  return path.withCString(encodedAs: UTF16.self) { lpwszPath in
    PathIsDirectoryW(lpwszPath)
  }
  #else
  var st = stat()
  guard stat(path, &st) == 0 else {
    return false
  }
  return (st.st_mode & S_IFMT) == S_IFDIR
  #endif
}

/// Test if the specified path exists
internal func exists(_ path: String) -> Bool {
  #if os(Windows)
  return path.withCString(encodedAs: UTF16.self) { lpwszPath in
    PathFileExistsW(lpwszPath)
  }
  #else
  var st = stat()
  guard stat(path, &st) == 0 else {
    return false
  }
  return true
  #endif
}

extension Sequence {
  /// Return the first element in a Sequence.
  ///
  /// This is not, in general, a safe thing to do, because the sequence might
  /// not be restartable.  For the cases where we're using it here, it's OK
  /// though.
  var consumingFirst: Element? {
    var iterator = makeIterator()
    return iterator.next()
  }
}

struct CFileStream: TextOutputStream {
  var fp: UnsafeMutablePointer<FILE>

  public func write(_ string: String) {
    fputs(string, fp)
  }

  public func flush() {
    fflush(fp)
  }

  public func close() {
    fclose(fp)
  }

  #if os(Windows)
  var handle: HANDLE {
    let fd = _fileno(fp)
    return HANDLE(bitPattern: _get_osfhandle(fd))!
  }
  #endif
}

var standardOutput = CFileStream(fp: stdout)
var standardError = CFileStream(fp: stderr)
