///|
/// A gRPC service declared with `#rpc` annotations. `greeter.g.mbt` is generated
/// from `greeter.mbt` by `cmd/rpc`; a project wires the generator as a dev_build
/// rule (see the README). The program checks the generated codec, service and
/// descriptor against the same ones written by hand in `hand.mbt`, byte for
/// byte, then shows the calls.
///
///     moon run src/examples/21-annotations
fn same(what : String, generated : String, hand : String) -> Unit {
  if generated != hand {
    abort("\{what} differs:\n  generated: \{generated}\n  by hand:   \{hand}")
  }
}

///|
fn hex(b : Bytes) -> String {
  let out = StringBuilder()
  for x in b {
    let n = x.to_int()
    out.write_char("0123456789abcdef"[n >> 4].unsafe_to_char())
    out.write_char("0123456789abcdef"[n & 15].unsafe_to_char())
  }
  out.to_string()
}

///|
/// One call on a fresh server, the frames shuttled until it completes.
fn call(
  register : (@moonrpc.H2Server) -> Unit,
  path : String,
  requests : Array[Bytes],
) -> @moonrpc.CallReply raise {
  let server = @moonrpc.H2Server::new()
  register(server)
  let client = @moonrpc.H2Client::new()
  let id = client.open(path)
  let to_server : Array[@http2.Frame] = [..client.preface()]
  for m in requests {
    to_server.append(client.send(id, m))
  }
  to_server.append(client.close_send(id))
  let to_client : Array[@http2.Frame] = [..server.preface()]
  let mut si = 0
  let mut ci = 0
  while !client.is_done(id) {
    if si < to_server.length() {
      to_client.append(server.feed(to_server[si]))
      si += 1
    } else if ci < to_client.length() {
      to_server.append(client.feed(to_client[ci]))
      ci += 1
    } else {
      break
    }
  }
  client.reply(id)
}

///|
fn shown(r : @moonrpc.CallReply) -> String {
  "status \{r.grpc_status} \{r.grpc_message} [\{r.messages.map(hex).join(", ")}]"
}

///|
#coverage.skip
fn main {
  let request : HelloRequest = {
    name: "Leo",
    tags: ["a", "bcd"],
    nickname: Some(""),
    mood: Grumpy,
    extras: { "calls": 42, "zero": 0 },
  }
  let bytes = @moonrpc.Message::encode(request)
  same("encoding", hex(bytes), hex(hand_request(request)))
  println("HelloRequest  \{hex(bytes)}")
  let back : HelloRequest = @moonrpc.Message::decode(bytes) catch {
    e => abort("decode: \{e}")
  }
  same("round trip", hex(@moonrpc.Message::encode(back)), hex(bytes))
  let greeter = Greeter::{ greeting: "hello", }
  let generated = (s : @moonrpc.H2Server) => greeter.register(s)
  let by_hand = (s : @moonrpc.H2Server) => hand_register(s, greeter)
  let calls = [
    ("/greet.Greeter/SayHello", [bytes]),
    ("/greet.Greeter/SayHello", [hand_request({ ..request, name: "", })]),
    ("/greet.Greeter/SayHello", [b"\x0a\x09short"]),
    // Mood 9 is no value of `Mood`: it reads as the first, `Calm`.
    ("/greet.Greeter/SayHello", [b"\x0a\x03Leo\x30\x09"]),
    ("/greet.Greeter/CountTo", [hand_int(1, 3)]),
    ("/greet.Greeter/Sum", [hand_int(1, 2), hand_int(1, 5), b""]),
    ("/greet.Greeter/Echo", [hand_int(1, 1), hand_int(1, 2), hand_int(1, 3)]),
  ]
  for c in calls {
    let (path, requests) = c
    let (g, h) = (
      call(generated, path, requests),
      call(by_hand, path, requests),
    ) catch {
      e => abort("call: \{e}")
    }
    same(path, shown(g), shown(h))
    println("\{path}  \{shown(g)}")
  }
  same(
    "descriptor",
    hex(descriptor().encode()),
    hex(hand_descriptor().encode()),
  )
  println("descriptor   \{descriptor().symbols().join(" ")}")
  println("")
  println(descriptor().source().trim_end(chars="\n"))
  println("")
  println("generated and hand-written services agree")
}