// Read-only inspection helpers for parsed TZif metadata.

///|
/// Return a structural summary of the parsed TZif data.
pub fn diagnostics(tz : @types.TzifData) -> @types.TzifDiagnostics {
  let transition_count = tz.transitions.length()
  let first_transition = if transition_count == 0 {
    None
  } else {
    Some(tz.transitions[0].utc_time)
  }
  let last_transition = if transition_count == 0 {
    None
  } else {
    Some(tz.transitions[transition_count - 1].utc_time)
  }
  let standard_indicator_count = match tz.standard_indicators {
    None => 0
    Some(values) => values.length()
  }
  let utc_indicator_count = match tz.utc_indicators {
    None => 0
    Some(values) => values.length()
  }
  let has_posix_footer = tz.posix_rule is Some(_)
  {
    version: tz.version,
    transition_count,
    time_type_count: tz.time_types.length(),
    leap_second_count: tz.leap_seconds.length(),
    standard_indicator_count,
    utc_indicator_count,
    first_transition,
    last_transition,
    has_posix_footer,
    post_transition_behavior: if has_posix_footer {
      PosixFooter
    } else {
      LastExplicitType
    },
  }
}

///|
/// Return the transition at `index`, or `None` when the index is invalid.
pub fn transition_at(tz : @types.TzifData, index : Int) -> @types.Transition? {
  if index < 0 || index >= tz.transitions.length() {
    None
  } else {
    Some(tz.transitions[index])
  }
}

///|
/// Return the latest transition strictly before `utc_time`.
pub fn previous_transition(
  tz : @types.TzifData,
  utc_time : Int64,
) -> @types.Transition? {
  let transitions = tz.transitions
  if transitions.length() == 0 || transitions[0].utc_time >= utc_time {
    return None
  }
  let mut low = 0
  let mut high = transitions.length() - 1
  while low < high {
    let middle = low + (high - low + 1) / 2
    if transitions[middle].utc_time < utc_time {
      low = middle
    } else {
      high = middle - 1
    }
  }
  Some(transitions[low])
}

///|
/// Return the earliest transition strictly after `utc_time`.
pub fn next_transition(
  tz : @types.TzifData,
  utc_time : Int64,
) -> @types.Transition? {
  let transitions = tz.transitions
  let length = transitions.length()
  if length == 0 || transitions[length - 1].utc_time <= utc_time {
    return None
  }
  let mut low = 0
  let mut high = length - 1
  while low < high {
    let middle = low + (high - low) / 2
    if transitions[middle].utc_time > utc_time {
      high = middle
    } else {
      low = middle + 1
    }
  }
  Some(transitions[low])
}

///|
/// Resolve a time-type index from user-constructible `TzifData` without
/// permitting a malformed in-memory table to panic an inspection tool.
fn query_time_type(
  tz : @types.TzifData,
  index : Int,
) -> @types.TzifResult[@types.TimeType] {
  if index < 0 || index >= tz.time_types.length() {
    Err(
      InvalidTransitionTable(
        "transition references missing local time type \{index}",
      ),
    )
  } else {
    Ok(tz.time_types[index])
  }
}

///|
fn query_local_boundary(
  utc_time : Int64,
  utoff : Int,
) -> @types.TzifResult[Int64] {
  let delta = utoff.to_int64()
  if (delta > 0L && utc_time > 9_223_372_036_854_775_807L - delta) ||
    (delta < 0L && utc_time < -9_223_372_036_854_775_808L - delta) {
    Err(TimestampOutOfRange(utc_time))
  } else {
    Ok(utc_time + delta)
  }
}

///|
/// Return a fully interpreted explicit transition, including both offsets and
/// the local timeline discontinuity.  The initial pre-transition type is
/// TZif time type zero by specification.
pub fn transition_detail(
  tz : @types.TzifData,
  index : Int,
) -> @types.TzifResult[@types.TransitionDetail] {
  if index < 0 || index >= tz.transitions.length() {
    return Err(
      InvalidTransitionTable("transition index \{index} is out of range"),
    )
  }
  let transition = tz.transitions[index]
  let before_index = if index == 0 {
    0
  } else {
    tz.transitions[index - 1].type_index
  }
  let before = match query_time_type(tz, before_index) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let after = match query_time_type(tz, transition.type_index) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let offset_change64 = after.utoff.to_int64() - before.utoff.to_int64()
  let offset_change = offset_change64.to_int()
  if offset_change.to_int64() != offset_change64 {
    return Err(TimestampOutOfRange(transition.utc_time))
  }
  let clock_change = if offset_change64 > 0L {
    @types.ClockChange::ClockForward
  } else if offset_change64 < 0L {
    ClockBackward
  } else {
    NoClockChange
  }
  let local_before = match
    query_local_boundary(transition.utc_time, before.utoff) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let local_after = match
    query_local_boundary(transition.utc_time, after.utoff) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({
    index,
    utc_time: transition.utc_time,
    before,
    after,
    offset_change,
    clock_change,
    local_before,
    local_after,
  })
}

///|
/// Return interpreted explicit transitions in the inclusive UTC range
/// `[start_utc, end_utc]`, ordered by timestamp.  POSIX footer rules are
/// synthetic and therefore intentionally excluded from this explicit-table
/// inspection API.
pub fn transition_details_between(
  tz : @types.TzifData,
  start_utc : Int64,
  end_utc : Int64,
) -> @types.TzifResult[Array[@types.TransitionDetail]] {
  if start_utc > end_utc {
    return Err(
      InvalidTransitionTable("transition range start must not be after its end"),
    )
  }
  let output : Array[@types.TransitionDetail] = []
  let mut low = 0
  let mut high = tz.transitions.length()
  while low < high {
    let middle = low + (high - low) / 2
    if tz.transitions[middle].utc_time < start_utc {
      low = middle + 1
    } else {
      high = middle
    }
  }
  for index in low.. end_utc {
      return Ok(output)
    }
    let detail = match transition_detail(tz, index) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    output.push(detail)
  }
  Ok(output)
}