///|
/// A wall-clock time with no date and no time zone, such as "07:30".
///
/// Reference: 
pub struct PlainTime {
  iso : IsoTime
} derive(Eq, Compare)

///|
pub impl Show for PlainTime with fn output(self, logger) {
  logger.write_string(
    try! self.to_string_with_options(ToStringRoundingOptions::default()),
  )
}

///|
pub impl Debug for PlainTime with fn to_repr(self) {
  Repr::Repr(self.to_string())
}

///|
/// Midnight.
pub let plain_time_midnight : PlainTime = { iso: iso_time_midnight }

///|
/// Creates a time, rejecting out-of-range fields.
///
/// ```mbt check
/// test {
///   inspect(@temporal.PlainTime::try_new(hour=7, minute=30), content="07:30:00")
/// }
/// ```
pub fn PlainTime::try_new(
  hour? : Int = 0,
  minute? : Int = 0,
  second? : Int = 0,
  millisecond? : Int = 0,
  microsecond? : Int = 0,
  nanosecond? : Int = 0,
) -> PlainTime raise TemporalError {
  PlainTime::new_with_overflow(
    hour,
    minute,
    second,
    millisecond,
    microsecond,
    nanosecond,
    Reject,
  )
}

///|
/// Creates a time, handling out-of-range fields according to `overflow`.
pub fn PlainTime::new_with_overflow(
  hour : Int,
  minute : Int,
  second : Int,
  millisecond : Int,
  microsecond : Int,
  nanosecond : Int,
  overflow : Overflow,
) -> PlainTime raise TemporalError {
  {
    iso: IsoTime::new_with_overflow(
      hour, minute, second, millisecond, microsecond, nanosecond, overflow,
    ),
  }
}

///|
/// Creates a time from an already-validated ISO time.
pub fn PlainTime::from_iso(iso : IsoTime) -> PlainTime {
  { iso, }
}

///|
/// Returns the underlying ISO time.
pub fn PlainTime::iso(self : PlainTime) -> IsoTime {
  self.iso
}

///|
/// Returns the hour.
pub fn PlainTime::hour(self : PlainTime) -> Int {
  self.iso.hour
}

///|
/// Returns the minute.
pub fn PlainTime::minute(self : PlainTime) -> Int {
  self.iso.minute
}

///|
/// Returns the second.
pub fn PlainTime::second(self : PlainTime) -> Int {
  self.iso.second
}

///|
/// Returns the millisecond.
pub fn PlainTime::millisecond(self : PlainTime) -> Int {
  self.iso.millisecond
}

///|
/// Returns the microsecond within the millisecond.
pub fn PlainTime::microsecond(self : PlainTime) -> Int {
  self.iso.microsecond
}

///|
/// Returns the nanosecond within the microsecond.
pub fn PlainTime::nanosecond(self : PlainTime) -> Int {
  self.iso.nanosecond
}

///|
/// Returns a copy with the given fields replaced.
pub fn PlainTime::with_fields(
  self : PlainTime,
  hour? : Int,
  minute? : Int,
  second? : Int,
  millisecond? : Int,
  microsecond? : Int,
  nanosecond? : Int,
  overflow? : Overflow = Constrain,
) -> PlainTime raise TemporalError {
  PlainTime::new_with_overflow(
    hour.unwrap_or(self.iso.hour),
    minute.unwrap_or(self.iso.minute),
    second.unwrap_or(self.iso.second),
    millisecond.unwrap_or(self.iso.millisecond),
    microsecond.unwrap_or(self.iso.microsecond),
    nanosecond.unwrap_or(self.iso.nanosecond),
    overflow,
  )
}

///|
/// `AddDurationToTime`: adds a duration, wrapping around midnight.
///
/// Calendar components are ignored, since a time has no date to hang them on.
///
/// ```mbt check
/// test {
///   let t = @temporal.PlainTime::try_new(hour=23, minute=30)
///   inspect(t.add(@temporal.Duration::of(hours=1)), content="00:30:00")
/// }
/// ```
pub fn PlainTime::add(self : PlainTime, duration : Duration) -> PlainTime {
  let (_, iso) = self.iso.add(TimeDuration::from_duration(duration))
  { iso, }
}

///|
/// Subtracts a duration, wrapping around midnight.
pub fn PlainTime::subtract(self : PlainTime, duration : Duration) -> PlainTime {
  self.add(duration.negated())
}

///|
/// `DifferenceTemporalPlainTime`.
fn PlainTime::diff_time(
  self : PlainTime,
  operation : DifferenceOperation,
  other : PlainTime,
  settings : DifferenceSettings,
) -> Duration raise TemporalError {
  let resolved = ResolvedRoundingOptions::from_diff_settings(
    settings,
    operation,
    UnitGroup::Time,
    Hour,
    Nanosecond,
  )
  let mut time = self.iso.diff(other.iso)
  if !resolved.is_noop() {
    time = time.round(resolved)
  }
  let result = Duration::from_internal(
    InternalDurationRecord::combine(DateDuration::default(), time),
    resolved.largest_unit,
  )
  match operation {
    Until => result
    Since => result.negated()
  }
}

///|
/// Returns the duration from this time until `other`.
pub fn PlainTime::until(
  self : PlainTime,
  other : PlainTime,
  settings? : DifferenceSettings = DifferenceSettings::default(),
) -> Duration raise TemporalError {
  self.diff_time(Until, other, settings)
}

///|
/// Returns the duration from `other` until this time.
pub fn PlainTime::since(
  self : PlainTime,
  other : PlainTime,
  settings? : DifferenceSettings = DifferenceSettings::default(),
) -> Duration raise TemporalError {
  self.diff_time(Since, other, settings)
}

///|
/// Rounds the time to a multiple of the given unit.
///
/// Rounding that carries past midnight wraps, since a time has no date to
/// carry into.
///
/// ```mbt check
/// test {
///   let t = @temporal.PlainTime::try_new(hour=12, minute=34, second=56)
///   let options = @temporal.RoundingOptions::new(smallest_unit=Minute)
///   inspect(t.round(options), content="12:35:00")
/// }
/// ```
pub fn PlainTime::round(
  self : PlainTime,
  options : RoundingOptions,
) -> PlainTime raise TemporalError {
  let resolved = ResolvedRoundingOptions::from_time_options(options)
  let (_, iso) = self.iso.round(resolved)
  { iso, }
}

///|
/// Renders the time as `HH:MM:SS`, extended with fractional digits as the
/// options require.
pub fn PlainTime::to_string_with_options(
  self : PlainTime,
  options : ToStringRoundingOptions,
) -> String raise TemporalError {
  let resolved = options.resolve()
  let rounding = ResolvedRoundingOptions::from_to_string_options(resolved)
  let (_, iso) = self.iso.round(rounding)
  let buf = StringBuilder::new()
  write_time(
    buf,
    iso.hour,
    iso.minute,
    iso.second,
    iso.subsecond_nanoseconds(),
    resolved.precision,
    true,
  )
  buf.to_string()
}

///|
/// Renders the time as `HH:MM:SS`, with as many fractional digits as it needs.
pub fn PlainTime::to_string(self : PlainTime) -> String {
  try! self.to_string_with_options(ToStringRoundingOptions::default())
}