///|
/// A physical value assigned to a signal when building a complete CAN frame.
pub(all) struct SignalAssignment {
  name : String
  physical : Double
} derive(Eq, Debug)

///|
pub(all) enum FrameEncodeError {
  FrameEncodeUnknownMessage(UInt)
  FrameEncodeInvalidCanIdentifier(UInt, Bool)
  FrameEncodeUnknownCanMessage(UInt, Bool)
  FrameEncodeInvalidPayloadSize(Int)
  FrameEncodeUnknownSignal(String)
  FrameEncodeDuplicateSignal(String)
  FrameEncodeMissingSignal(String)
  FrameEncodeInactiveSignal(String, Int)
  FrameEncodeInactiveRangeSignal(String)
  FrameEncodeMissingMultiplexer
  FrameEncodeMultipleMultiplexers
  FrameSignalEncodeFailed(String, EncodeError)
  FrameMultiplexerDecodeFailed(String, DecodeError)
} derive(Eq, Debug)

///|
fn find_assignment(
  assignments : Array[SignalAssignment],
  name : StringView,
) -> SignalAssignment? {
  for assignment in assignments {
    if assignment.name.view() == name {
      return Some(assignment)
    }
  }
  None
}

///|
fn validate_assignments(
  message : Message,
  assignments : Array[SignalAssignment],
) -> Result[Unit, FrameEncodeError] {
  for index, assignment in assignments {
    if message.find_signal(assignment.name) is None {
      return Err(FrameEncodeUnknownSignal(assignment.name))
    }
    for previous = 0; previous < index; previous = previous + 1 {
      if assignments[previous].name == assignment.name {
        return Err(FrameEncodeDuplicateSignal(assignment.name))
      }
    }
  }
  Ok(())
}

///|
fn encode_multiplexer_layout(
  message : Message,
) -> Result[Signal?, FrameEncodeError] {
  let mut multiplexer : Signal? = None
  let mut has_multiplexed_signal = false
  for signal in message.signals {
    match signal.multiplex {
      Plain => ()
      Multiplexed(_) => has_multiplexed_signal = true
      MultiplexedRanges(_, _) => has_multiplexed_signal = true
      Multiplexer =>
        match multiplexer {
          None => multiplexer = Some(signal)
          Some(_) => return Err(FrameEncodeMultipleMultiplexers)
        }
    }
  }
  if has_multiplexed_signal && multiplexer is None {
    Err(FrameEncodeMissingMultiplexer)
  } else {
    Ok(multiplexer)
  }
}

///|
fn encode_assigned_signal(
  signal : Signal,
  assignment : SignalAssignment,
  payload : BytesView,
) -> Result[Bytes, FrameEncodeError] {
  match signal.encode(payload, assignment.physical) {
    Ok(encoded) => Ok(encoded)
    Err(error) => Err(FrameSignalEncodeFailed(signal.name, error))
  }
}

///|
fn require_assignment(
  signal : Signal,
  assignments : Array[SignalAssignment],
) -> Result[SignalAssignment, FrameEncodeError] {
  match find_assignment(assignments, signal.name) {
    Some(assignment) => Ok(assignment)
    None => Err(FrameEncodeMissingSignal(signal.name))
  }
}

///|
/// Build a zero-initialized CAN payload from named physical signal values.
/// Plain signals and the selected multiplex branch must be assigned exactly
/// once. Assignments for inactive multiplex branches are rejected.
pub fn Message::encode_frame(
  self : Message,
  assignments : Array[SignalAssignment],
) -> Result[Bytes, FrameEncodeError] {
  if self.payload_size < 0 {
    return Err(FrameEncodeInvalidPayloadSize(self.payload_size))
  }
  match validate_assignments(self, assignments) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let multiplexer = match encode_multiplexer_layout(self) {
    Err(error) => return Err(error)
    Ok(value) => value
  }
  let mut payload = Bytes::make(self.payload_size, b'\x00')
  let selector = match multiplexer {
    None => None
    Some(signal) => {
      let assignment = match require_assignment(signal, assignments) {
        Err(error) => return Err(error)
        Ok(value) => value
      }
      payload = match encode_assigned_signal(signal, assignment, payload) {
        Err(error) => return Err(error)
        Ok(value) => value
      }
      match signal.decode_raw(payload) {
        Ok(raw) => Some(raw)
        Err(error) =>
          return Err(FrameMultiplexerDecodeFailed(signal.name, error))
      }
    }
  }
  for signal in self.signals {
    match signal.multiplex {
      Multiplexer => ()
      Plain => {
        let assignment = match require_assignment(signal, assignments) {
          Err(error) => return Err(error)
          Ok(value) => value
        }
        payload = match encode_assigned_signal(signal, assignment, payload) {
          Err(error) => return Err(error)
          Ok(value) => value
        }
      }
      Multiplexed(expected) => {
        let active = match selector {
          Some(raw) => selector_matches(raw, expected)
          None => false
        }
        match (active, find_assignment(assignments, signal.name)) {
          (true, Some(assignment)) =>
            payload = match
              encode_assigned_signal(signal, assignment, payload) {
              Err(error) => return Err(error)
              Ok(value) => value
            }
          (true, None) => return Err(FrameEncodeMissingSignal(signal.name))
          (false, Some(_)) =>
            return Err(FrameEncodeInactiveSignal(signal.name, expected))
          (false, None) => ()
        }
      }
      MultiplexedRanges(_, ranges) => {
        let active = match selector {
          Some(raw) => selector_in_ranges(raw, ranges)
          None => false
        }
        match (active, find_assignment(assignments, signal.name)) {
          (true, Some(assignment)) =>
            payload = match
              encode_assigned_signal(signal, assignment, payload) {
              Err(error) => return Err(error)
              Ok(value) => value
            }
          (true, None) => return Err(FrameEncodeMissingSignal(signal.name))
          (false, Some(_)) =>
            return Err(FrameEncodeInactiveRangeSignal(signal.name))
          (false, None) => ()
        }
      }
    }
  }
  Ok(payload)
}

///|
/// Find a message by its DBC declaration identifier and build its CAN payload.
/// Use `encode_can_frame` when starting from a bus arbitration identifier.
pub fn Database::encode_frame(
  self : Database,
  message_id : UInt,
  assignments : Array[SignalAssignment],
) -> Result[Bytes, FrameEncodeError] {
  match self.find_message(message_id) {
    None => Err(FrameEncodeUnknownMessage(message_id))
    Some(message) => message.encode_frame(assignments)
  }
}

///|
/// Build a complete standard or extended CAN frame from named physical values.
pub fn Database::encode_can_frame(
  self : Database,
  frame_id : UInt,
  is_extended_frame : Bool,
  assignments : Array[SignalAssignment],
) -> Result[CanFrame, FrameEncodeError] {
  let dbc_id = match dbc_message_id_from_frame(frame_id, is_extended_frame) {
    Some(value) => value
    None =>
      return Err(FrameEncodeInvalidCanIdentifier(frame_id, is_extended_frame))
  }
  let message = match self.find_message(dbc_id) {
    Some(value) => value
    None =>
      return Err(FrameEncodeUnknownCanMessage(frame_id, is_extended_frame))
  }
  let payload = match message.encode_frame(assignments) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({ id: frame_id, is_extended_frame, payload, })
}