///|

///|
/// A decoded ISO-TP packet.
pub enum IsoTpPacket {
  Single(payload~ : Array[Byte])
  First(total_length~ : Int, payload~ : Array[Byte])
  Consecutive(sequence~ : Byte, payload~ : Array[Byte])
  FlowControl(status~ : Byte, block_size~ : Byte, separation_time~ : Byte)
}

///|
/// Encode a payload into ISO-TP packets carried by classic CAN frames.
pub fn isotp_segment(payload : Array[Byte]) -> Array[Array[Byte]] {
  if payload.length() <= 7 {
    return [[payload.length().to_byte()] + payload]
  }
  let packets : Array[Array[Byte]] = []
  let first_length = payload.length()
  packets.push(
    [
      (0x10 | ((first_length >> 8) & 0x0F)).to_byte(),
      (first_length & 0xFF).to_byte(),
    ] +
    payload[0:7].to_owned(),
  )
  let mut offset = 7
  let mut sequence : Byte = 1
  while offset < payload.length() {
    let count = if payload.length() - offset > 7 {
      7
    } else {
      payload.length() - offset
    }
    packets.push(
      [(0x20 | sequence.to_int()).to_byte()] +
      payload[offset:offset + count].to_owned(),
    )
    offset += count
    sequence = ((sequence.to_int() + 1) & 0x0F).to_byte()
  }
  packets
}

///|
/// Decode one ISO-TP packet.
pub fn isotp_decode(packet : Array[Byte]) -> IsoTpPacket? {
  if packet.is_empty() {
    return None
  }
  let pci = packet[0].to_int()
  let kind = pci >> 4
  if kind == 0 {
    Some(Single(payload=packet[1:1 + (pci & 0x0F)].to_owned()))
  } else if kind == 1 && packet.length() >= 2 {
    let length = ((pci & 0x0F) << 8) | packet[1].to_int()
    Some(First(total_length=length, payload=packet[2:].to_owned()))
  } else if kind == 2 {
    Some(
      Consecutive(
        sequence=(pci & 0x0F).to_byte(),
        payload=packet[1:].to_owned(),
      ),
    )
  } else if kind == 3 && packet.length() >= 3 {
    Some(
      FlowControl(
        status=(pci & 0x0F).to_byte(),
        block_size=packet[1],
        separation_time=packet[2],
      ),
    )
  } else {
    None
  }
}

///|
/// Reassemble consecutive ISO-TP packets after a first frame.
pub fn isotp_reassemble(
  first : IsoTpPacket,
  rest : Array[IsoTpPacket],
) -> Array[Byte]? {
  match first {
    First(total_length~, payload~) => {
      let result = payload.copy()
      for packet in rest {
        match packet {
          Consecutive(sequence~, payload~) => {
            ignore(sequence)
            for byte in payload {
              result.push(byte)
            }
          }
          _ => ()
        }
      }
      if result.length() >= total_length {
        Some(result[0:total_length].to_owned())
      } else {
        None
      }
    }
    _ => None
  }
}