///|
pub struct DecodedSignal {
  name_value : String
  raw_bits_value : UInt64
  signed_raw_value : Int64
  physical_value : Double
  label_value : String?
} derive(Eq, Debug)

///|
pub struct SignalAssignment {
  name_value : String
  physical_value : Double
} derive(Eq, Debug)

///|
pub fn DecodedSignal::name(self : DecodedSignal) -> String {
  self.name_value
}

///|
pub fn DecodedSignal::raw_bits(self : DecodedSignal) -> UInt64 {
  self.raw_bits_value
}

///|
pub fn DecodedSignal::signed_raw(self : DecodedSignal) -> Int64 {
  self.signed_raw_value
}

///|
pub fn DecodedSignal::physical(self : DecodedSignal) -> Double {
  self.physical_value
}

///|
pub fn DecodedSignal::label(self : DecodedSignal) -> String? {
  self.label_value
}

///|
pub fn SignalAssignment::new(
  name : String,
  physical : Double,
) -> SignalAssignment {
  { name_value: name, physical_value: physical }
}

///|
pub fn SignalAssignment::name(self : SignalAssignment) -> String {
  self.name_value
}

///|
pub fn SignalAssignment::physical(self : SignalAssignment) -> Double {
  self.physical_value
}

///|
fn codec_error(
  code : DbcErrorCode,
  message : String,
  signal : DbcSignal,
) -> DbcError {
  DbcError::new(code, message, signal.source_line(), 1, context=signal.name())
}

///|
fn bit_mask(length : Int) -> UInt64 {
  if length >= 64 {
    0xFFFFFFFFFFFFFFFFUL
  } else {
    (1UL << length) - 1UL
  }
}

///|
fn ensure_frame_length(
  message : DbcMessage,
  data : Bytes,
) -> Result[Unit, DbcError] {
  if data.length() != message.size() {
    Err(
      DbcError::new(
        InvalidDataLength,
        "frame length does not match the DBC message size",
        message.source_line(),
        1,
        context="expected \{message.size()}, received \{data.length()}",
      ),
    )
  } else {
    Ok(())
  }
}

///|
fn raw_bit_offset(signal : DbcSignal, sequence_index : Int) -> Int {
  match signal.byte_order() {
    Intel => sequence_index
    Motorola => signal.bit_length() - 1 - sequence_index
  }
}

///|
fn extract_raw_bits(
  signal : DbcSignal,
  data : Bytes,
) -> Result[UInt64, DbcError] {
  let positions = match signal_bit_positions(signal) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let mut raw = 0UL
  for sequence_index, position in positions {
    let byte_index = position / 8
    let bit_index = position % 8
    if byte_index < 0 || byte_index >= data.length() {
      return Err(
        codec_error(
          InvalidBitRange,
          "signal extends beyond supplied frame",
          signal,
        ),
      )
    }
    if (data[byte_index].to_int() & (1 << bit_index)) != 0 {
      raw = raw | (1UL << raw_bit_offset(signal, sequence_index))
    }
  }
  Ok(raw)
}

///|
fn insert_raw_bits(
  signal : DbcSignal,
  raw : UInt64,
  data : Array[Byte],
) -> Result[Unit, DbcError] {
  let positions = match signal_bit_positions(signal) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  for sequence_index, position in positions {
    let byte_index = position / 8
    let bit_index = position % 8
    if byte_index < 0 || byte_index >= data.length() {
      return Err(
        codec_error(
          InvalidBitRange,
          "signal extends beyond supplied frame",
          signal,
        ),
      )
    }
    let mask = 1 << bit_index
    let raw_offset = raw_bit_offset(signal, sequence_index)
    let current = data[byte_index].to_int()
    data[byte_index] = if (raw & (1UL << raw_offset)) != 0UL {
      (current | mask).to_byte()
    } else {
      (current & (0xFF ^ mask)).to_byte()
    }
  }
  Ok(())
}

///|
fn sign_extend(raw : UInt64, length : Int) -> Int64 {
  if length >= 64 {
    raw.reinterpret_as_int64()
  } else if (raw & (1UL << (length - 1))) != 0UL {
    (raw | (0xFFFFFFFFFFFFFFFFUL ^ bit_mask(length))).reinterpret_as_int64()
  } else {
    raw.reinterpret_as_int64()
  }
}

///|
fn raw_label(signal : DbcSignal, signed : Int64) -> String? {
  if signed < -2147483648L || signed > 2147483647L {
    return None
  }
  signal.value_label(signed.to_int())
}

///|
pub fn decode_signal(
  message : DbcMessage,
  signal : DbcSignal,
  data : Bytes,
) -> Result[DecodedSignal, DbcError] {
  match ensure_frame_length(message, data) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let raw = match extract_raw_bits(signal, data) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let signed = match signal.value_type() {
    Signed => sign_extend(raw, signal.bit_length())
    Unsigned => {
      if signal.bit_length() == 64 && raw > 0x7FFFFFFFFFFFFFFFUL {
        return Err(
          codec_error(
            ValueOutOfRange,
            "unsigned 64-bit value cannot be represented as an exact physical Double",
            signal,
          ),
        )
      }
      raw.reinterpret_as_int64()
    }
  }
  let physical = signed.to_double() * signal.factor() + signal.offset()
  Ok({
    name_value: signal.name(),
    raw_bits_value: raw,
    signed_raw_value: signed,
    physical_value: physical,
    label_value: raw_label(signal, signed),
  })
}

