///|
struct Input {
  encoding : @api.InputEncoding
  source : String
  bytes : Bytes
  read_callback : ((UInt32, @api.Point) -> InputChunk)?
  decode_callback : ((Bytes, Int32) -> InputDecodeResult)?
}

///|
priv struct InputMaterialized {
  bytes : Bytes
  chunk_end_offsets : Array[Int32]
}

///|
pub(all) struct InputChunk {
  chunk : Bytes?
  bytes_read : Int32
} derive(Eq, Debug)

///|
pub fn InputChunk::new(chunk~ : Bytes?, bytes_read~ : Int32) -> InputChunk {
  { chunk, bytes_read, }
}

///|
pub fn InputChunk::from_bytes(chunk : Bytes) -> InputChunk {
  { chunk: Some(chunk), bytes_read: chunk.length(), }
}

///|
pub fn InputChunk::eof() -> InputChunk {
  { chunk: Some(Bytes::new(0)), bytes_read: 0, }
}

///|
pub(all) enum InputDecodeResult {
  CodePoint(Int32, bytes~ : Int32)
  DecodeError
} derive(Eq, Debug)

///|
pub fn Input::from_string(
  source : String,
  encoding~ : @api.InputEncoding = Utf8,
) -> Input {
  {
    encoding,
    source,
    bytes: encode_input_string(source, encoding),
    read_callback: None,
    decode_callback: None,
  }
}

///|
pub fn Input::from_bytes(
  source : Bytes,
  encoding~ : @api.InputEncoding = Utf8,
) -> Input {
  {
    encoding,
    source: "",
    bytes: source,
    read_callback: None,
    decode_callback: None,
  }
}

///|
pub fn Input::from_callback(
  read : (UInt32, @api.Point) -> InputChunk,
  encoding~ : @api.InputEncoding = Utf8,
) -> Input {
  {
    encoding,
    source: "",
    bytes: Bytes::new(0),
    read_callback: Some(read),
    decode_callback: None,
  }
}

///|
pub fn Input::from_custom_bytes(
  source : Bytes,
  decode : (Bytes, Int32) -> InputDecodeResult,
) -> Input {
  {
    encoding: Custom,
    source: "",
    bytes: source,
    read_callback: None,
    decode_callback: Some(decode),
  }
}

///|
pub fn Input::from_custom_callback(
  read : (UInt32, @api.Point) -> InputChunk,
  decode : (Bytes, Int32) -> InputDecodeResult,
) -> Input {
  {
    encoding: Custom,
    source: "",
    bytes: Bytes::new(0),
    read_callback: Some(read),
    decode_callback: Some(decode),
  }
}

///|
pub fn Input::encoding(self : Input) -> @api.InputEncoding {
  self.encoding
}

///|
pub fn Input::source(self : Input) -> String {
  self.source
}

///|
pub fn Input::bytes(self : Input) -> Bytes {
  self.bytes
}

///|
fn Input::materialize(self : Input) -> InputMaterialized raise ParseError {
  match self.read_callback {
    None => { bytes: self.bytes, chunk_end_offsets: [], }
    Some(read) =>
      materialize_input_callback(
        read,
        self.encoding,
        decode_callback=self.decode_callback,
      )
  }
}

///|
fn materialize_input_callback(
  read : (UInt32, @api.Point) -> InputChunk,
  encoding : @api.InputEncoding,
  decode_callback~ : ((Bytes, Int32) -> InputDecodeResult)?,
) -> InputMaterialized raise ParseError {
  let bytes : Array[Byte] = []
  let chunk_end_offsets : Array[Int32] = []
  let mut offset : UInt32 = 0
  let mut point = @api.Point::zero()
  while true {
    let result = read(offset, point)
    validate_input_chunk(result, byte_offset=offset, point~)
    if result.bytes_read == 0 {
      return { bytes: Bytes::from_array(bytes), chunk_end_offsets, }
    }
    guard result.chunk is Some(chunk) else {
      raise ParseError::InvalidInputCallback(
        byte_offset=offset,
        point~,
        reason="non-empty read without chunk",
      )
    }
    let start = bytes.length()
    for i in 0.. Unit raise ParseError {
  if result.bytes_read < 0 {
    raise ParseError::InvalidInputCallback(
      byte_offset~,
      point~,
      reason="negative bytes_read",
    )
  }
  match result.chunk {
    None =>
      if result.bytes_read != 0 {
        raise ParseError::InvalidInputCallback(
          byte_offset~,
          point~,
          reason="non-empty read without chunk",
        )
      }
    Some(chunk) => {
      if result.bytes_read > chunk.length() {
        raise ParseError::InvalidInputCallback(
          byte_offset~,
          point~,
          reason="bytes_read exceeds chunk length",
        )
      }
      if result.bytes_read == 0 && !chunk.is_empty() {
        raise ParseError::InvalidInputCallback(
          byte_offset~,
          point~,
          reason="EOF read returned non-empty chunk",
        )
      }
    }
  }
}

///|
fn advance_point_for_input_chunk(
  bytes : Bytes,
  start : Int32,
  bytes_read : Int32,
  encoding : @api.InputEncoding,
  decode_callback~ : ((Bytes, Int32) -> InputDecodeResult)?,
  point : @api.Point,
) -> @api.Point raise ParseError {
  let mut byte_index = start
  let end = start + bytes_read
  let mut current = point
  while byte_index < end {
    match decode_input_scalar(bytes, byte_index, encoding~, decode_callback~) {
      Some(scalar) => {
        let width = if byte_index + scalar.byte_width > end {
          end - byte_index
        } else {
          scalar.byte_width
        }
        current = advance_point(current, width, scalar.codepoint)
        byte_index += width
      }
      None => {
        current = advance_point(current, 1, decode_error_codepoint)
        byte_index += 1
      }
    }
  }
  current
}

///|
fn encode_input_string(source : String, encoding : @api.InputEncoding) -> Bytes {
  match encoding {
    Utf8 | Custom => @utf8.encode(source)
    Utf16LE => encode_utf16_string(source, little_endian=true)
    Utf16BE => encode_utf16_string(source, little_endian=false)
  }
}

///|
fn encode_utf16_string(source : String, little_endian~ : Bool) -> Bytes {
  let bytes : Array[Byte] = []
  for i in 0.. Unit {
  let low = (value % 0x100).to_byte()
  let high = (value / 0x100).to_byte()
  if little_endian {
    bytes.push(low)
    bytes.push(high)
  } else {
    bytes.push(high)
    bytes.push(low)
  }
}

///|
pub(all) enum LogType {
  Lex
  Parse
} derive(Eq, Debug)

///|
struct Logger {
  callback : (LogType, String) -> Unit
}

///|
pub fn Logger::new(callback : (LogType, String) -> Unit) -> Logger {
  { callback, }
}

///|
pub fn Logger::log(self : Logger, kind : LogType, message : String) -> Unit {
  (self.callback)(kind, message)
}