///|
/// A queue of immutable input chunks. Bytes are copied only by `copy_exact`.
pub struct ChunkBuffer {
  mut chunks : Array[Bytes]
  mut first_chunk : Int
  mut first_offset : Int
  mut available_bytes : Int
  mut copied_bytes_value : Int
} derive(Debug)

///|
pub fn ChunkBuffer::new() -> ChunkBuffer {
  {
    chunks: [],
    first_chunk: 0,
    first_offset: 0,
    available_bytes: 0,
    copied_bytes_value: 0,
  }
}

///|
pub fn ChunkBuffer::append(self : ChunkBuffer, chunk : Bytes) -> Unit {
  if chunk.length() != 0 {
    self.chunks.push(chunk)
    self.available_bytes = self.available_bytes + chunk.length()
  }
}

///|
pub fn ChunkBuffer::available(self : ChunkBuffer) -> Int {
  self.available_bytes
}

///|
pub fn ChunkBuffer::chunk_count(self : ChunkBuffer) -> Int {
  self.chunks.length() - self.first_chunk
}

///|
pub fn ChunkBuffer::copied_bytes(self : ChunkBuffer) -> Int {
  self.copied_bytes_value
}

///|
pub fn ChunkBuffer::peek_byte(self : ChunkBuffer, offset : Int) -> Byte? {
  if offset < 0 || offset >= self.available_bytes {
    return None
  }
  let mut remaining = offset
  let mut index = self.first_chunk
  let mut position = self.first_offset
  while index < self.chunks.length() {
    let chunk = self.chunks[index]
    let usable = chunk.length() - position
    if remaining < usable {
      return Some(chunk[position + remaining])
    }
    remaining = remaining - usable
    index = index + 1
    position = 0
  }
  None
}

///|
pub fn ChunkBuffer::copy_exact(self : ChunkBuffer, length : Int) -> Bytes? {
  if length < 0 || length > self.available_bytes {
    return None
  }
  let output = @buffer.Buffer(size_hint=length)
  for i = 0; i < length; i = i + 1 {
    match self.peek_byte(i) {
      Some(byte) => output.write_byte(byte)
      None => return None
    }
  }
  self.copied_bytes_value = self.copied_bytes_value + length
  Some(output.to_bytes())
}

///|
pub fn ChunkBuffer::discard(self : ChunkBuffer, length : Int) -> Bool {
  if length < 0 || length > self.available_bytes {
    return false
  }
  let mut left = length
  while left > 0 {
    let chunk = self.chunks[self.first_chunk]
    let usable = chunk.length() - self.first_offset
    if left < usable {
      self.first_offset = self.first_offset + left
      left = 0
    } else {
      left = left - usable
      self.first_chunk = self.first_chunk + 1
      self.first_offset = 0
    }
  }
  self.available_bytes = self.available_bytes - length
  if self.available_bytes == 0 {
    self.clear()
  }
  true
}

///|
pub fn ChunkBuffer::clear(self : ChunkBuffer) -> Unit {
  self.chunks = []
  self.first_chunk = 0
  self.first_offset = 0
  self.available_bytes = 0
  self.copied_bytes_value = 0
}