///|
/// The Bolt transport abstraction: a byte pipe between the client and the
/// server. The Bolt protocol above this layer — the handshake and the chunked
/// message framing — is built entirely on [`Transport`], so a state machine can
/// be driven over any concrete transport (a TCP socket in production, or the
/// in-memory [`MockTransport`] in tests).
///
/// The three methods mirror what a blocking socket provides: `write` sends
/// bytes, `read_exact` reads exactly `n` bytes (returning `None` on EOF before
/// `n` bytes arrive), and `close` tears the connection down.

///|
/// A raw, byte-oriented transport for Bolt.
pub trait Transport {
  ///| Send `bytes` to the peer.
  fn write(Self, Bytes) -> Unit
  ///| Read exactly `n` bytes, or `None` if the connection closes first.
  fn read_exact(Self, Int) -> Bytes?
  ///| Close the connection.
  fn close(Self) -> Unit
}

///|
/// An in-memory [`Transport`] used to test the protocol layer without a
/// network. Tests pre-load the server's reply with [`MockTransport::feed`] and
/// inspect what the client sent with [`MockTransport::outgoing`].
pub struct MockTransport {
  inbound : Buffer
  mut inpos : Int
  outbound : Buffer
  mut closed : Bool
}

///|
pub fn MockTransport::new() -> MockTransport {
  {
    inbound: Buffer::Buffer(),
    inpos: 0,
    outbound: Buffer::Buffer(),
    closed: false,
  }
}

///|
/// Append bytes the "server" will later send to the client.
pub fn MockTransport::feed(self : MockTransport, bytes : Bytes) -> Unit {
  self.inbound.write_bytes(bytes.exact_view())
}

///|
/// The bytes the client has written so far.
pub fn MockTransport::outgoing(self : MockTransport) -> Bytes {
  self.outbound.to_bytes()
}

///|
/// Whether [`MockTransport::close`] has been called.
pub fn MockTransport::is_closed(self : MockTransport) -> Bool {
  self.closed
}

///|
pub impl Transport for MockTransport with fn write(self, bytes) {
  self.outbound.write_bytes(bytes.exact_view())
}

///|
pub impl Transport for MockTransport with fn read_exact(self, n) {
  let all = self.inbound.to_bytes()
  if self.inpos + n > all.length() {
    None
  } else {
    let chunk = all.exact_view(start=self.inpos, end=self.inpos + n).to_owned()
    self.inpos = self.inpos + n
    Some(chunk)
  }
}

///|
pub impl Transport for MockTransport with fn close(self) {
  self.closed = true
}

///|
/// Re-export the transport methods as regular methods on [`MockTransport`].
pub extend MockTransport with Transport::{write, read_exact, close}