///|
/// An application that answers what it was asked, wrapped in a middleware that
/// writes one JSON line per ASGI message: the scope it was called with, every
/// `receive` and every `send`. The playground's right-hand column is this output.
///
///     moon run src/examples/11-event-log --target native
async fn main {
  @quic.serve_config(logged(answer), @mooncat.Config::new(port=12000), {
    certificate_file: "src/certs/cert.pem",
    private_key_file: "src/certs/key.pem",
  })
}

///|
/// Reads the whole body, then says what arrived; echoes WebSocket text.
async fn answer(
  scope : @moonasgi.Scope,
  receive : @moonasgi.Receive,
  send : @moonasgi.Send,
) -> Unit {
  match scope {
    Http(http) => {
      let mut size = 0
      for ;; {
        guard receive() is HttpRequest(body~, more_body~) else { return }
        size = size + body.length()
        if !more_body {
          break
        }
      }
      send(
        @moonasgi.Event::HttpResponseStart(
          status=200,
          headers=[("content-type", "text/plain")],
          trailers=false,
        ),
      )
      send(
        @moonasgi.Event::HttpResponseBody(
          body=@utf8.encode(
            "HTTP/\{http.http_version} \{http.http_method} \{http.path} ยท \{size} B\n",
          ),
          more_body=false,
        ),
      )
    }
    WebSocket(_) =>
      for ;; {
        match receive() {
          WebSocketConnect =>
            send(@moonasgi.Event::WebSocketAccept(subprotocol=None, headers=[]))
          WebSocketReceive(text=Some(text), ..) =>
            send(@moonasgi.Event::WebSocketSendText("echo: " + text))
          WebSocketDisconnect(..) => break
          _ => ()
        }
      }
    Lifespan(_) =>
      for ;; {
        match receive() {
          LifespanStartup => send(@moonasgi.Event::LifespanStartupComplete)
          LifespanShutdown => {
            send(@moonasgi.Event::LifespanShutdownComplete)
            break
          }
          _ => ()
        }
      }
  }
}

///|
/// Any application, with every message it sees and sends written down. A message
/// is logged before it is handed on, so a line never claims what has not happened.
fn logged(app : @moonasgi.AsgiApp) -> @moonasgi.AsgiApp {
  (scope, receive, send) => {
    let peer = peer_of(scope)
    emit(peer, "scope", scope_json(scope))
    app(
      scope,
      async fn() {
        let event = receive()
        emit(peer, "receive", event_json(event))
        event
      },
      async fn(event) {
        emit(peer, "send", event_json(event))
        send(event)
      },
    )
  }
}

///|
/// Times are milliseconds since the server started, on the monotonic clock: the
/// wall clock ticks too coarsely on Windows to order a request's own steps.
let started : @bench.Timestamp = @bench.monotonic_clock_start()

///|
/// Two streams of one HTTP/2 connection log at once, and a descriptor takes one
/// writer at a time.
let writing : @async.Mutex = @async.Mutex()

///|
/// Straight to the descriptor: a buffered line would reach a pipe only when the
/// process exits, and the recorder reads while the server runs.
async fn emit(peer : String, call : String, message : Json) -> Unit {
  writing.acquire()
  defer writing.release()
  @stdio.stdout.write(
    Json::object({
      "t": Json::number(@bench.monotonic_clock_end(started) / 1000.0),
      "peer": Json::string(peer),
      "call": Json::string(call),
      "message": message,
    }).stringify() +
    "\n",
  )
}

///|
fn peer_of(scope : @moonasgi.Scope) -> String {
  let client = match scope {
    Http(http) => http.client
    WebSocket(ws) => ws.client
    Lifespan(_) => None
  }
  match client {
    Some((host, port)) => "\{host}:\{port}"
    None => ""
  }
}

///|
fn pairs(headers : Array[(String, String)]) -> Json {
  Json::array(
    headers.map(h => Json::array([Json::string(h.0), Json::string(h.1)])),
  )
}

///|
/// The scope as ASGI writes it, minus the fields this server never fills.
fn scope_json(scope : @moonasgi.Scope) -> Json {
  match scope {
    Http(http) =>
      Json::object({
        "type": Json::string("http"),
        "http_version": Json::string(http.http_version),
        "method": Json::string(http.http_method),
        "scheme": Json::string(http.scheme),
        "path": Json::string(http.path),
        "headers": pairs(http.headers),
      })
    WebSocket(ws) =>
      Json::object({
        "type": Json::string("websocket"),
        "http_version": Json::string(ws.http_version),
        "scheme": Json::string(ws.scheme),
        "path": Json::string(ws.path),
        "headers": pairs(ws.headers),
      })
    Lifespan(_) => Json::object({ "type": Json::string("lifespan") })
  }
}

///|
fn text(body : Bytes) -> Json {
  Json::string(@utf8.decode_lossy(body[:]))
}

///|
/// A message as the ASGI specification spells it: its `type`, then its keys.
fn event_json(event : @moonasgi.Event) -> Json {
  let (kind, keys) : (String, Map[String, Json]) = match event {
    HttpRequest(body~, more_body~) =>
      (
        "http.request",
        { "body": text(body), "more_body": Json::boolean(more_body) },
      )
    HttpDisconnect => ("http.disconnect", {})
    HttpResponseStart(status~, headers~, trailers~) =>
      (
        "http.response.start",
        {
          "status": Json::number(status.to_double()),
          "headers": pairs(headers),
          "trailers": Json::boolean(trailers),
        },
      )
    HttpResponseBody(body~, more_body~) =>
      (
        "http.response.body",
        { "body": text(body), "more_body": Json::boolean(more_body) },
      )
    WebSocketConnect => ("websocket.connect", {})
    WebSocketAccept(subprotocol~, ..) =>
      (
        "websocket.accept",
        {
          "subprotocol": match subprotocol {
            Some(name) => Json::string(name)
            None => Json::null()
          },
        },
      )
    WebSocketReceive(text=Some(message), ..) =>
      ("websocket.receive", { "text": Json::string(message) })
    WebSocketReceive(bytes~, ..) =>
      ("websocket.receive", { "bytes": text(bytes.unwrap_or(b"")) })
    WebSocketSendText(message) =>
      ("websocket.send", { "text": Json::string(message) })
    WebSocketSendBytes(bytes) => ("websocket.send", { "bytes": text(bytes) })
    WebSocketDisconnect(code~, ..) =>
      ("websocket.disconnect", { "code": Json::number(code.to_double()) })
    WebSocketClose(code~, reason~) =>
      (
        "websocket.close",
        {
          "code": Json::number(code.to_double()),
          "reason": Json::string(reason),
        },
      )
    LifespanStartup => ("lifespan.startup", {})
    LifespanStartupComplete => ("lifespan.startup.complete", {})
    LifespanShutdown => ("lifespan.shutdown", {})
    LifespanShutdownComplete => ("lifespan.shutdown.complete", {})
    Other(type_~, ..) => (type_, {})
    _ => ("message", {})
  }
  keys["type"] = Json::string(kind)
  Json::object(keys)
}