// SAE J2012 / ISO 15031-6 diagnostic trouble codes.

pub(all) struct Dtc {
  code : String
  system : String
  description : String
  failure_type : Int
  status : Int
  status_bits : Array[String]
}

fn hex3(value : Int) -> String {
  let out = StringBuilder()
  out.write_char(hex_nibble_char(value >> 8))
  out.write_char(hex_nibble_char(value >> 4))
  out.write_char(hex_nibble_char(value))
  out.to_string()
}

pub fn decode_dtc_status(status : Int) -> Array[String] {
  let out : Array[String] = Array::new()
  let names = [
    "testFailed",
    "testFailedThisCycle",
    "pending",
    "confirmed",
    "notCompletedSinceClear",
    "failedSinceClear",
    "notCompletedThisCycle",
    "warningIndicator",
  ]
  for i in 0..<8 {
    if ((status >> i) & 1) == 1 {
      out.push(names[i])
    }
  }
  out
}

pub fn decode_dtc(high : Int, low : Int, status? : Int = 0) -> Dtc {
  let system_index = (high >> 6) & 0x3
  let systems = ["Powertrain", "Chassis", "Body", "Network"]
  let letters = ["P", "C", "B", "U"]
  let first = (high >> 4) & 0x3
  let rest = ((high & 0x0f) << 8) | low
  let code = letters[system_index] + first.to_string() + hex3(rest)
  {
    code,
    system: systems[system_index],
    description: "",
    failure_type: 0,
    status,
    status_bits: decode_dtc_status(status),
  }
}

// UDS records are 3-byte DTC + 1 status byte. The third DTC byte is the
// failure type byte, while the displayed code comes from the first two bytes.
pub fn decode_uds_dtc(
  high : Int,
  middle : Int,
  failure_type : Int,
  status : Int,
) -> Dtc {
  let base = decode_dtc(high, middle, status=status)
  {
    code: base.code + "-" + hex_byte(failure_type),
    system: base.system,
    description: base.description,
    failure_type,
    status,
    status_bits: base.status_bits,
  }
}

fn hex_byte(value : Int) -> String {
  let out = StringBuilder()
  out.write_char(hex_nibble_char(value >> 4))
  out.write_char(hex_nibble_char(value))
  out.to_string()
}

pub fn is_present_uds_record(
  high : Int,
  middle : Int,
  failure_type : Int,
  status : Int,
) -> Bool {
  high != 0 || middle != 0 || failure_type != 0 || status != 0
}

pub fn is_present_dtc(high : Int, low : Int) -> Bool {
  high != 0 || low != 0
}

pub fn filter_dtcs_by_status(list : Array[Dtc], mask : Int) -> Array[Dtc] {
  let out : Array[Dtc] = Array::new()
  for d in list {
    if (d.status & mask) != 0 {
      out.push(d)
    }
  }
  out
}