///|
pub fn decode_message(
  message : DbcMessage,
  data : Bytes,
) -> Result[Array[DecodedSignal], DbcError] {
  match ensure_frame_length(message, data) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let decoded : Array[DecodedSignal] = []
  for signal in message.signals() {
    match decode_signal(message, signal, data) {
      Ok(value) => decoded.push(value)
      Err(error) => return Err(error)
    }
  }
  Ok(decoded)
}

///|
fn unsigned_raw_fits(signal : DbcSignal, raw : UInt64) -> Bool {
  raw <= bit_mask(signal.bit_length())
}

///|
fn signed_raw_fits(signal : DbcSignal, raw : Int64) -> Bool {
  if signal.bit_length() >= 64 {
    return true
  }
  let maximum = (1L << (signal.bit_length() - 1)) - 1L
  let minimum = -(1L << (signal.bit_length() - 1))
  raw >= minimum && raw <= maximum
}

///|
pub fn encode_unsigned_raw_signal(
  message : DbcMessage,
  signal : DbcSignal,
  raw : UInt64,
  data : Array[Byte],
) -> Result[Unit, DbcError] {
  if data.length() != message.size() {
    return Err(
      DbcError::new(
        InvalidDataLength,
        "frame length does not match the DBC message size",
        message.source_line(),
        1,
      ),
    )
  }
  if signal.value_type() != Unsigned {
    return Err(
      codec_error(ValueOutOfRange, "signal is declared as signed", signal),
    )
  }
  if !unsigned_raw_fits(signal, raw) {
    return Err(
      codec_error(
        ValueOutOfRange,
        "raw value does not fit signal width",
        signal,
      ),
    )
  }
  insert_raw_bits(signal, raw, data)
}

///|
pub fn encode_signed_raw_signal(
  message : DbcMessage,
  signal : DbcSignal,
  raw : Int64,
  data : Array[Byte],
) -> Result[Unit, DbcError] {
  if data.length() != message.size() {
    return Err(
      DbcError::new(
        InvalidDataLength,
        "frame length does not match the DBC message size",
        message.source_line(),
        1,
      ),
    )
  }
  if signal.value_type() != Signed {
    return Err(
      codec_error(ValueOutOfRange, "signal is declared as unsigned", signal),
    )
  }
  if !signed_raw_fits(signal, raw) {
    return Err(
      codec_error(
        ValueOutOfRange,
        "raw value does not fit signal width",
        signal,
      ),
    )
  }
  insert_raw_bits(
    signal,
    raw.reinterpret_as_uint64() & bit_mask(signal.bit_length()),
    data,
  )
}

///|
fn absolute_double(value : Double) -> Double {
  if value < 0.0 {
    -value
  } else {
    value
  }
}

///|
pub fn encode_physical_signal(
  message : DbcMessage,
  signal : DbcSignal,
  physical : Double,
  data : Array[Byte],
  tolerance? : Double = 0.000000001,
) -> Result[Unit, DbcError] {
  if data.length() != message.size() {
    return Err(
      DbcError::new(
        InvalidDataLength,
        "frame length does not match the DBC message size",
        message.source_line(),
        1,
      ),
    )
  }
  if tolerance < 0.0 {
    return Err(
      codec_error(
        ValueOutOfRange,
        "quantization tolerance cannot be negative",
        signal,
      ),
    )
  }
  if physical < signal.minimum() || physical > signal.maximum() {
    return Err(
      codec_error(
        ValueOutOfRange,
        "physical value is outside declared signal range",
        signal,
      ),
    )
  }
  let exact_raw = signal.raw_from_physical(physical)
  let rounded = (if exact_raw < 0.0 {
    exact_raw - 0.5
  } else {
    exact_raw + 0.5
  }).to_int64()
  let reconstructed = rounded.to_double() * signal.factor() + signal.offset()
  if absolute_double(reconstructed - physical) > tolerance {
    return Err(
      codec_error(
        ValueOutOfRange,
        "physical value is not representable within quantization tolerance",
        signal,
      ),
    )
  }
  match signal.value_type() {
    Signed => encode_signed_raw_signal(message, signal, rounded, data)
    Unsigned => {
      if rounded < 0L {
        return Err(
          codec_error(
            ValueOutOfRange,
            "unsigned signal cannot encode a negative raw value",
            signal,
          ),
        )
      }
      encode_unsigned_raw_signal(
        message,
        signal,
        rounded.reinterpret_as_uint64(),
        data,
      )
    }
  }
}

///|
fn assignment_seen(assignments : Array[SignalAssignment], index : Int) -> Bool {
  for previous in 0.. Result[Bytes, DbcError] {
  let data : Array[Byte] = Array::make(message.size(), (0 : Byte))
  for index, assignment in assignments {
    if assignment_seen(assignments, index) {
      return Err(
        DbcError::new(
          DuplicateSignalName,
          "signal assignment is repeated",
          message.source_line(),
          1,
          context=assignment.name(),
        ),
      )
    }
    let signal = match message.find_signal(assignment.name()) {
      Some(value) => value
      None =>
        return Err(
          DbcError::new(
            UnknownSignal,
            "assignment references an unknown signal",
            message.source_line(),
            1,
            context=assignment.name(),
          ),
        )
    }
    match encode_physical_signal(message, signal, assignment.physical(), data) {
      Ok(_) => ()
      Err(error) => return Err(error)
    }
  }
  Ok(Bytes::from_array(data))
}