///|
/// A DBC signal description.
pub struct Signal {
  name : String
  start_bit : Int
  size : Int
  little_endian : Bool
  signed : Bool
  factor : Double
  offset : Double
  minimum : Double
  maximum : Double
  unit : String
}

///|
/// Create a signal descriptor.
pub fn signal(
  name : String,
  start_bit : Int,
  size : Int,
  little_endian? : Bool = true,
  signed? : Bool = false,
  factor? : Double = 1.0,
  offset? : Double = 0.0,
  minimum? : Double = 0.0,
  maximum? : Double = 0.0,
  unit? : String = "",
) -> Signal {
  {
    name,
    start_bit,
    size,
    little_endian,
    signed,
    factor,
    offset,
    minimum,
    maximum,
    unit,
  }
}

///|
/// Extract a raw signal value from a payload.
pub fn Signal::decode_raw(self : Signal, data : Array[Byte]) -> UInt {
  let mut value : UInt = 0
  for bit_index in 0..> offset) & 1) == 1 {
      value = value | (1 << bit_index)
    }
  }
  value
}

///|
/// Decode a physical value using factor and offset.
pub fn Signal::decode(self : Signal, data : Array[Byte]) -> Double {
  self.decode_raw(data).to_double() * self.factor + self.offset
}

///|
/// Encode a raw value into a payload copy.
pub fn Signal::encode_raw(
  self : Signal,
  data : Array[Byte],
  value : UInt,
) -> Array[Byte] {
  let result = data.copy()
  let required = (self.start_bit + self.size + 7) / 8
  while result.length() < required {
    result.push(0)
  }
  for bit_index in 0..> bit_index) & 1) == 1 {
      result[byte_index] = result[byte_index] | mask.to_byte()
    } else {
      result[byte_index] = result[byte_index] & (0xFF ^ mask).to_byte()
    }
  }
  result
}

///|
/// Encode a physical value into a payload copy.
pub fn Signal::encode(
  self : Signal,
  data : Array[Byte],
  value : Double,
) -> Array[Byte] {
  let raw = ((value - self.offset) / self.factor).to_int().reinterpret_as_uint()
  self.encode_raw(data, raw)
}

///|
/// A CAN message with named signals.
pub struct Message {
  id : UInt
  name : String
  dlc : Int
  signals : Array[Signal]
}

///|
/// Create a message definition.
pub fn message(id : UInt, name : String, dlc : Int) -> Message {
  { id, name, dlc, signals: [] }
}

///|
/// Add a signal to a message.
pub fn Message::add_signal(self : Message, item : Signal) -> Unit {
  self.signals.push(item)
}

///|
/// Find a signal by name.
pub fn Message::find(self : Message, name : String) -> Signal? {
  for item in self.signals {
    if item.name == name {
      return Some(item)
    }
  }
  None
}

///|
/// Return whether the signal uses Intel/little-endian bit ordering.
pub fn Signal::little_endian(self : Signal) -> Bool {
  self.little_endian
}