///|
pub(all) struct DecodedSignal {
  name : String
  raw : RawValue
  physical : Double
  unit : String
  label : String?
} derive(Eq, Debug)

///|
pub(all) struct DecodedMessage {
  /// Arbitration identifier without the DBC bit-31 marker.
  id : UInt
  is_extended_frame : Bool
  name : String
  signals : Array[DecodedSignal]
} derive(Eq, Debug)

///|
pub(all) enum FrameDecodeError {
  UnknownMessage(UInt)
  InvalidCanFrameIdentifier(UInt, Bool)
  UnknownCanMessage(UInt, Bool)
  PayloadTooShort(Int, Int)
  MissingMultiplexer
  MultipleMultiplexers
  SignalDecodeFailed(String, DecodeError)
} derive(Eq, Debug)

///|
fn decode_signal(
  signal : Signal,
  payload : BytesView,
) -> Result[DecodedSignal, FrameDecodeError] {
  match signal.decode_raw(payload) {
    Err(error) => Err(SignalDecodeFailed(signal.name, error))
    Ok(raw) =>
      Ok({
        name: signal.name,
        raw,
        physical: raw.to_double() * signal.factor + signal.offset,
        unit: signal.unit,
        label: None,
      })
  }
}

///|
fn selector_matches(raw : RawValue, selector : Int) -> Bool {
  match raw {
    UnsignedRaw(value) => value == selector.to_uint64()
    SignedRaw(value) => value == selector.to_int64()
    FloatRaw(_) => false
  }
}

///|
fn selector_in_ranges(raw : RawValue, ranges : Array[MultiplexRange]) -> Bool {
  for range in ranges {
    if range.start < 0 || range.end < range.start {
      continue
    }
    let within = match raw {
      UnsignedRaw(value) =>
        value >= range.start.to_uint64() && value <= range.end.to_uint64()
      SignedRaw(value) =>
        value >= range.start.to_int64() && value <= range.end.to_int64()
      FloatRaw(_) => false
    }
    if within {
      return true
    }
  }
  false
}

///|
/// Decode every active signal in a CAN frame. Multiplexed signals are emitted
/// only when their selector matches the decoded multiplexer value.
pub fn Message::decode_frame(
  self : Message,
  payload : BytesView,
) -> Result[DecodedMessage, FrameDecodeError] {
  if payload.length() < self.payload_size {
    return Err(PayloadTooShort(self.payload_size, payload.length()))
  }
  let mut multiplexer : Signal? = None
  let mut has_multiplexed_signal = false
  for signal in self.signals {
    match signal.multiplex {
      Plain => ()
      Multiplexed(_) => has_multiplexed_signal = true
      MultiplexedRanges(_, _) => has_multiplexed_signal = true
      Multiplexer =>
        match multiplexer {
          Some(_) => return Err(MultipleMultiplexers)
          None => multiplexer = Some(signal)
        }
    }
  }
  if has_multiplexed_signal && multiplexer is None {
    return Err(MissingMultiplexer)
  }
  let decoded_multiplexer = match multiplexer {
    None => None
    Some(signal) =>
      match decode_signal(signal, payload) {
        Ok(decoded) => Some(decoded)
        Err(error) => return Err(error)
      }
  }
  let signals : Array[DecodedSignal] = []
  for signal in self.signals {
    match signal.multiplex {
      Plain =>
        match decode_signal(signal, payload) {
          Ok(decoded) => signals.push(decoded)
          Err(error) => return Err(error)
        }
      Multiplexer =>
        match decoded_multiplexer {
          Some(decoded) => signals.push(decoded)
          None => ()
        }
      Multiplexed(selector) =>
        match decoded_multiplexer {
          Some(decoded) if selector_matches(decoded.raw, selector) =>
            match decode_signal(signal, payload) {
              Ok(value) => signals.push(value)
              Err(error) => return Err(error)
            }
          _ => ()
        }
      MultiplexedRanges(_, ranges) =>
        match decoded_multiplexer {
          Some(decoded) if selector_in_ranges(decoded.raw, ranges) =>
            match decode_signal(signal, payload) {
              Ok(value) => signals.push(value)
              Err(error) => return Err(error)
            }
          _ => ()
        }
    }
  }
  Ok({
    id: self.frame_id(),
    is_extended_frame: self.is_extended_frame(),
    name: self.name,
    signals,
  })
}

///|
fn raw_value_as_int64(raw : RawValue) -> Int64? {
  match raw {
    SignedRaw(value) => Some(value)
    FloatRaw(_) => None
    UnsignedRaw(value) =>
      if value <= 9223372036854775807UL {
        Some(value.reinterpret_as_int64())
      } else {
        None
      }
  }
}

///|
fn enrich_decoded_message(
  database : Database,
  message : Message,
  decoded : DecodedMessage,
) -> DecodedMessage {
  let signals : Array[DecodedSignal] = []
  for signal in decoded.signals {
    let label = match raw_value_as_int64(signal.raw) {
      Some(value) => database.value_label(message.id, signal.name, value)
      None => None
    }
    signals.push({
      name: signal.name,
      raw: signal.raw,
      physical: signal.physical,
      unit: signal.unit,
      label,
    })
  }
  {
    id: decoded.id,
    is_extended_frame: decoded.is_extended_frame,
    name: decoded.name,
    signals,
  }
}

///|
/// Find and decode a message by its DBC declaration identifier, enriching
/// decoded signals with `VAL_` labels. Use `decode_can_payload` for a bus ID.
pub fn Database::decode_frame(
  self : Database,
  message_id : UInt,
  payload : BytesView,
) -> Result[DecodedMessage, FrameDecodeError] {
  let message = match self.find_message(message_id) {
    Some(message) => message
    None => return Err(UnknownMessage(message_id))
  }
  match message.decode_frame(payload) {
    Err(error) => Err(error)
    Ok(decoded) => Ok(enrich_decoded_message(self, message, decoded))
  }
}

///|
/// Decode a payload using its bus identifier and explicit standard/extended
/// frame format.
pub fn Database::decode_can_payload(
  self : Database,
  frame_id : UInt,
  is_extended_frame : Bool,
  payload : BytesView,
) -> Result[DecodedMessage, FrameDecodeError] {
  let dbc_id = match dbc_message_id_from_frame(frame_id, is_extended_frame) {
    Some(value) => value
    None => return Err(InvalidCanFrameIdentifier(frame_id, is_extended_frame))
  }
  let message = match self.find_message(dbc_id) {
    Some(value) => value
    None => return Err(UnknownCanMessage(frame_id, is_extended_frame))
  }
  match message.decode_frame(payload) {
    Err(error) => Err(error)
    Ok(decoded) => Ok(enrich_decoded_message(self, message, decoded))
  }
}