///|
/// Decodes bytes as UTF-8 with replacement.
fn re_text(value : Bytes) -> String {
  @utf8.decode_lossy(value.clamped_view(start=0, end=value.length()))
}

///|
/// Appends text as UTF-8 bytes.
fn re_push_text(buffer : Array[Byte], text : String) -> Unit {
  let encoded = @utf8.encode(text)
  let mut index = 0
  while index < encoded.length() {
    buffer.push(encoded[index])
    index = index + 1
  }
}

///|
/// Appends a carriage return and a line feed.
fn re_push_newline(buffer : Array[Byte]) -> Unit {
  buffer.push((13).to_byte())
  buffer.push((10).to_byte())
}

///|
/// Appends one byte as two lowercase hex digits.
fn re_push_hex(buffer : Array[Byte], value : Int32) -> Unit {
  let high = (value >> 4) & 0x0F
  let low = value & 0x0F
  if high < 10 {
    buffer.push((48 + high).to_byte())
  } else {
    buffer.push((97 + high - 10).to_byte())
  }
  if low < 10 {
    buffer.push((48 + low).to_byte())
  } else {
    buffer.push((97 + low - 10).to_byte())
  }
}

///|
/// Escapes backslashes and quotes of one name.
fn re_escape(text : String) -> String {
  let encoded = @utf8.encode(text)
  let out : Array[Byte] = []
  let mut index = 0
  while index < encoded.length() {
    let code = encoded[index].to_int()
    if code == 92 || code == 34 {
      out.push((92).to_byte())
    }
    out.push(encoded[index])
    index = index + 1
  }
  re_text(Bytes::from_array(out))
}

///|
/// Appends the name part of an assignment, using @ for the default value.
fn re_push_name(buffer : Array[Byte], name : String) -> Unit {
  if name == "" {
    buffer.push((64).to_byte())
  } else {
    buffer.push((34).to_byte())
    re_push_text(buffer, re_escape(name))
    buffer.push((34).to_byte())
  }
  buffer.push((61).to_byte())
}

///|
/// Appends the data part of an assignment in the .reg form of its type.
fn re_push_data(buffer : Array[Byte], value : HiveValue) -> Unit {
  match value.data_type {
    Sz => {
      let text = decode_utf16le(value.data, 0, value.data.length() / 2)
      buffer.push((34).to_byte())
      re_push_text(buffer, re_escape(text))
      buffer.push((34).to_byte())
    }
    Dword => {
      re_push_text(buffer, "dword:")
      let number = if value.data.length() >= 4 {
        read_u32_le(value.data, 0).reinterpret_as_int()
      } else {
        0
      }
      let mut shift = 28
      while shift >= 0 {
        let digit = (number >> shift) & 0x0F
        if digit < 10 {
          buffer.push((48 + digit).to_byte())
        } else {
          buffer.push((97 + digit - 10).to_byte())
        }
        shift = shift - 4
      }
    }
    _ => {
      let prefix = match value.data_type {
        Binary => "hex:"
        _ => "hex(" + value_type_code(value.data_type).to_string() + "):"
      }
      re_push_text(buffer, prefix)
      let mut index = 0
      while index < value.data.length() {
        if index > 0 {
          buffer.push((44).to_byte())
        }
        re_push_hex(buffer, value.data[index].to_int())
        index = index + 1
      }
    }
  }
}

///|
/// Section path: root_path followed by the key path below the hive root key.
///
/// The hive root key itself is not part of any registry path, so it is
/// dropped; the remaining segments are joined with backslashes.
fn re_section_path(path : String, root_path : String) -> String {
  let encoded = @utf8.encode(path)
  let out : Array[Byte] = []
  re_push_text(out, root_path)
  let mut index = 0
  let mut seen_root = false
  while index < encoded.length() {
    let code = encoded[index].to_int()
    if code == 47 {
      if !seen_root {
        seen_root = true
        if out.length() > 0 {
          out.push((92).to_byte())
        }
      } else {
        out.push((92).to_byte())
      }
    } else if seen_root {
      out.push(encoded[index])
    }
    index = index + 1
  }
  re_text(Bytes::from_array(out))
}

///|
/// Renders a hive as .reg text.
///
/// Every section path is root_path followed by the key path below the hive
/// root key, so a hive whose root holds Environment renders as
/// [HKEY_CURRENT_USER\Environment] when root_path is HKEY_CURRENT_USER.
pub fn export_reg_text(
  bytes : Bytes,
  root_path : String,
) -> Result[String, RegfError] {
  match parse_header(bytes) {
    Err(error) => Err(error)
    Ok(header) => {
      let out : Array[Byte] = []
      re_push_text(out, "Windows Registry Editor Version 5.00")
      re_push_newline(out)
      re_push_newline(out)
      let entries = walk_key_tree(bytes, header.root_cell_offset, 32)
      let mut index = 0
      while index < entries.length() {
        let entry = entries[index]
        out.push((91).to_byte())
        re_push_text(out, re_section_path(entry.path, root_path))
        out.push((93).to_byte())
        re_push_newline(out)
        match parse_key_node(bytes, entry.offset) {
          Err(_) => ()
          Ok(key) =>
            match value_offsets(bytes, key) {
              Err(_) => ()
              Ok(offsets) => {
                let mut inner = 0
                while inner < offsets.length() {
                  match parse_value(bytes, offsets[inner]) {
                    Err(_) => ()
                    Ok(value) => {
                      re_push_name(out, value.name)
                      re_push_data(out, value)
                      re_push_newline(out)
                    }
                  }
                  inner = inner + 1
                }
              }
            }
        }
        re_push_newline(out)
        index = index + 1
      }
      Ok(re_text(Bytes::from_array(out)))
    }
  }
}