///|
/// Errors produced while reading a PE file.
///
/// Every variant that can be caused by the input carries the byte offset it was
/// detected at. A malformed download then produces "bad signature at 0x3c"
/// instead of a generic failure, which is the difference between a tool you can
/// debug with and one you cannot.

///|
pub(all) suberror PeError {
  // Read past end of file: offset, bytes needed, bytes available.
  OutOfBounds(Int32, Int32, Int32)
  // Wrong or missing signature: description, offset.
  BadMagic(String, Int32)
  // Structurally inconsistent file: description, offset.
  Malformed(String, Int32)
  // A recognised feature this version does not implement.
  Unsupported(String)
}

///|
/// A one-line description with the offset rendered in hex.
pub fn PeError::message(self : PeError) -> String {
  match self {
    OutOfBounds(offset, need, have) =>
      "unexpected end of file at 0x" +
      hex(offset) +
      ": needed " +
      need.to_string() +
      " byte(s), " +
      have.to_string() +
      " available"
    BadMagic(what, offset) => "bad signature at 0x" + hex(offset) + ": " + what
    Malformed(what, offset) => "malformed PE at 0x" + hex(offset) + ": " + what
    Unsupported(what) => "unsupported PE feature: " + what
  }
}

///|
/// Lowercase hexadecimal without the `0x` prefix. Offsets are never negative.
pub fn hex(value : Int32) -> String {
  if value == 0 {
    return "0"
  }
  let digits = "0123456789abcdef"
  let table : Array[Char] = []
  for c in digits {
    table.push(c)
  }
  let out : Array[Char] = []
  let mut v = value
  while v > 0 {
    out.push(table[v % 16])
    v = v / 16
  }
  let sb = StringBuilder()
  let mut i = out.length() - 1
  while i >= 0 {
    sb.write_char(out[i])
    i = i - 1
  }
  sb.to_string()
}

///|
/// Uppercase hexadecimal, for machine names such as `AMD64`.
pub fn hex_upper(value : Int32) -> String {
  let text = hex(value)
  let sb = StringBuilder()
  for c in text {
    if c >= 'a' && c <= 'f' {
      sb.write_char(Int32::unsafe_to_char(c.to_int() - 32))
    } else {
      sb.write_char(c)
    }
  }
  sb.to_string()
}

///|
/// Trimmed lowercase hexadecimal for a 64-bit value, used for image bases.
pub fn hex64(value : Int64) -> String {
  let digits = "0123456789abcdef"
  let table : Array[Char] = []
  for c in digits {
    table.push(c)
  }
  let out : Array[Char] = []
  let mut v = value
  let mut i = 0
  while i < 16 {
    out.push(table[(v & 0xF).to_int()])
    v = v >> 4
    i = i + 1
  }
  let sb = StringBuilder()
  let mut j = out.length() - 1
  let mut started = false
  while j >= 0 {
    let c = out[j]
    if c != '0' || started || j == 0 {
      started = true
      sb.write_char(c)
    }
    j = j - 1
  }
  sb.to_string()
}