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

/// A representation of high precision time.
///
/// `Duration` represents an elapsed time value with high precision in an 
/// integral form. It may be used for measurements of varying clock sources. In 
/// those cases it represents the elapsed time measured by that clock. 
/// Calculations using `Duration` may span from a negative value to a positive 
/// value and have a suitable range to at least cover attosecond scale for both
/// small elapsed durations like sub-second precision to durations that span
/// centuries.
///
/// Typical construction of `Duration` values should be created via the
/// static methods for specific time values. 
///
///      var d: Duration = .seconds(3)
///      d += .milliseconds(33)
///      print(d) // 3.033 seconds
///
/// `Duration` itself does not ferry any additional information other than the 
/// temporal measurement component; specifically leap seconds should be 
/// represented as an additional accessor since that is specific only to certain
/// clock implementations.
@available(StdlibDeploymentTarget 5.7, *)
@frozen
public struct Duration: Sendable {
  /// The low 64 bits of a 128-bit signed integer value counting attoseconds.
  public var _low: UInt64

  /// The high 64 bits of a 128-bit signed integer value counting attoseconds.
  public var _high: Int64

  @inlinable
  public init(_high: Int64, low: UInt64) {
    self._low = low
    self._high = _high
  }

  internal init(_attoseconds: _Int128) {
    self.init(_high: _attoseconds.high, low: _attoseconds.low)
  }

  /// Construct a `Duration` by adding attoseconds to a seconds value.
  ///
  /// This is useful for when an external decomposed components of a `Duration`
  /// has been stored and needs to be reconstituted. Since the values are added
  /// no precondition is expressed for the attoseconds being limited to 1e18.
  ///
  ///       let d1 = Duration(
  ///         secondsComponent: 3, 
  ///         attosecondsComponent: 123000000000000000)
  ///       print(d1) // 3.123 seconds
  ///
  ///       let d2 = Duration(
  ///         secondsComponent: 3, 
  ///         attosecondsComponent: -123000000000000000)
  ///       print(d2) // 2.877 seconds
  ///
  ///       let d3 = Duration(
  ///         secondsComponent: -3, 
  ///         attosecondsComponent: -123000000000000000)
  ///       print(d3) // -3.123 seconds
  ///
  /// - Parameters:
  ///   - secondsComponent: The seconds component portion of the `Duration` 
  ///                       value.
  ///   - attosecondsComponent: The attosecond component portion of the 
  ///                           `Duration` value.
  public init(secondsComponent: Int64, attosecondsComponent: Int64) {
    self = Duration.seconds(secondsComponent) +
           Duration(_attoseconds: _Int128(attosecondsComponent))
  }

  internal var _attoseconds: _Int128 {
    _Int128(high: _high, low: _low)
  }
}

@available(StdlibDeploymentTarget 5.7, *)
extension Duration {
  /// The composite components of the `Duration`.
  ///
  /// This is intended for facilitating conversions to existing time types. The
  /// attoseconds value will not exceed 1e18 or be lower than -1e18.
  @available(StdlibDeploymentTarget 5.7, *)
  public var components: (seconds: Int64, attoseconds: Int64) {
    let (seconds, attoseconds) = _attoseconds.dividedBy1e18()
    return (Int64(seconds), Int64(attoseconds))
  }
}

@available(SwiftStdlib 6.0, *)
extension Duration {
  /// The number of attoseconds represented by this `Duration`.
  ///
  /// This property provides direct access to the underlying number of attoseconds 
  /// that the current `Duration` represents.
  ///
  ///     let d = Duration.seconds(1)
  ///     print(d.attoseconds) // 1_000_000_000_000_000_000
  @available(SwiftStdlib 6.0, *)
  @_alwaysEmitIntoClient
  public var attoseconds: Int128 {
    Int128(_low: _low, _high: _high)
  }
  
  /// Construct a `Duration` from the given number of attoseconds.
  ///
  /// This directly constructs a `Duration` from the given number of attoseconds.
  ///
  ///     let d = Duration(attoseconds: 1_000_000_000_000_000_000)
  ///     print(d) // 1.0 seconds
  ///
  /// - Parameter attoseconds: The total duration expressed in attoseconds.
  @available(SwiftStdlib 6.0, *)
  @_alwaysEmitIntoClient
  public init(attoseconds: Int128) {
    self.init(_high: attoseconds._high, low: attoseconds._low)
  }
}

@available(StdlibDeploymentTarget 5.7, *)
extension Duration {
  /// Construct a `Duration` given a number of seconds represented as a 
  /// `BinaryInteger`.
  ///
  ///       let d: Duration = .seconds(77)
  ///
  /// - Returns: A `Duration` representing a given number of seconds.
  @available(StdlibDeploymentTarget 5.7, *)
  @inlinable
  public static func seconds<T: BinaryInteger>(_ seconds: T) -> Duration {
    guard let high = Int64(exactly: seconds >> 64) else { fatalError() }
    let low = UInt64(truncatingIfNeeded: seconds)
    let lowScaled = low.multipliedFullWidth(by: 1_000_000_000_000_000_000)
    let highScaled = high * 1_000_000_000_000_000_000
    return Duration(_high: highScaled + Int64(lowScaled.high), low: lowScaled.low)
  }
  
