///|
/// One value read from a .reg file.
pub(all) struct RegValue {
  name : String
  data_type : HiveValueType
  data : Bytes
}

///|
/// One [path] section of a .reg file.
pub(all) struct RegSection {
  path : String
  delete : Bool
  values : Array[RegValue]
}

///|
/// A parsed .reg document.
pub(all) struct RegFile {
  sections : Array[RegSection]
}

///|
/// One logical line of a .reg file and the index to resume from.
priv struct RegLine {
  buffer : Array[Byte]
  next : Int32
}

///|
/// Quoted text and the index just after the closing quote.
priv struct QuotedText {
  text : String
  next : Int32
}

///|
/// Number of sections in the document.
pub fn RegFile::section_count(self : RegFile) -> Int32 {
  self.sections.length()
}

///|
/// Number of values across all sections.
pub fn RegFile::value_count(self : RegFile) -> Int32 {
  let mut total = 0
  let mut index = 0
  while index < self.sections.length() {
    total = total + self.sections[index].values.length()
    index = index + 1
  }
  total
}

///|
/// True for a space or tab byte.
fn is_space_code(code : Int32) -> Bool {
  code == 32 || code == 9
}

///|
/// Lowercases one ASCII byte code.
fn lowercase_code(code : Int32) -> Int32 {
  if code >= 65 && code <= 90 {
    code + 32
  } else {
    code
  }
}

///|
/// Value of a hex digit byte, or -1 when it is not a digit.
fn hex_value(code : Int32) -> Int32 {
  if code >= 48 && code <= 57 {
    code - 48
  } else if code >= 97 && code <= 102 {
    code - 87
  } else if code >= 65 && code <= 70 {
    code - 55
  } else {
    -1
  }
}

///|
/// Index of target within [start, limit), if present.
fn find_byte(
  source : Bytes,
  start : Int32,
  limit : Int32,
  target : Int32,
) -> Int32? {
  let mut index = start
  while index < limit {
    if source[index].to_int() == target {
      return Some(index)
    }
    index = index + 1
  }
  None
}

///|
/// Decodes bytes[start, end) as UTF-8 with replacement.
fn text_of_slice(source : Bytes, start : Int32, end : Int32) -> String {
  @utf8.decode_lossy(source.clamped_view(start~, end~))
}

///|
/// Decodes a byte buffer as UTF-8 with replacement.
fn text_of(buffer : Array[Byte]) -> String {
  let encoded = Bytes::from_array(buffer)
  text_of_slice(encoded, 0, encoded.length())
}

///|
/// True when the ASCII word appears at index, ignoring case.
fn has_word_at(
  source : Bytes,
  index : Int32,
  limit : Int32,
  word : String,
) -> Bool {
  let encoded = @utf8.encode(word)
  if index + encoded.length() > limit {
    return false
  }
  let mut cursor = 0
  while cursor < encoded.length() {
    if lowercase_code(source[index + cursor].to_int()) !=
      lowercase_code(encoded[cursor].to_int()) {
      return false
    }
    cursor = cursor + 1
  }
  true
}

///|
/// Parses a quoted string starting at start.
fn parse_quoted(source : Bytes, start : Int32, limit : Int32) -> QuotedText? {
  if start >= limit || source[start].to_int() != 34 {
    return None
  }
  let buffer : Array[Byte] = []
  let mut index = start + 1
  while index < limit {
    let code = source[index].to_int()
    if code == 92 && index + 1 < limit {
      buffer.push(source[index + 1])
      index = index + 2
    } else if code == 34 {
      let parsed : QuotedText = { text: text_of(buffer), next: index + 1, }
      return Some(parsed)
    } else {
      buffer.push(source[index])
      index = index + 1
    }
  }
  None
}

///|
/// Parses a comma separated hex byte list.
fn parse_hex_bytes(
  source : Bytes,
  start : Int32,
  limit : Int32,
) -> Array[Byte]? {
  let out : Array[Byte] = []
  let mut index = start
  let mut high = -1
  while index < limit {
    let code = source[index].to_int()
    if code == 44 || is_space_code(code) {
      index = index + 1
    } else {
      let digit = hex_value(code)
      if digit < 0 {
        return None
      }
      if high < 0 {
        high = digit
      } else {
        out.push(((high * 16 + digit) & 0xFF).to_byte())
        high = -1
      }
      index = index + 1
    }
  }
  if high >= 0 {
    return None
  }
  Some(out)
}

///|
/// Parses a decimal integer, stopping at the first non digit.
fn decimal_between(source : Bytes, start : Int32, limit : Int32) -> Int32? {
  let mut value = 0
  let mut digits = 0
  let mut index = start
  let mut stop = false
  while index < limit && !stop {
    let code = source[index].to_int()
    if code >= 48 && code <= 57 {
      value = value * 10 + (code - 48)
      digits = digits + 1
      index = index + 1
    } else {
      stop = true
    }
  }
  if digits == 0 {
    None
  } else {
    Some(value)
  }
}

