///|
pub(all) enum FirmwareFormat {
  IntelHexFormat
  SRecordFormat
} derive(Eq, Debug)

///|
pub struct ParsedFirmware {
  format_value : FirmwareFormat
  image_value : FirmwareImage
  record_count_value : Int
} derive(Eq, Debug)

///|
pub struct FirmwareInput {
  label_value : String
  text_value : String
  format_value : FirmwareFormat?
  policy_value : OverlapPolicy
} derive(Eq, Debug)

///|
pub struct FirmwareBatchItem {
  label_value : String
  format_value : FirmwareFormat?
  image_value : FirmwareImage?
  error_value : FirmwareError?
} derive(Eq, Debug)

///|
pub struct FirmwareBatch {
  item_values : Array[FirmwareBatchItem]
  success_count_value : Int
  failure_count_value : Int
  total_bytes_value : Int
} derive(Eq, Debug)

///|
/// Detect Intel HEX or S-record from the first non-line-ending code unit.
pub fn detect_firmware_format(
  text : String,
) -> Result[FirmwareFormat, FirmwareError] {
  for index = 0; index < text.length(); index = index + 1 {
    let code = text[index].to_int()
    if code == 13 || code == 10 {
      continue
    }
    if code == 58 {
      return Ok(IntelHexFormat)
    }
    if code == 83 {
      return Ok(SRecordFormat)
    }
    return Err(
      FirmwareError::new(
        RecordInvalidPrefix,
        "firmware text is neither Intel HEX nor Motorola S-record",
        SourcePosition::column(0, index),
      ),
    )
  }
  Err(
    FirmwareError::new(
      RecordEmpty,
      "firmware text contains no records",
      SourcePosition::line(0),
    ),
  )
}

///|
/// Parse either supported format and build a canonical sparse image.
pub fn parse_firmware_image(
  text : String,
  format? : FirmwareFormat? = None,
  policy? : OverlapPolicy = RejectOverlap,
) -> Result[ParsedFirmware, FirmwareError] {
  let selected = match format {
    Some(value) => value
    None =>
      match detect_firmware_format(text) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
  }
  match selected {
    IntelHexFormat => {
      let document = match parse_intel_hex_document(text) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let count = document.records().length()
      let image = match FirmwareImage::from_intel_document(document, policy~) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      Ok({
        format_value: selected,
        image_value: image,
        record_count_value: count,
      })
    }
    SRecordFormat => {
      let document = match parse_srecord_document(text) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let count = document.records().length()
      let image = match
        FirmwareImage::from_srecord_document(document, policy~) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      Ok({
        format_value: selected,
        image_value: image,
        record_count_value: count,
      })
    }
  }
}

///|
pub fn ParsedFirmware::format(self : ParsedFirmware) -> FirmwareFormat {
  self.format_value
}

///|
pub fn ParsedFirmware::image(self : ParsedFirmware) -> FirmwareImage {
  self.image_value
}

///|
pub fn ParsedFirmware::record_count(self : ParsedFirmware) -> Int {
  self.record_count_value
}

///|
pub fn FirmwareInput::new(
  label : String,
  text : String,
  format? : FirmwareFormat? = None,
  policy? : OverlapPolicy = RejectOverlap,
) -> FirmwareInput {
  {
    label_value: label,
    text_value: text,
    format_value: format,
    policy_value: policy,
  }
}

///|
/// Process inputs independently and retain ordered error evidence.
pub fn process_firmware_batch(inputs : Array[FirmwareInput]) -> FirmwareBatch {
  let items : Array[FirmwareBatchItem] = []
  let mut successes = 0
  let mut failures = 0
  let mut total_bytes = 0
  for input in inputs {
    match
      parse_firmware_image(
        input.text_value,
        format=input.format_value,
        policy=input.policy_value,
      ) {
      Ok(parsed) => {
        let image = parsed.image()
        total_bytes = total_bytes + image.total_bytes()
        successes = successes + 1
        items.push({
          label_value: input.label_value,
          format_value: Some(parsed.format()),
          image_value: Some(image),
          error_value: None,
        })
      }
      Err(error) => {
        failures = failures + 1
        let format = match input.format_value {
          Some(value) => Some(value)
          None =>
            match detect_firmware_format(input.text_value) {
              Ok(value) => Some(value)
              Err(_) => None
            }
        }
        items.push({
          label_value: input.label_value,
          format_value: format,
          image_value: None,
          error_value: Some(error),
        })
      }
    }
  }
  {
    item_values: items,
    success_count_value: successes,
    failure_count_value: failures,
    total_bytes_value: total_bytes,
  }
}

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

///|
pub fn FirmwareBatchItem::format(self : FirmwareBatchItem) -> FirmwareFormat? {
  self.format_value
}

///|
pub fn FirmwareBatchItem::image(self : FirmwareBatchItem) -> FirmwareImage? {
  self.image_value
}

///|
pub fn FirmwareBatchItem::error(self : FirmwareBatchItem) -> FirmwareError? {
  self.error_value
}

///|
pub fn FirmwareBatch::items(self : FirmwareBatch) -> Array[FirmwareBatchItem] {
  self.item_values.copy()
}

///|
pub fn FirmwareBatch::success_count(self : FirmwareBatch) -> Int {
  self.success_count_value
}

///|
pub fn FirmwareBatch::failure_count(self : FirmwareBatch) -> Int {
  self.failure_count_value
}

///|
pub fn FirmwareBatch::total_bytes(self : FirmwareBatch) -> Int {
  self.total_bytes_value
}