///|
/// A read-only chunk view over an encoded GIF for sockets and file sinks.
pub(all) struct GifChunk {
  index : Int
  offset : Int
  data : Bytes
  is_last : Bool
} derive(Debug)

///|
pub fn GifChunk::index(self : GifChunk) -> Int {
  self.index
}

///|
pub fn GifChunk::offset(self : GifChunk) -> Int {
  self.offset
}

///|
pub fn GifChunk::data(self : GifChunk) -> Bytes {
  self.data
}

///|
pub fn GifChunk::is_final(self : GifChunk) -> Bool {
  self.is_last
}

///|
pub(all) struct GifByteStream {
  bytes : Bytes
  chunk_size : Int
} derive(Debug)

///|
pub fn GifByteStream::new(bytes : Bytes, chunk_size : Int) -> GifByteStream {
  { bytes, chunk_size: if chunk_size < 1 { 1 } else { chunk_size } }
}

///|
pub fn GifByteStream::length(self : GifByteStream) -> Int {
  self.bytes.length()
}

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

///|
pub fn GifByteStream::chunk_size(self : GifByteStream) -> Int {
  self.chunk_size
}

///|
pub fn GifByteStream::chunk_count(self : GifByteStream) -> Int {
  if self.bytes.length() == 0 {
    0
  } else {
    (self.bytes.length() + self.chunk_size - 1) / self.chunk_size
  }
}

///|
pub fn GifByteStream::chunk(self : GifByteStream, index : Int) -> GifChunk? {
  if index < 0 || index >= self.chunk_count() {
    None
  } else {
    let offset = index * self.chunk_size
    let remaining = self.bytes.length() - offset
    let length = if remaining > self.chunk_size {
      self.chunk_size
    } else {
      remaining
    }
    let payload : Array[Byte] = []
    for i in 0.. Array[GifChunk] {
  let result : Array[GifChunk] = []
  for index in 0.. ()
      Some(chunk) => result.push(chunk)
    }
  }
  result
}

///|
/// Encode an animation and expose the result as fixed-size chunks.
pub fn encode_stream(
  animation : Animation,
  options? : EncodeOptions = EncodeOptions::default(),
  chunk_size? : Int = 4096,
) -> GifByteStream raise GifError {
  GifByteStream::new(encode_with_options(animation, options), chunk_size)
}

///|
/// A collector for asynchronous or network-delivered GIF chunks.
pub(all) struct GifStreamCollector {
  parts : Array[Bytes]
  total_bytes : Int
} derive(Debug)

///|
pub fn GifStreamCollector::new() -> GifStreamCollector {
  { parts: [], total_bytes: 0 }
}

///|
pub fn GifStreamCollector::push(
  self : GifStreamCollector,
  chunk : Bytes,
) -> GifStreamCollector {
  self.parts.push(chunk)
  { ..self, total_bytes: self.total_bytes + chunk.length() }
}

///|
pub fn GifStreamCollector::part_count(self : GifStreamCollector) -> Int {
  self.parts.length()
}

///|
pub fn GifStreamCollector::total_bytes(self : GifStreamCollector) -> Int {
  self.total_bytes
}

///|
pub fn GifStreamCollector::finish(self : GifStreamCollector) -> Bytes {
  let result : Array[Byte] = []
  for part in self.parts {
    for byte in part {
      result.push(byte)
    }
  }
  Bytes::from_array(result[:])
}

///|
/// Decode a collected stream after checking its byte-level structure.
pub fn GifStreamCollector::decode(
  self : GifStreamCollector,
) -> Animation raise GifError {
  decode(self.finish())
}

///|
/// Feed chunks in arbitrary order and reassemble by their declared offset.
pub fn collect_chunks(chunks : ArrayView[GifChunk]) -> Bytes {
  let mut total = 0
  for chunk in chunks {
    let end = chunk.offset() + chunk.data().length()
    if end > total {
      total = end
    }
  }
  let result = Array::make(total, b'\x00')
  for chunk in chunks {
    let data = chunk.data()
    for i in 0.. Bool {
  let mut expected = 0
  let mut index = 0
  for chunk in chunks {
    if chunk.index() != index || chunk.offset() != expected {
      return false
    }
    expected += chunk.data().length()
    index += 1
  }
  chunks.length() == 0 || chunks[chunks.length() - 1].is_final()
}

///|
/// Return a stream digest suitable for resumable transfer logs.
pub fn GifByteStream::checksum(self : GifByteStream) -> UInt {
  let mut hash : UInt = 2166136261
  for byte in self.bytes {
    hash = (hash ^ byte.to_int().reinterpret_as_uint()) * 16777619
  }
  hash
}

///|
/// Find a safe chunk size for a transport with a maximum packet budget.
pub fn recommended_chunk_size(packet_budget : Int) -> Int {
  if packet_budget < 64 {
    64
  } else if packet_budget > 65536 {
    65536
  } else {
    packet_budget
  }
}

///|
/// Split an encoded animation into chunks without duplicating the source buffer.
pub fn encode_chunks(
  animation : Animation,
  options? : EncodeOptions = EncodeOptions::default(),
  chunk_size? : Int = 4096,
) -> Array[GifChunk] raise GifError {
  encode_stream(animation, options~, chunk_size~).chunks()
}