///|
/// Parses up to eight hex digits into four little-endian bytes.
fn parse_dword_bytes(
  source : Bytes,
  start : Int32,
  limit : Int32,
) -> Array[Byte]? {
  let digits : Array[Int32] = []
  let mut index = start
  let mut stop = false
  while index < limit && !stop {
    let code = source[index].to_int()
    let digit = hex_value(code)
    if is_space_code(code) {
      index = index + 1
    } else if digit >= 0 {
      digits.push(digit)
      index = index + 1
    } else {
      stop = true
    }
  }
  if digits.length() == 0 || digits.length() > 8 {
    return None
  }
  let padded : Array[Int32] = []
  let mut pad = 8 - digits.length()
  while pad > 0 {
    padded.push(0)
    pad = pad - 1
  }
  let mut cursor = 0
  while cursor < digits.length() {
    padded.push(digits[cursor])
    cursor = cursor + 1
  }
  let out : Array[Byte] = []
  out.push(((padded[6] * 16 + padded[7]) & 0xFF).to_byte())
  out.push(((padded[4] * 16 + padded[5]) & 0xFF).to_byte())
  out.push(((padded[2] * 16 + padded[3]) & 0xFF).to_byte())
  out.push(((padded[0] * 16 + padded[1]) & 0xFF).to_byte())
  Some(out)
}

///|
/// Maps a .reg hex type suffix to a hive value type.
fn type_from_int(code : Int32) -> HiveValueType {
  let low = (code & 0xFF).to_byte()
  HiveValueType::from_code(low.to_uint())
}

///|
/// Parses one value line into a RegValue.
fn parse_value_line(source : Bytes, start : Int32, limit : Int32) -> RegValue? {
  let mut index = start
  let mut name : String = ""
  if index < limit && source[index].to_int() == 64 {
    index = index + 1
  } else if index < limit && source[index].to_int() == 34 {
    let quoted = parse_quoted(source, index, limit)
    match quoted {
      Some(value) => {
        name = value.text
        index = value.next
      }
      None => return None
    }
  } else {
    return None
  }
  if index >= limit || source[index].to_int() != 61 {
    return None
  }
  index = index + 1
  if index >= limit {
    return None
  }
  if source[index].to_int() == 34 {
    let quoted = parse_quoted(source, index, limit)
    match quoted {
      Some(value) => {
        let parsed : RegValue = {
          name,
          data_type: Sz,
          data: encode_utf16le(value.text),
        }
        return Some(parsed)
      }
      None => return None
    }
  }
  if has_word_at(source, index, limit, "dword:") {
    let body = parse_dword_bytes(source, index + 6, limit)
    match body {
      Some(bytes) => {
        let parsed : RegValue = {
          name,
          data_type: Dword,
          data: Bytes::from_array(bytes),
        }
        return Some(parsed)
      }
      None => return None
    }
  }
  if has_word_at(source, index, limit, "hex") {
    let mut cursor = index + 3
    let mut type_code = 3
    if cursor < limit && source[cursor].to_int() == 40 {
      let close = find_byte(source, cursor + 1, limit, 41)
      match close {
        Some(position) => {
          let code = decimal_between(source, cursor + 1, position)
          match code {
            Some(value) => {
              type_code = value
              cursor = position + 1
            }
            None => return None
          }
        }
        None => return None
      }
    }
    if cursor >= limit || source[cursor].to_int() != 58 {
      return None
    }
    let body = parse_hex_bytes(source, cursor + 1, limit)
    match body {
      Some(bytes) => {
        let parsed : RegValue = {
          name,
          data_type: type_from_int(type_code),
          data: Bytes::from_array(bytes),
        }
        return Some(parsed)
      }
      None => return None
    }
  }
  None
}

///|
/// Reads one logical line, joining continuations that end with a backslash.
fn next_reg_line(source : Bytes, start : Int32, limit : Int32) -> RegLine {
  let buffer : Array[Byte] = []
  let mut position = start
  let mut done = false
  while !done && position < limit {
    let mut stop = position
    while stop < limit {
      let code = source[stop].to_int()
      if code == 13 || code == 10 {
        break
      }
      stop = stop + 1
    }
    let continues = stop > position && source[stop - 1].to_int() == 92
    let copy_end = if continues { stop - 1 } else { stop }
    let mut cursor = position
    while cursor < copy_end {
      buffer.push(source[cursor])
      cursor = cursor + 1
    }
    position = stop
    if continues {
      if position < limit && source[position].to_int() == 13 {
        position = position + 1
      }
      if position < limit && source[position].to_int() == 10 {
        position = position + 1
      }
    } else {
      done = true
    }
  }
  if done {
    if position < limit && source[position].to_int() == 13 {
      position = position + 1
    }
    if position < limit && source[position].to_int() == 10 {
      position = position + 1
    }
  }
  { buffer, next: position, }
}