  /// Construct a `Duration` given a duration and scale, taking care so that
  /// exact integer durations are preserved exactly.
  internal init(_ duration: Double, scale: UInt64) {
    // Split the duration into integral and fractional parts, as we need to
    // handle them slightly differently to ensure that integer values are
    // never rounded if `scale` is representable as Double.
    let integralPart = duration.rounded(.towardZero)
    let fractionalPart = duration - integralPart
    self.init(_attoseconds:
      // This term may trap due to overflow, but it cannot round, so if the
      // input `seconds` is an exact integer, we get an exact integer result.
      _Int128(integralPart).multiplied(by: scale) +
      // This term may round, but cannot overflow.
      _Int128((fractionalPart * Double(scale)).rounded())
    )
  }

  /// Construct a `Duration` given a number of seconds represented as a 
  /// `Double` by converting the value into the closest attosecond scale value.
  ///
  ///       let d: Duration = .seconds(22.93)
  ///
  /// - Returns: A `Duration` representing a given number of seconds.
  @available(StdlibDeploymentTarget 5.7, *)
  public static func seconds(_ seconds: Double) -> Duration {
    Duration(seconds, scale: 1_000_000_000_000_000_000)
  }

  /// Construct a `Duration` given a number of milliseconds represented as a 
  /// `BinaryInteger`.
  ///
  ///       let d: Duration = .milliseconds(645)
  ///
  /// - Returns: A `Duration` representing a given number of milliseconds.
  @available(StdlibDeploymentTarget 5.7, *)
  @inlinable
  public static func milliseconds<T: BinaryInteger>(
    _ milliseconds: T
  ) -> Duration {
    guard let high = Int64(exactly: milliseconds >> 64) else { fatalError() }
    let low = UInt64(truncatingIfNeeded: milliseconds)
    let lowScaled = low.multipliedFullWidth(by: 1_000_000_000_000_000)
    let highScaled = high * 1_000_000_000_000_000
    return Duration(_high: highScaled + Int64(lowScaled.high), low: lowScaled.low)
  }

  /// Construct a `Duration` given a number of seconds milliseconds as a 
  /// `Double` by converting the value into the closest attosecond scale value.
  ///
  ///       let d: Duration = .milliseconds(88.3)
  ///
  /// - Returns: A `Duration` representing a given number of milliseconds.
  @available(StdlibDeploymentTarget 5.7, *)
  public static func milliseconds(_ milliseconds: Double) -> Duration {
    Duration(milliseconds, scale: 1_000_000_000_000_000)
  }

  /// Construct a `Duration` given a number of microseconds represented as a 
  /// `BinaryInteger`.
  ///
  ///       let d: Duration = .microseconds(12)
  ///
  /// - Returns: A `Duration` representing a given number of microseconds.
  @available(StdlibDeploymentTarget 5.7, *)
  @inlinable
  public static func microseconds<T: BinaryInteger>(
    _ microseconds: T
  ) -> Duration {
    guard let high = Int64(exactly: microseconds >> 64) else { fatalError() }
    let low = UInt64(truncatingIfNeeded: microseconds)
    let lowScaled = low.multipliedFullWidth(by: 1_000_000_000_000)
    let highScaled = high * 1_000_000_000_000
    return Duration(_high: highScaled + Int64(lowScaled.high), low: lowScaled.low)
  }

  /// Construct a `Duration` given a number of seconds microseconds as a 
  /// `Double` by converting the value into the closest attosecond scale value.
  ///
  ///       let d: Duration = .microseconds(382.9)
  ///
  /// - Returns: A `Duration` representing a given number of microseconds.
  @available(StdlibDeploymentTarget 5.7, *)
  public static func microseconds(_ microseconds: Double) -> Duration {
    Duration(microseconds, scale: 1_000_000_000_000)
  }

  /// Construct a `Duration` given a number of nanoseconds represented as a 
  /// `BinaryInteger`.
  ///
  ///       let d: Duration = .nanoseconds(1929)
  ///
  /// - Returns: A `Duration` representing a given number of nanoseconds.
  @available(StdlibDeploymentTarget 5.7, *)
  @inlinable
  public static func nanoseconds<T: BinaryInteger>(
    _ nanoseconds: T
  ) -> Duration {
    guard let high = Int64(exactly: nanoseconds >> 64) else { fatalError() }
    let low = UInt64(truncatingIfNeeded: nanoseconds)
    let lowScaled = low.multipliedFullWidth(by: 1_000_000_000)
    let highScaled = high * 1_000_000_000
    return Duration(_high: highScaled + Int64(lowScaled.high), low: lowScaled.low)
  }
  
  /// Construct a `Duration` given a number of nanoseconds as a
  /// `Double` by converting the value into the closest attosecond scale value.
  ///
  ///       let d: Duration = .nanoseconds(382.9)
  ///
  /// - Returns: A `Duration` representing a given number of nanoseconds.
  @available(SwiftStdlib 6.2, *)
  public static func nanoseconds(_ nanoseconds: Double) -> Duration {
    Duration(nanoseconds, scale: 1_000_000_000)
  }
}

