module Client = struct
  let open_connection ~(host : Ipaddr.V4.t) ~port =
    let open Lwt.Infix in
    let addr = Unix.ADDR_INET (Ipaddr_unix.V4.to_inet_addr host, port) in
    (* Create socket with Lwt_unix *)
    let sock = Lwt_unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
    (* Set socket options before connecting *)
    Unix.setsockopt (Lwt_unix.unix_file_descr sock) Unix.TCP_NODELAY false;
    Logs.debug (fun m -> m "TCP_NODELAY Disabled");
    (* Connect the socket *)
    Lwt_unix.connect sock addr >>= fun () ->
    (* Create I/O channels from the socket *)
    let ic = Lwt_io.of_fd ~mode:Lwt_io.Input sock in
    let oc = Lwt_io.of_fd ~mode:Lwt_io.Output sock in
    Lwt.return (ic, oc)
  ;;

  let write_bytes (_ic, oc) buf =
    let open Lwt.Infix in
    Lwt_io.write_from_exactly oc buf 0 (Bytes.length buf) >>= fun () ->
    Lwt_io.flush oc
  ;;

  let write socket_flow str =
    write_bytes socket_flow (Bytes.unsafe_of_string str)
  ;;

  let read_bytes (ic, _oc) size =
    let open Lwt.Infix in
    let buf = Bytes.create size in
    Lwt_io.read_into_exactly ic buf 0 size >>= fun () ->
    Lwt.return buf
  ;;

  let read socket_flow size =
    let open Lwt.Infix in
    read_bytes socket_flow size >>= fun bytes ->
    Lwt.return (Bytes.to_string bytes)
  ;;
end
