///|
/// Errors raised by CAN bit-timing calculations.
pub suberror CanTimingError {
  InvalidClock
  InvalidBitrate
  InvalidSamplePoint
  NoTimingCandidate
} derive(Debug)

///|
/// A nominal or data-phase bit-timing candidate.
pub struct CanBitTiming {
  clock_hz : UInt
  bitrate : UInt
  prescaler : UInt
  time_quanta : UInt
  propagation_segment : UInt
  phase_segment_1 : UInt
  phase_segment_2 : UInt
  sample_point_percent : Double
}

///|
/// Calculate a practical timing candidate using an integer clock divider.
pub fn calculate_bit_timing(
  clock_hz : UInt,
  bitrate : UInt,
  sample_point_percent : Double,
) -> CanBitTiming raise CanTimingError {
  if clock_hz == 0 {
    raise InvalidClock
  }
  if bitrate == 0 {
    raise InvalidBitrate
  }
  if sample_point_percent <= 50.0 || sample_point_percent >= 100.0 {
    raise InvalidSamplePoint
  }
  let mut best : CanBitTiming? = None
  let mut best_error = 1.0e30
  for prescaler in 1..<=1024 {
    let prescaler_u = prescaler.reinterpret_as_uint()
    let divider = bitrate * prescaler_u
    if divider == 0 || clock_hz % divider != 0 {
      continue
    }
    let quanta = clock_hz / divider
    if quanta < 8 || quanta > 80 {
      continue
    }
    let desired_phase_one = quanta.to_double() * sample_point_percent / 100.0
    let mut phase_one : UInt = 2
    for candidate in 2..<=80 {
      if candidate.to_double() <= desired_phase_one {
        phase_one = candidate.reinterpret_as_uint()
      }
    }
    if phase_one >= quanta - 2 {
      continue
    }
    let phase_two = quanta - phase_one
    let actual_sample = phase_one.to_double() * 100.0 / quanta.to_double()
    let error = (actual_sample - sample_point_percent).abs()
    if error < best_error {
      best_error = error
      best = Some({
        clock_hz,
        bitrate,
        prescaler: prescaler_u,
        time_quanta: quanta,
        propagation_segment: 1,
        phase_segment_1: phase_one - 1,
        phase_segment_2: phase_two,
        sample_point_percent: actual_sample,
      })
    }
  }
  match best {
    Some(value) => value
    None => raise NoTimingCandidate
  }
}

///|
pub fn CanBitTiming::clock_hz(self : CanBitTiming) -> UInt {
  self.clock_hz
}

///|
pub fn CanBitTiming::bitrate(self : CanBitTiming) -> UInt {
  self.bitrate
}

///|
pub fn CanBitTiming::prescaler(self : CanBitTiming) -> UInt {
  self.prescaler
}

///|
pub fn CanBitTiming::time_quanta(self : CanBitTiming) -> UInt {
  self.time_quanta
}

///|
pub fn CanBitTiming::sample_point(self : CanBitTiming) -> Double {
  self.sample_point_percent
}

///|
pub fn CanBitTiming::bit_time_ns(self : CanBitTiming) -> UInt64 {
  1_000_000_000 / self.bitrate.to_uint64()
}

///|
/// CAN-FD timing for nominal and data phases.
pub struct CanFdTiming {
  nominal : CanBitTiming
  data : CanBitTiming
}

///|
pub fn can_fd_timing(
  nominal : CanBitTiming,
  data : CanBitTiming,
) -> CanFdTiming raise CanTimingError {
  if data.bitrate() < nominal.bitrate() {
    raise InvalidBitrate
  }
  { nominal, data }
}

///|
pub fn CanFdTiming::nominal(self : CanFdTiming) -> CanBitTiming {
  self.nominal
}

///|
pub fn CanFdTiming::data(self : CanFdTiming) -> CanBitTiming {
  self.data
}

///|
/// Estimate a frame's nominal-only transmission duration.
pub fn frame_duration_ns(frame : Frame, timing : CanBitTiming) -> UInt64 {
  frame_wire_bits(frame).to_uint64() * timing.bit_time_ns()
}

///|
/// Estimate the data-phase duration for a CAN-FD frame with BRS.
pub fn fd_frame_duration_ns(frame : Frame, timing : CanFdTiming) -> UInt64 {
  if frame.protocol() is CanFd && frame.bitrate_switch() {
    let nominal_bits = if frame.is_extended() { 70 } else { 50 }
    let data_bits = frame.data().length() * 8 +
      (if frame.data().length() <= 16 { 17 } else { 21 })
    nominal_bits.to_uint64() * timing.nominal.bit_time_ns() +
    data_bits.to_uint64() * timing.data.bit_time_ns()
  } else {
    frame_duration_ns(frame, timing.nominal)
  }
}

///|
/// Return the actual bitrate represented by a timing configuration.
pub fn CanBitTiming::actual_bitrate(self : CanBitTiming) -> UInt {
  self.clock_hz / (self.prescaler * self.time_quanta)
}