@available(StdlibDeploymentTarget 5.7, *)
@_unavailableInEmbedded
extension Duration: Codable {
  @available(StdlibDeploymentTarget 5.7, *)
  public init(from decoder: Decoder) throws {
    var container = try decoder.unkeyedContainer()
    let high = try container.decode(Int64.self)
    let low = try container.decode(UInt64.self)
    self.init(_high: high, low: low)
  }

  @available(StdlibDeploymentTarget 5.7, *)
  public func encode(to encoder: Encoder) throws {
    var container = encoder.unkeyedContainer()
    try container.encode(_high)
    try container.encode(_low)
  }
}

@available(StdlibDeploymentTarget 5.7, *)
extension Duration: Hashable {
  @available(StdlibDeploymentTarget 5.7, *)
  public func hash(into hasher: inout Hasher) {
    hasher.combine(_attoseconds)
  }
}

@available(StdlibDeploymentTarget 5.7, *)
extension Duration: Equatable {
  @available(StdlibDeploymentTarget 5.7, *)
  public static func == (_ lhs: Duration, _ rhs: Duration) -> Bool {
    return lhs._attoseconds == rhs._attoseconds
  }
}

@available(StdlibDeploymentTarget 5.7, *)
extension Duration: Comparable {
  @available(StdlibDeploymentTarget 5.7, *)
  public static func < (_ lhs: Duration, _ rhs: Duration) -> Bool {
    return lhs._attoseconds < rhs._attoseconds
  }
}

@available(StdlibDeploymentTarget 5.7, *)
extension Duration: AdditiveArithmetic {
  @available(StdlibDeploymentTarget 5.7, *)
  public static var zero: Duration { Duration(_attoseconds: 0) }

  @available(StdlibDeploymentTarget 5.7, *)
  public static func + (_ lhs: Duration, _ rhs: Duration) -> Duration {
    return Duration(_attoseconds: lhs._attoseconds + rhs._attoseconds)
  }

  @available(StdlibDeploymentTarget 5.7, *)
  public static func - (_ lhs: Duration, _ rhs: Duration) -> Duration {
    return Duration(_attoseconds: lhs._attoseconds - rhs._attoseconds)
  }

  @available(StdlibDeploymentTarget 5.7, *)
  public static func += (_ lhs: inout Duration, _ rhs: Duration) {
    lhs = lhs + rhs
  }

  @available(StdlibDeploymentTarget 5.7, *)
  public static func -= (_ lhs: inout Duration, _ rhs: Duration) {
    lhs = lhs - rhs
  }
}

@available(StdlibDeploymentTarget 5.7, *)
extension Duration {
  @available(StdlibDeploymentTarget 5.7, *)
  public static func / (_ lhs: Duration, _ rhs: Double) -> Duration {
    return Duration(_attoseconds:
      _Int128(Double(lhs._attoseconds) / rhs))
  }

  @available(StdlibDeploymentTarget 5.7, *)
  public static func /= (_ lhs: inout Duration, _ rhs: Double) {
    lhs = lhs / rhs
  }

  @available(StdlibDeploymentTarget 5.7, *)
  public static func / <T: BinaryInteger>(
    _ lhs: Duration, _ rhs: T
  ) -> Duration {
    Duration(_attoseconds: lhs._attoseconds / _Int128(rhs))
  }

  @available(StdlibDeploymentTarget 5.7, *)
  public static func /= <T: BinaryInteger>(_ lhs: inout Duration, _ rhs: T) {
    lhs = lhs / rhs
  }

  @available(StdlibDeploymentTarget 5.7, *)
  public static func / (_ lhs: Duration, _ rhs: Duration) -> Double {
    Double(lhs._attoseconds) / Double(rhs._attoseconds)
  }

  @available(StdlibDeploymentTarget 5.7, *)
  public static func * (_ lhs: Duration, _ rhs: Double) -> Duration {
    Duration(_attoseconds: _Int128(Double(lhs._attoseconds) * rhs))
  }

  @available(StdlibDeploymentTarget 5.7, *)
  public static func * <T: BinaryInteger>(
    _ lhs: Duration, _ rhs: T
  ) -> Duration {
    Duration(_attoseconds: lhs._attoseconds * _Int128(rhs))
  }

  @available(StdlibDeploymentTarget 5.7, *)
  public static func *= <T: BinaryInteger>(_ lhs: inout Duration, _ rhs: T) {
    lhs = lhs * rhs
  }
}

@available(StdlibDeploymentTarget 5.7, *)
@_unavailableInEmbedded
extension Duration: CustomStringConvertible {
  @available(StdlibDeploymentTarget 5.7, *)
  public var description: String {
    return (Double(_attoseconds) / 1e18).description + " seconds"
  }
}

@available(StdlibDeploymentTarget 5.7, *)
extension Duration: DurationProtocol { }

@available(StdlibDeploymentTarget 5.7, *)
extension Duration: ConvertibleToBytes, ConvertibleFromBytes {}
