///|
/// The WebSocket frame codec (RFC 6455 §5.2): a server text frame (unmasked) and
/// a masked client binary frame both round-trip through the decoder, which
/// unmasks the payload; a close frame carries its 16-bit code then the reason;
/// the opcode wire values map both ways; and the receive rules reject what a
/// server must never accept from a client.
///
/// `client` picks which side's rules apply. It defaults to a server reading a
/// client, for whom masking is mandatory (§5.1), so decoding a frame the server
/// itself sent asks for the other direction.
///
///     moon run src/examples/05-ws-frame
#coverage.skip
fn main {
  let text : @ws.Frame = {
    fin: true,
    opcode: Text,
    payload: b"hello heke1228",
  }
  let text = text.encode()
  println("text frame     = \{hex(text)}")
  println("as server frame  = \{describe(text, false)}")
  // The very same bytes read as if a client had sent them: §5.1 requires a mask.
  println("as client frame  = \{describe(text, true)}")
  let binary : @ws.Frame = {
    fin: true,
    opcode: Binary,
    payload: b"\x00\x01\x02\xfe\xff",
  }
  let masked = binary.encode(mask=b"\x37\xfa\x21\x3d")
  println("masked frame   = \{hex(masked)}")
  println("as client frame  = \{describe(masked, true)}")
  // A reserved opcode (0x3) names the close code the connection must be failed with.
  println("reserved opcode  = \{describe(b"\x83\x80\x00\x00\x00\x00", true)}")
  println("close payload  = \{hex(@ws.close(1000, reason="bye"))}")
  println(
    "opcode Ping wire = \{@ws.Opcode::code(Ping)}, of_int(0xA) is Pong = \{@ws.Opcode::of(0xA) is Some(Pong)}",
  )
}

///|
/// Decode one frame and say what came back. The decoder answers three ways: a frame, `None` for a
/// buffer that does not hold a whole one yet, or a refusal naming the RFC 6455 §7.4.1 status the
/// connection must then be failed with.
fn describe(frame : Bytes, client : Bool) -> String {
  try {
    match @ws.decode(frame[:], client~) {
      Some((f, n)) =>
        "fin=\{f.fin} opcode=\{op(f.opcode)} payload=\{ascii(f.payload)} consumed=\{n}"
      None => "incomplete"
    }
  } catch {
    @ws.Protocol(why) => "failed with 1002: \{why}"
    @ws.Payload(why) => "failed with 1007: \{why}"
    @ws.Exceeded(limit~, got~) => "failed with 1009: \{got} past \{limit}"
  }
}

///|
fn op(o : @ws.Opcode) -> String {
  match o {
    Continuation => "continuation"
    Text => "text"
    Binary => "binary"
    Close => "close"
    Ping => "ping"
    Pong => "pong"
  }
}

///|
fn hex(b : Bytes) -> String {
  let digits = "0123456789abcdef"
  let sb = StringBuilder()
  for i = 0; i < b.length(); i = i + 1 {
    let x = b[i].to_int()
    sb.write_char(digits[x >> 4].unsafe_to_char())
    sb.write_char(digits[x & 0xf].unsafe_to_char())
  }
  sb.to_string()
}

///|
fn ascii(b : Bytes) -> String {
  let sb = StringBuilder()
  for i = 0; i < b.length(); i = i + 1 {
    sb.write_char(b[i].to_int().unsafe_to_char())
  }
  sb.to_string()
}