open Base
module Arg = Stdlib.Arg

open Httpun_eio
open Httpun

let error_handler (_ : Eio.Net.Sockaddr.stream) = Httpun_examples.Server.error_handler

let request_handler ~u (_ : Eio.Net.Sockaddr.stream) { Gluten.reqd; _ } =
    match Reqd.request reqd  with
    | { Request.meth = `POST; headers; _ } ->
      let response =
        let content_type =
          match Headers.get headers "content-type" with
          | None   -> "application/octet-stream"
          | Some x -> x
        in
        Response.create
          ~headers:(Headers.of_list
            [ "content-type", content_type;
            "transfer-encoding", "chunked"
            (* ; "connection", "close" *)
            ]) `OK
      in
      let request_body  = Reqd.request_body reqd in
      let response_body = Reqd.respond_with_streaming reqd response in
      let rec on_read buffer ~off ~len =
        Body.Writer.write_bigstring response_body buffer ~off ~len;
        Body.Reader.schedule_read request_body ~on_eof ~on_read;
      and on_eof () =
        Stdlib.Format.eprintf "EOF@.";
        Body.Writer.close response_body;
        Eio.Promise.resolve_ok u ()
      in
      Body.Reader.schedule_read (Reqd.request_body reqd) ~on_eof ~on_read
    | _ ->
      let headers = Headers.of_list [ "connection", "close" ] in
      Reqd.respond_with_string reqd (Response.create ~headers `Method_not_allowed) ""
;;


let log_connection_error ex =
  Eio.traceln "Uncaught exception handling client: %a" Fmt.exn ex

let main port =
  Eio_main.run (fun env ->
  let listen_address = (`Tcp (Eio.Net.Ipaddr.V4.loopback, port)) in
  let network = Eio.Stdenv.net env in
  let handler ~u =
    Server.create_connection_handler ~request_handler:(request_handler ~u) ~error_handler in
  Eio.Switch.run (fun sw ->
    let socket =
      Eio.Net.listen ~reuse_addr:true  ~reuse_port:true ~backlog:5 ~sw
      network
      listen_address
    in
  Stdio.printf "Listening on port %i and echoing POST requests.\n" port;
  Stdio.printf "To send a POST request, try one of the following\n\n";
  Stdio.printf "  echo \"Testing echo POST\" | dune exec examples/async/async_post.exe\n";
  Stdio.printf "  echo \"Testing echo POST\" | dune exec examples/lwt/lwt_post.exe\n";
  Stdio.printf "  echo \"Testing echo POST\" | curl -XPOST --data @- http://localhost:%d\n\n%!" port;
  let domain_mgr = Eio.Stdenv.domain_mgr env in
  let p, _ = Eio.Promise.create () in
  for _i = 1 to Stdlib.Domain.recommended_domain_count () do
    Eio.Fiber.fork_daemon ~sw (fun () ->
      Eio.Domain_manager.run domain_mgr (fun () ->
        Eio.Switch.run (fun sw ->
          while true do
            Eio.Net.accept_fork socket ~sw ~on_error:log_connection_error (fun client_sock client_addr ->
                let p, u = Eio.Promise.create () in
                handler ~sw ~u client_addr client_sock;
                Eio.Promise.await_exn p)
          done;
        `Stop_daemon)))
  done;
  Eio.Promise.await p));

;;

let () =
  let port = ref 8080 in
  Arg.parse
    ["-p", Arg.Set_int port, " Listening port number (8080 by default)"]
    ignore
    "Echoes POST requests. Runs forever.";
  main !port
;;
