///|
pub(all) enum IntelRecordType {
  IntelData
  IntelEndOfFile
  IntelExtendedSegmentAddress
  IntelStartSegmentAddress
  IntelExtendedLinearAddress
  IntelStartLinearAddress
} derive(Eq, Debug)

///|
pub struct IntelHexRecord {
  record_type_value : IntelRecordType
  address_value : Int
  data_value : Bytes
  checksum_value : Byte
  line_index_value : Int
} derive(Eq, Debug)

///|
fn intel_error(
  code : FirmwareErrorCode,
  message : String,
  line_index : Int,
  column_index? : Int? = None,
) -> FirmwareError {
  let position = match column_index {
    Some(column) => SourcePosition::column(line_index, column)
    None => SourcePosition::line(line_index)
  }
  FirmwareError::new(code, message, position)
}

///|
fn intel_record_type(
  value : Int,
  line_index : Int,
) -> Result[IntelRecordType, FirmwareError] {
  match value {
    0 => Ok(IntelData)
    1 => Ok(IntelEndOfFile)
    2 => Ok(IntelExtendedSegmentAddress)
    3 => Ok(IntelStartSegmentAddress)
    4 => Ok(IntelExtendedLinearAddress)
    5 => Ok(IntelStartLinearAddress)
    _ =>
      Err(
        intel_error(
          RecordInvalidType,
          "Intel HEX record type must be in the range 00 through 05",
          line_index,
          column_index=Some(7),
        ),
      )
  }
}

///|
fn IntelRecordType::type_byte(self : IntelRecordType) -> Byte {
  match self {
    IntelData => 0
    IntelEndOfFile => 1
    IntelExtendedSegmentAddress => 2
    IntelStartSegmentAddress => 3
    IntelExtendedLinearAddress => 4
    IntelStartLinearAddress => 5
  }
}

///|
fn validate_intel_record_shape(
  record_type : IntelRecordType,
  address : Int,
  byte_count : Int,
  line_index : Int,
) -> Result[Unit, FirmwareError] {
  let required_count = match record_type {
    IntelData => None
    IntelEndOfFile => Some(0)
    IntelExtendedSegmentAddress => Some(2)
    IntelStartSegmentAddress => Some(4)
    IntelExtendedLinearAddress => Some(2)
    IntelStartLinearAddress => Some(4)
  }
  match required_count {
    Some(expected) if byte_count != expected =>
      Err(
        intel_error(
          RecordLengthMismatch,
          "Intel HEX metadata record has an invalid data byte count",
          line_index,
        ),
      )
    _ =>
      if record_type != IntelData && address != 0 {
        Err(
          intel_error(
            AddressOutOfRange,
            "Intel HEX metadata record address field must be zero",
            line_index,
            column_index=Some(3),
          ),
        )
      } else {
        Ok(())
      }
  }
}

///|
/// Parse and validate one Intel HEX line without accepting surrounding spaces.
pub fn parse_intel_hex_record(
  line : String,
  line_index? : Int = 0,
) -> Result[IntelHexRecord, FirmwareError] {
  if line.length() == 0 {
    return Err(
      intel_error(RecordEmpty, "Intel HEX record is empty", line_index),
    )
  }
  if line[0].to_int() != 58 {
    return Err(
      intel_error(
        RecordInvalidPrefix,
        "Intel HEX record must start with a colon",
        line_index,
        column_index=Some(0),
      ),
    )
  }
  let encoded = line[1:].to_owned()
  let bytes = match decode_hex_bytes(encoded, line_index~) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  guard bytes.length() >= 5 else {
    return Err(
      intel_error(
        RecordLengthMismatch,
        "Intel HEX record is shorter than its fixed fields",
        line_index,
      ),
    )
  }
  let byte_count = bytes[0].to_int()
  if bytes.length() != byte_count + 5 {
    return Err(
      intel_error(
        RecordLengthMismatch,
        "Intel HEX byte count does not match the encoded record length",
        line_index,
        column_index=Some(1),
      ),
    )
  }
  if !twos_complement_valid(bytes) {
    return Err(
      intel_error(
        ChecksumMismatch,
        "Intel HEX checksum does not match the record bytes",
        line_index,
        column_index=Some(line.length() - 2),
      ),
    )
  }
  let record_type = match intel_record_type(bytes[3].to_int(), line_index) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let address = bytes[1].to_int() * 256 + bytes[2].to_int()
  match
    validate_intel_record_shape(record_type, address, byte_count, line_index) {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  let data = Bytes::makei(byte_count, index => bytes[index + 4])
  Ok({
    record_type_value: record_type,
    address_value: address,
    data_value: data,
    checksum_value: bytes[bytes.length() - 1],
    line_index_value: line_index,
  })
}

///|
pub fn IntelHexRecord::record_type(self : IntelHexRecord) -> IntelRecordType {
  self.record_type_value
}

///|
pub fn IntelHexRecord::address(self : IntelHexRecord) -> Int {
  self.address_value
}

///|
pub fn IntelHexRecord::byte_count(self : IntelHexRecord) -> Int {
  self.data_value.length()
}

///|
pub fn IntelHexRecord::data(self : IntelHexRecord) -> Bytes {
  Bytes::makei(self.data_value.length(), index => self.data_value[index])
}

///|
pub fn IntelHexRecord::checksum(self : IntelHexRecord) -> Byte {
  self.checksum_value
}

///|
pub fn IntelHexRecord::line_index(self : IntelHexRecord) -> Int {
  self.line_index_value
}

///|
/// Encode a validated record using uppercase hexadecimal digits.
pub fn IntelHexRecord::encode(self : IntelHexRecord) -> String {
  let count = self.data_value.length()
  let body = Bytes::makei(count + 4, index => {
    if index == 0 {
      count.to_byte()
    } else if index == 1 {
      ((self.address_value >> 8) & 0xFF).to_byte()
    } else if index == 2 {
      (self.address_value & 0xFF).to_byte()
    } else if index == 3 {
      self.record_type_value.type_byte()
    } else {
      self.data_value[index - 4]
    }
  })
  let checksum = Bytes::makei(1, _ => twos_complement_checksum(body))
  ":" + encode_hex_bytes(body) + encode_hex_bytes(checksum)
}