///|
/// Parses .reg text into sections and values.
pub fn parse_reg_file(text : String) -> Result[RegFile, RegfError] {
  let source = @utf8.encode(text)
  let limit = source.length()
  let sections : Array[RegSection] = []
  let mut current : Int32? = None
  let mut position = 0
  if limit >= 3 &&
    source[0].to_int() == 0xEF &&
    source[1].to_int() == 0xBB &&
    source[2].to_int() == 0xBF {
    position = 3
  }
  while position < limit {
    let line = next_reg_line(source, position, limit)
    position = line.next
    let bytes = Bytes::from_array(line.buffer)
    let mut from = 0
    let mut to = bytes.length()
    while from < to && is_space_code(bytes[from].to_int()) {
      from = from + 1
    }
    while to > from && is_space_code(bytes[to - 1].to_int()) {
      to = to - 1
    }
    if from >= to {
      let _ = from
    } else if bytes[from].to_int() == 59 {
      let _ = from
    } else if bytes[from].to_int() == 91 {
      let close = find_byte(bytes, from + 1, to, 93)
      match close {
        Some(end) => {
          let mut path_start = from + 1
          let mut delete = false
          if path_start < end && bytes[path_start].to_int() == 45 {
            delete = true
            path_start = path_start + 1
          }
          let section : RegSection = {
            path: text_of_slice(bytes, path_start, end),
            delete,
            values: [],
          }
          sections.push(section)
          current = Some(sections.length() - 1)
        }
        None =>
          return Err(BadRegFile("section header without a closing bracket"))
      }
    } else {
      let section_index = match current {
        Some(at) => at
        None => -1
      }
      if section_index >= 0 {
        let value = parse_value_line(bytes, from, to)
        match value {
          Some(parsed) => sections[section_index].values.push(parsed)
          None =>
            return Err(
              BadRegFile(
                "cannot parse the value line: " + text_of_slice(bytes, from, to),
              ),
            )
        }
      } else if has_word_at(bytes, from, to, "windows registry editor") ||
        has_word_at(bytes, from, to, "regedit") {
        let _ = from
      } else {
        return Err(BadRegFile("value line before the first section"))
      }
    }
  }
  let parsed : RegFile = { sections, }
  Ok(parsed)
}

///|
/// Parses .reg text from raw file bytes, honouring UTF-16LE and UTF-8 byte order marks.
pub fn parse_reg_bytes(raw : Bytes) -> Result[RegFile, RegfError] {
  let size = raw.length()
  if size >= 2 && raw[0].to_int() == 0xFF && raw[1].to_int() == 0xFE {
    return parse_reg_file(decode_utf16le(raw, 2, (size - 2) / 2))
  }
  if size >= 3 &&
    raw[0].to_int() == 0xEF &&
    raw[1].to_int() == 0xBB &&
    raw[2].to_int() == 0xBF {
    return parse_reg_file(text_of_slice(raw, 3, size))
  }
  parse_reg_file(text_of_slice(raw, 0, size))
}

///|
/// Splits a .reg path into its segments.
fn split_path(path : String) -> Array[String] {
  let source = @utf8.encode(path)
  let segments : Array[String] = []
  let mut start = 0
  let mut index = 0
  while index < source.length() {
    if source[index].to_int() == 92 {
      if index > start {
        segments.push(text_of_slice(source, start, index))
      }
      start = index + 1
    }
    index = index + 1
  }
  if index > start {
    segments.push(text_of_slice(source, start, index))
  }
  segments
}

///|
/// Builds a hive from the document.
///
/// The first path segment of each section (the predefined root such as
/// HKEY_CURRENT_USER) is dropped, so the remaining path hangs off the hive
/// root key. Delete sections are reported instead of being dropped silently.
pub fn RegFile::to_builder(self : RegFile) -> Result[HiveBuilder, RegfError] {
  let builder = HiveBuilder::new()
  let mut index = 0
  while index < self.sections.length() {
    let section = self.sections[index]
    if section.delete {
      return Err(UnsupportedDelete(section.path))
    }
    let segments = split_path(section.path)
    let mut key_index = 0
    let mut segment = 1
    while segment < segments.length() {
      let name = segments[segment]
      match builder.child_index(key_index, name) {
        Some(existing) => key_index = existing
        None => key_index = builder.add_key(key_index, name)
      }
      segment = segment + 1
    }
    let mut value_index = 0
    while value_index < section.values.length() {
      let value = section.values[value_index]
      builder.set_value(key_index, value.name, value.data_type, value.data)
      value_index = value_index + 1
    }
    index = index + 1
  }
  Ok(builder)
}