module type S = sig
  type stack
  type ipaddr

  module TCP : sig
    include Mirage_flow.S

    val dst : flow -> ipaddr * int
    val no_close : flow -> unit
    val to_close : flow -> unit
  end

  module TLS : sig
    type error =
      [ `Tls_alert of Tls.Packet.alert_type
      | `Tls_failure of Tls.Engine.failure
      | `Read of TCP.error
      | `Write of TCP.write_error ]

    type write_error = [ `Closed | error ]

    include
      Mirage_flow.S with type error := error and type write_error := write_error

    val no_close : flow -> unit
    val to_close : flow -> unit
    val epoch : flow -> (Tls.Core.epoch_data, unit) result

    val reneg :
      ?authenticator:X509.Authenticator.t ->
      ?acceptable_cas:X509.Distinguished_name.t list ->
      ?cert:Tls.Config.own_cert ->
      ?drop:bool ->
      flow ->
      (unit, [ write_error | `Msg of string ]) result Lwt.t

    val key_update :
      ?request:bool ->
      flow ->
      (unit, [ write_error | `Msg of string ]) result Lwt.t

    val server_of_flow :
      Tls.Config.server -> TCP.flow -> (flow, write_error) result Lwt.t

    val client_of_flow :
      Tls.Config.client ->
      ?host:[ `host ] Domain_name.t ->
      TCP.flow ->
      (flow, write_error) result Lwt.t
  end

  val tcp_protocol : (stack * ipaddr * int, TCP.flow) Mimic.protocol
  val tcp_edn : (stack * ipaddr * int) Mimic.value

  val tls_edn :
    ([ `host ] Domain_name.t option * Tls.Config.client * stack * ipaddr * int)
    Mimic.value

  val tls_protocol :
    ( [ `host ] Domain_name.t option * Tls.Config.client * stack * ipaddr * int,
      TLS.flow )
    Mimic.protocol

  type t
  type dst = ipaddr * int

  val init : port:int -> stack -> t Lwt.t
  val accept : t -> (TCP.flow, [> `Closed ]) result Lwt.t
  val close : t -> unit Lwt.t

  val http_service :
    ?config:H1.Config.t ->
    error_handler:(dst -> H1.Server_connection.error_handler) ->
    (TCP.flow -> dst -> H1.Server_connection.request_handler) ->
    t Paf.service

  val https_service :
    tls:Tls.Config.server ->
    ?config:H1.Config.t ->
    error_handler:(dst -> H1.Server_connection.error_handler) ->
    (TLS.flow -> dst -> H1.Server_connection.request_handler) ->
    t Paf.service

  val alpn_service :
    tls:Tls.Config.server ->
    ?config:H1.Config.t * H2.Config.t ->
    (TLS.flow, dst) Alpn.server_handler ->
    t Paf.service

  val serve :
    ?stop:Lwt_switch.t -> 't Paf.service -> 't -> [ `Initialized of unit Lwt.t ]
end

module Make (Stack : Tcpip.Tcp.S) :
  S with type stack = Stack.t and type ipaddr = Stack.ipaddr = struct
  open Lwt.Infix

  type ipaddr = Stack.ipaddr
  type dst = ipaddr * int

  module TCP = struct
    let src = Logs.Src.create "paf-tcp"

    module Log = (val Logs.src_log src : Logs.LOG)
    include Stack

    type nonrec flow = { flow : flow; mutable no_close : bool }
    type endpoint = Stack.t * Stack.ipaddr * int

    type nonrec write_error =
      [ `Write of write_error | `Connect of error | `Closed ]

    let pp_write_error ppf = function
      | `Write err | (`Closed as err) -> pp_write_error ppf err
      | `Connect err -> pp_error ppf err

    let read flow = read flow.flow
    let dst flow = dst flow.flow

    let write flow cs =
      write flow.flow cs >>= function
      | Ok _ as v -> Lwt.return v
      | Error err -> Lwt.return_error (`Write err)

    let writev flow css =
      writev flow.flow css >>= function
      | Ok _ as v -> Lwt.return v
      | Error err -> Lwt.return_error (`Write err)

    let connect (stack, ipaddr, port) =
      create_connection stack (ipaddr, port) >>= function
      | Ok flow -> Lwt.return_ok { flow; no_close = false }
      | Error err -> Lwt.return_error (`Connect err)

    let no_close flow = flow.no_close <- true
    let to_close flow = flow.no_close <- false

    let close flow =
      match flow.no_close with
      | true ->
          Log.debug (fun m -> m "Fakely close the connection.") ;
          Lwt.return_unit
      | false ->
          Log.debug (fun m -> m "Really close the connection.") ;
          close flow.flow

    let shutdown flow = shutdown flow.flow
  end

  module TLS = struct
    let src = Logs.Src.create "paf-tls"

    module Log = (val Logs.src_log src : Logs.LOG)
    include Tls_mirage.Make (TCP)

    type endpoint =
      [ `host ] Domain_name.t option
      * Tls.Config.client
      * Stack.t
      * Stack.ipaddr
      * int

    type nonrec flow = TCP.flow * flow

    let connect (host, cfg, stack, ipaddr, port) =
      Stack.create_connection stack (ipaddr, port) >>= function
      | Error err -> Lwt.return_error (`Read err)
      | Ok flow ->
          let open Lwt_result.Infix in
          let tcp_flow = { TCP.flow; TCP.no_close = false } in
          client_of_flow cfg ?host tcp_flow >>= fun tls_flow ->
          Lwt.return_ok (tcp_flow, tls_flow)

    let no_close (tcp_flow, _) = TCP.no_close tcp_flow
    let to_close (tcp_flow, _) = TCP.to_close tcp_flow
    let read (_, tls_flow) = read tls_flow
    let write (_, tls_flow) = write tls_flow
    let writev (_, tls_flow) = writev tls_flow
    let shutdown (_, tls_flow) = shutdown tls_flow
    let epoch (_, tls_flow) = epoch tls_flow

    let reneg ?authenticator ?acceptable_cas ?cert ?drop (_, tls_flow) =
      reneg ?authenticator ?acceptable_cas ?cert ?drop tls_flow

    let key_update ?request (_, tls_flow) = key_update ?request tls_flow

    let server_of_flow config tcp_flow =
      Lwt_result.Infix.(
        server_of_flow config tcp_flow >>= fun tls_flow ->
        Lwt.return_ok (tcp_flow, tls_flow))

    let client_of_flow config ?host tcp_flow =
      Lwt_result.Infix.(
        client_of_flow config ?host tcp_flow >>= fun tls_flow ->
        Lwt.return_ok (tcp_flow, tls_flow))

    let close (tcp_flow, tls_flow) =
      match tcp_flow.TCP.no_close with
      | true -> Lwt.return_unit
      | false -> close tls_flow
  end

  let src = Logs.Src.create "paf-layer"

  module Log = (val Logs.src_log src : Logs.LOG)

  type stack = Stack.t

  let tcp_edn, tcp_protocol = Mimic.register ~name:"tcp" (module TCP)

  let tls_edn, tls_protocol =
    Mimic.register ~priority:10 ~name:"tls" (module TLS)

  type t = {
    stack : Stack.t;
    queue : Stack.flow Queue.t;
    condition : unit Lwt_condition.t;
    mutex : Lwt_mutex.t;
    mutable closed : bool;
  }

  let init ~port stack =
    let queue = Queue.create () in
    let condition = Lwt_condition.create () in
    let mutex = Lwt_mutex.create () in
    let listener flow =
      Lwt_mutex.lock mutex >>= fun () ->
      Queue.push flow queue ;
      Lwt_condition.signal condition () ;
      Lwt_mutex.unlock mutex ;
      Lwt.return () in
    Stack.listen ~port stack listener ;
    Lwt.return { stack; queue; condition; mutex; closed = false }

  let rec accept ({ queue; condition; mutex; _ } as t) =
    Lwt_mutex.lock mutex >>= fun () ->
    let rec await () =
      if Queue.is_empty queue && not t.closed
      then Lwt_condition.wait condition ~mutex >>= await
      else Lwt.return_unit in
    await () >>= fun () ->
    match Queue.pop queue with
    | flow ->
        Lwt_mutex.unlock mutex ;
        Lwt.return_ok { TCP.flow; TCP.no_close = false }
    | exception Queue.Empty ->
        if t.closed
        then (
          Lwt_mutex.unlock mutex ;
          Lwt.return_error `Closed)
        else (
          Lwt_mutex.unlock mutex ;
          accept t)

  let close ({ condition; _ } as t) =
    t.closed <- true ;
    (* Stack.disconnect stack >>= fun () -> *)
    Lwt_condition.signal condition () ;
    Lwt.return_unit

  let http_service ?config ~error_handler request_handler =
    let module R = (val Mimic.repr tcp_protocol) in
    let connection flow =
      let dst = TCP.dst flow in
      let error_handler = error_handler dst in
      let request_handler' reqd = request_handler flow dst reqd in
      let conn =
        H1.Server_connection.create ?config ~error_handler request_handler'
      in
      Lwt.return_ok (R.T flow, Paf.Runtime ((module H1.Server_connection), conn))
    in
    Paf.service connection Lwt.return_ok accept close

  let https_service ~tls ?config ~error_handler request_handler =
    let module R = (val Mimic.repr tls_protocol) in
    let handshake tcp_flow =
      let dst = TCP.dst tcp_flow in
      TLS.server_of_flow tls tcp_flow >>= function
      | Ok flow -> Lwt.return_ok (dst, flow)
      | Error `Closed ->
          (* XXX(dinosaure): be care! [`Closed] at this stage does not mean
           * that the bound socket is closed but the socket with the peer is
           * closed. *)
          Log.err (fun m -> m "The connection was closed by peer.") ;
          TCP.close tcp_flow >>= fun () -> Lwt.return_error `Closed
      | Error err ->
          Log.err (fun m -> m "Got a TLS error: %a." TLS.pp_write_error err) ;
          TCP.close tcp_flow >>= fun () -> Lwt.return_error err in
    let connection (dst, flow) =
      let error_handler = error_handler dst in
      let request_handler' reqd = request_handler flow dst reqd in
      let conn =
        H1.Server_connection.create ?config ~error_handler request_handler'
      in
      Lwt.return_ok (R.T flow, Paf.Runtime ((module H1.Server_connection), conn))
    in
    Paf.service connection handshake accept close

  let alpn =
    let module R = (val Mimic.repr tls_protocol) in
    let alpn_of_tls_connection (_edn, flow) =
      match TLS.epoch flow with
      | Ok { Tls.Core.alpn_protocol; _ } -> alpn_protocol
      | Error _ -> None in
    let peer_of_tls_connection (edn, _flow) = edn in
    (* XXX(dinosaure): [TLS]/[ocaml-tls] should let us to project the underlying
     * [flow] and apply [TCP.dst] on it.
     * Actually, we did it with the [TLS] module. *)
    let injection (_edn, flow) = R.T flow in
    {
      Alpn.alpn = alpn_of_tls_connection;
      Alpn.peer = peer_of_tls_connection;
      Alpn.injection;
    }

  let alpn_service ~tls ?config:(_ = (H1.Config.default, H2.Config.default))
      handler =
    let handshake tcp_flow =
      let dst = TCP.dst tcp_flow in
      TLS.server_of_flow tls tcp_flow >>= function
      | Ok flow -> Lwt.return_ok (dst, flow)
      | Error `Closed ->
          (* XXX(dinosaure): be care! [`Closed] at this stage does not mean
           * that the bound socket is closed but the socket with the peer is
           * closed. *)
          Log.err (fun m -> m "The connection was closed by peer.") ;
          Lwt.return_error (`Write `Closed)
      | Error err ->
          Log.err (fun m -> m "Got a TLS error: %a." TLS.pp_write_error err) ;
          TCP.close tcp_flow >>= fun () ->
          Lwt.return_error (err :> [ TLS.write_error | `Msg of string ]) in
    let module R = (val Mimic.repr tls_protocol) in
    let request flow edn reqd protocol =
      match flow with
      | R.T flow -> handler.Alpn.request flow edn reqd protocol
      | _ -> assert false
      (* XXX(dinosaure): this case should never occur. Indeed, the [injection]
         given to [Alpn.service] only create a [tls_protocol] flow. We just
         destruct it and give it to [request_handler]. *)
    in
    Alpn.service alpn { handler with request } handshake accept close

  let serve ?stop service t = Paf.serve ?stop service t
end

type transmission = [ `Clear | `TLS of string option ]

let paf_transmission : transmission Mimic.value =
  Mimic.make ~name:"paf-transmission"

let paf_endpoint : (Ipaddr.t * int) Mimic.value =
  Mimic.make ~name:"paf-endpoint"

open Lwt.Infix

let rec kind_of_flow : Mimic.edn list -> transmission option = function
  | Mimic.Edn (k, v) :: r -> (
      match Mimic.equal k paf_transmission with
      | Some Mimic.Refl -> Some v
      | None -> kind_of_flow r)
  | [] -> None

let rec endpoint_of_flow : Mimic.edn list -> (Ipaddr.t * int) option = function
  | Mimic.Edn (k, v) :: r -> (
      match Mimic.equal k paf_endpoint with
      | Some Mimic.Refl -> Some v
      | None -> endpoint_of_flow r)
  | [] -> None

let ( >>? ) = Lwt_result.bind

let run ~ctx handler request =
  Mimic.unfold ctx >>? fun ress ->
  Mimic.connect ress >>= fun res ->
  match (res, kind_of_flow ress) with
  | (Error _ as err), _ -> Lwt.return err
  | Ok flow, (Some `Clear | None) ->
      let edn = endpoint_of_flow ress in
      let alpn = match request with `V1 _ -> "http/1.1" | `V2 _ -> "h2c" in
      Alpn.run ~alpn handler edn request flow
  | Ok flow, Some (`TLS alpn) ->
      let edn = endpoint_of_flow ress in
      Alpn.run ?alpn handler edn request flow
