// Binary/byte-level parsing utilities.

///|
pub fn take_bytes(n : Int, input : ParseInput) -> (ParseInput, String)? {
  if input.pos + n > input.source.length() {
    return None
  }
  let bytes = input.source[input.pos:input.pos + n].to_owned()
  Some((input.advance_by(n), bytes))
}

///|
/// Read a single unsigned byte.
pub fn u8(input : ParseInput) -> (ParseInput, Int)? {
  match take_bytes(1, input) {
    Some((rest, bytes)) => Some((rest, bytes[0].to_int()))
    None => None
  }
}

///|
/// Read an unsigned 16-bit integer in little-endian byte order.
pub fn u16_le(input : ParseInput) -> (ParseInput, Int)? {
  match take_bytes(2, input) {
    Some((rest, bytes)) => {
      let lo = bytes[0].to_int()
      let hi = bytes[1].to_int()
      Some((rest, lo | (hi << 8)))
    }
    None => None
  }
}

///|
/// Read an unsigned 32-bit integer in little-endian byte order.
pub fn u32_le(input : ParseInput) -> (ParseInput, Int)? {
  match take_bytes(4, input) {
    Some((rest, bytes)) => {
      let mut v : UInt = 0
      for i in 0..<4 {
        v = v | (bytes[i].to_uint() << (i * 8))
      }
      Some((rest, v.reinterpret_as_int()))
    }
    None => None
  }
}

///|
/// Read an unsigned 64-bit integer in little-endian byte order.
pub fn u64_le(input : ParseInput) -> (ParseInput, Int)? {
  match take_bytes(8, input) {
    Some((rest, bytes)) => {
      let mut v : UInt64 = 0
      for i in 0..<8 {
        v = v | (bytes[i].to_uint64() << (i * 8))
      }
      Some((rest, v.reinterpret_as_int64().to_int()))
    }
    None => None
  }
}

// LEB128 variable-length encoding (unsigned)

///|
pub fn leb128_u(input : ParseInput) -> (ParseInput, Int)? {
  leb128_u_acc(input, 0, 0)
}

///|
fn leb128_u_acc(
  input : ParseInput,
  shift : Int,
  acc : Int,
) -> (ParseInput, Int)? {
  if input.is_eof() || shift >= 64 {
    return None
  }
  match u8(input) {
    None => None
    Some((rest, val)) => {
      let new_acc = acc + ((val & 0x7F) << shift)
      if (val & 0x80) == 0 {
        Some((rest, new_acc))
      } else {
        leb128_u_acc(rest, shift + 7, new_acc)
      }
    }
  }
}

// LEB128 variable-length encoding (signed)

///|
pub fn leb128_s(input : ParseInput) -> (ParseInput, Int)? {
  leb128_s_acc(input, 0, 0)
}

///|
fn leb128_s_acc(
  input : ParseInput,
  shift : Int,
  acc : Int,
) -> (ParseInput, Int)? {
  if input.is_eof() || shift >= 64 {
    return None
  }
  match u8(input) {
    None => None
    Some((rest, val)) => {
      let new_acc = acc + ((val & 0x7F) << shift)
      if (val & 0x80) == 0 {
        let sign_bit = 1 << (shift + 6)
        let result = if (val & 0x40) != 0 {
          new_acc | -sign_bit
        } else {
          new_acc
        }
        Some((rest, result))
      } else {
        leb128_s_acc(rest, shift + 7, new_acc)
      }
    }
  }
}

// IEEE 754 float parsing from bytes

///|
pub fn f32_le(input : ParseInput) -> (ParseInput, Float)? {
  match take_bytes(4, input) {
    Some((rest, bytes)) => {
      let mut bits : UInt = 0
      for i in 0..<4 {
        bits = bits | (bytes[i].to_uint() << (i * 8))
      }
      Some((rest, Float::reinterpret_from_uint(bits)))
    }
    None => None
  }
}

///|
pub fn f64_le(input : ParseInput) -> (ParseInput, Double)? {
  match take_bytes(8, input) {
    Some((rest, bytes)) => {
      let mut bits : UInt64 = 0
      for i in 0..<8 {
        bits = bits | (bytes[i].to_uint64() << (i * 8))
      }
      Some((rest, bits.reinterpret_as_double()))
    }
    None => None
  }
}

// Counted string: [length: u32_le] [payload: length bytes]

///|
pub fn counted_string(input : ParseInput) -> (ParseInput, String)? {
  match u32_le(input) {
    Some((r1, len)) => {
      if len < 0 || len > 1048576 {
        return None
      }
      take_bytes(len, r1)
    }
    None => None
  }
}

// Null-terminated string

///|
pub fn c_string(input : ParseInput) -> (ParseInput, String)? {
  let (rest, result) = take_while(fn(ch) -> Bool { ch != "\x00" }, input)
  if result == "" && input.is_eof() {
    None
  } else {
    Some((rest.advance(), result))
  }
}