// Byte transport for the memcached text protocol client.
//
// The client owns exactly one connection and speaks the text protocol over it.
// Keeping the channel behind a trait lets the protocol logic run against an
// in-memory transcript, which is what the targets available here can verify.

///|
/// Failure raised while moving bytes to or from the server.
///
/// The variants stay constructible outside the package: a `Connection`
/// implementation living in another package has to be able to raise them.
pub(all) suberror TransportError {
  /// The underlying channel failed, e.g. it was reset or ended early.
  Io(String)
} derive(@debug.Debug)

///|
/// A duplex byte channel.
pub(open) trait Connection {
  /// Send every byte of `data` to the peer.
  fn write(Self, BytesView) -> Unit raise TransportError
  /// Receive at most `max` bytes, waiting until at least one byte is available.
  ///
  /// An empty result means the peer closed the connection.
  fn read(Self, Int) -> Bytes raise TransportError
  /// Close the channel and release its resources.
  fn close(Self) -> Unit raise TransportError
}

///|
/// An in-memory [`Connection`] that replays a canned server transcript.
///
/// Everything the client writes is recorded, so the connection doubles as a
/// spy. It stands in for a real socket in the examples and tests, since the
/// targets usable here cannot open one.
pub struct ScriptedConnection {
  chunks : Array[Bytes]
  mut index : Int
  mut pending : Bytes
  written : @buffer.Buffer
}

///|
/// Build a connection whose reads replay `chunks` in order.
///
/// Each element is handed out by a separate read (truncated to the requested
/// size), which makes it easy to force the client through partial responses.
pub fn ScriptedConnection::new(chunks : Array[Bytes]) -> ScriptedConnection {
  { chunks, index: 0, pending: Bytes::new(0), written: @buffer.Buffer() }
}

///|
/// Every byte the client has written so far.
pub fn ScriptedConnection::written(self : ScriptedConnection) -> Bytes {
  self.written.to_bytes()
}

///|
pub impl Connection for ScriptedConnection with fn write(self, data) {
  self.written.write_bytesview(data)
}

///|
pub impl Connection for ScriptedConnection with fn read(self, max) {
  while self.pending.length() == 0 {
    guard self.index < self.chunks.length() else { return Bytes::new(0) }
    self.pending = self.chunks[self.index]
    self.index += 1
  }
  let take = if self.pending.length() < max {
    self.pending.length()
  } else {
    max
  }
  let head = self.pending.view(end=take).to_owned()
  self.pending = self.pending.view(start=take).to_owned()
  head
}

///|
pub impl Connection for ScriptedConnection with fn close(self) {
  self.index = self.chunks.length()
  self.pending = Bytes::new(0)
}