///|
/// A raw CAN signal value retains its integer signedness or IEEE-754 value.
pub(all) enum RawValue {
  UnsignedRaw(UInt64)
  SignedRaw(Int64)
  FloatRaw(Double)
} derive(Eq, Debug)

///|
pub(all) enum DecodeError {
  InvalidSignalLayout(Int, Int)
  FrameTooShort(Int, Int)
} derive(Eq, Debug)

///|
pub fn RawValue::to_double(self : RawValue) -> Double {
  match self {
    UnsignedRaw(value) => value.to_double()
    SignedRaw(value) => value.to_double()
    FloatRaw(value) => value
  }
}

///|
fn decode_intel_bits(
  payload : BytesView,
  start_bit : Int,
  bit_length : Int,
) -> Result[UInt64, DecodeError] {
  let available_bits = payload.length() * 8
  if bit_length > available_bits || start_bit > available_bits - bit_length {
    return Err(FrameTooShort(start_bit + bit_length, available_bits))
  }
  let mut raw = 0UL
  for offset = 0; offset < bit_length; offset = offset + 1 {
    let absolute_bit = start_bit + offset
    let byte = payload[absolute_bit / 8].to_uint64()
    let bit = (byte >> (absolute_bit % 8)) & 1UL
    raw = raw | (bit << offset)
  }
  Ok(raw)
}

///|
fn decode_motorola_bits(
  payload : BytesView,
  start_bit : Int,
  bit_length : Int,
) -> Result[UInt64, DecodeError] {
  let available_bits = payload.length() * 8
  let mut absolute_bit = start_bit
  let mut raw = 0UL
  for offset = 0; offset < bit_length; offset = offset + 1 {
    if absolute_bit >= available_bits {
      return Err(FrameTooShort(absolute_bit + 1, available_bits))
    }
    let byte = payload[absolute_bit / 8].to_uint64()
    let bit = (byte >> (absolute_bit % 8)) & 1UL
    raw = (raw << 1) | bit
    if offset + 1 < bit_length {
      absolute_bit = if absolute_bit % 8 == 0 {
        absolute_bit + 15
      } else {
        absolute_bit - 1
      }
    }
  }
  Ok(raw)
}

///|
/// Decode the raw bits of an Intel- or Motorola-ordered DBC signal.
pub fn Signal::decode_raw(
  self : Signal,
  payload : BytesView,
) -> Result[RawValue, DecodeError] {
  if self.start_bit < 0 || self.bit_length < 1 || self.bit_length > 64 {
    return Err(InvalidSignalLayout(self.start_bit, self.bit_length))
  }
  let raw_result = match self.byte_order {
    Intel => decode_intel_bits(payload, self.start_bit, self.bit_length)
    Motorola => decode_motorola_bits(payload, self.start_bit, self.bit_length)
  }
  match raw_result {
    Err(error) => Err(error)
    Ok(raw) =>
      match self.value_type {
        Float32 =>
          if self.bit_length == 32 {
            Ok(
              FloatRaw(Float::reinterpret_from_uint(raw.to_uint()).to_double()),
            )
          } else {
            Err(InvalidSignalLayout(self.start_bit, self.bit_length))
          }
        Float64 =>
          if self.bit_length == 64 {
            Ok(FloatRaw(raw.reinterpret_as_double()))
          } else {
            Err(InvalidSignalLayout(self.start_bit, self.bit_length))
          }
        Integer =>
          match self.value_kind {
            Unsigned => Ok(UnsignedRaw(raw))
            Signed => {
              if self.bit_length == 64 {
                return Ok(SignedRaw(raw.reinterpret_as_int64()))
              }
              let sign_bit = 1UL << (self.bit_length - 1)
              let signed_bits = if (raw & sign_bit) != 0UL {
                raw | (0UL.lnot() << self.bit_length)
              } else {
                raw
              }
              Ok(SignedRaw(signed_bits.reinterpret_as_int64()))
            }
          }
      }
  }
}

///|
/// Decode a DBC signal and apply its factor and offset.
pub fn Signal::decode(
  self : Signal,
  payload : BytesView,
) -> Result[Double, DecodeError] {
  match self.decode_raw(payload) {
    Ok(raw) => Ok(raw.to_double() * self.factor + self.offset)
    Err(error) => Err(error)
  }
}