///|
/// Incremental decoder for chunked VCD input.
pub struct StreamDecoder {
  mut pending : String
  mut record_buffer : String
  mut records : Int
  mut lines : Int
  mut finished : Bool
} derive(Debug, Eq)

///|
/// Create an empty chunk decoder.
pub fn new_stream_decoder() -> StreamDecoder {
  { pending: "", record_buffer: "", records: 0, lines: 0, finished: false }
}

///|
/// Number of complete records emitted by this decoder.
pub fn StreamDecoder::record_count(self : StreamDecoder) -> Int {
  self.records
}

///|
/// Number of lines received by this decoder.
pub fn StreamDecoder::line_count(self : StreamDecoder) -> Int {
  self.lines
}

///|
/// Feed a chunk and emit all complete VCD documents ending in `//`.
pub fn StreamDecoder::feed(
  self : StreamDecoder,
  chunk : String,
) -> Result[Array[Trace], ParseError] {
  if self.finished {
    return Err(InvalidDirective("stream already finished"))
  }
  let combined = self.pending + chunk
  let ready : Array[Trace] = []
  let lines = combined.split("\n").map(fn(line) { line.to_owned() }).to_array()
  if combined.has_suffix("\n") {
    self.pending = ""
  } else {
    self.pending = match lines.pop() {
      Some(tail) => tail
      None => ""
    }
  }
  for line in lines {
    self.lines += 1
    self.record_buffer = self.record_buffer + line + "\n"
    if line.trim().to_owned() == "//" {
      let record_text = self.record_buffer
      self.record_buffer = ""
      if record_text.trim().to_owned() != "//" {
        match parse(record_text) {
          Ok(trace) => ready.push(trace)
          Err(error) => return Err(error)
        }
        self.records += 1
      }
    }
  }
  Ok(ready)
}

///|
/// Finish a stream and parse its final record when present.
pub fn StreamDecoder::finish(
  self : StreamDecoder,
) -> Result[Array[Trace], ParseError] {
  if self.finished {
    return Ok([])
  }
  self.finished = true
  if self.pending.trim().to_owned() != "" {
    self.record_buffer = self.record_buffer + self.pending
    self.pending = ""
  }
  if self.record_buffer.trim().to_owned() == "" {
    Ok([])
  } else {
    self.records += 1
    match parse(self.record_buffer) {
      Ok(trace) => Ok([trace])
      Err(error) => Err(error)
    }
  }
}

///|
/// Parse a complete stream in chunks of a fixed size.
pub fn parse_chunked(
  input : String,
  chunk_size : Int,
) -> Result[Array[Trace], ParseError] {
  let decoder = new_stream_decoder()
  let safe = Int::max(1, chunk_size)
  let result : Array[Trace] = []
  let mut start = 0
  while start < input.length() {
    let end = Int::min(input.length(), start + safe)
    let chunk = input.unsafe_substring(start~, end~)
    match decoder.feed(chunk) {
      Ok(traces) => result.append(traces)
      Err(error) => return Err(error)
    }
    start = end
  }
  match decoder.finish() {
    Ok(traces) => {
      result.append(traces)
      Ok(result)
    }
    Err(error) => Err(error)
  }
}