let connection address port =
  let open Lwt.Syntax in
  (* Setup Http/2 connection *)
  let* addresses =
    Lwt_unix.getaddrinfo address (string_of_int port)
      [ Unix.(AI_FAMILY PF_INET) ]
  in
  let socket = Lwt_unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
  let* () = Lwt_unix.connect socket (List.hd addresses).Unix.ai_addr in
  let error_handler e = raise (Elpe.Error e) in
  H2_lwt_unix.Client.create_connection ~error_handler socket

let run_shell (spec : Elpe.std_derivation) cmd =
  Lwt_main.run
    (let open Lwt.Syntax in
     let port = 50051 in
     let address = "127.0.0.1" in
     let* c = connection address port in
     Elpe.backend_conn := Some c;
     let* b = spec#setup in

     let* bash = (Elpe.ubuntu "bash-static")#build in
     let bash = List.hd bash.destdir in

     let f = Filename.temp_dir "elpe-" "" ^ "/setup" in
     Unix.mkfifo f 0o666;

     let pid =
       Unix.create_process
         (bash ^ "/usr/bin/bash-static")
         (match cmd with
         | None -> [| "bash"; "--init-file"; f; "-i" |]
         | Some cmd -> [| "bash"; "--init-file"; f; "-i"; "-c"; cmd |])
         Unix.stdin Unix.stdout Unix.stderr
     in

     let p = Unix.openfile f [ Unix.O_WRONLY ] 0o644 in
     let _ = Unix.write_substring p b 0 (String.length b) in
     Unix.close p;

     match Unix.waitpid [] pid with
     | _, Unix.WEXITED e -> Lwt.return e
     | _ -> failwith "Unknown")

let run_build (spec : Elpe.derivation) =
  Lwt_main.run
    (let open Lwt.Syntax in
     let port = 50051 in
     let address = "127.0.0.1" in
     let* c = connection address port in
     Elpe.backend_conn := Some c;
     let* b = spec#build in
     print_endline (List.fold_left (fun _ x -> x) "" b.destdir);
     Lwt.return ())

let compile input_files =
  (* Compile *)
  let args =
    Array.of_list
      ([
         "ocamlfind";
         (if Dynlink.is_native then "ocamlopt" else "ocamlc");
         "-package";
         "elpe";
         "-c";
       ]
      @ input_files)
  in
  let pid =
    let rd, wr = Unix.pipe () in
    let pid = Unix.create_process "ocamlfind" args rd Unix.stdout Unix.stderr in
    Unix.close wr;
    pid
  in
  let _ =
    match Unix.waitpid [] pid with
    | _, Unix.WEXITED 0 -> ()
    | _, Unix.WEXITED e -> Unix._exit e
    | _ -> print_endline "other"
  in

  (* Link *)
  Dynlink.set_allowed_units [ "Elpe" ];
  let obj =
    Dynlink.adapt_filename
      (Filename.remove_extension (Array.get args (Array.length args - 1))
      ^ ".cmo")
  in
  Dynlink.loadfile obj

let shell =
  Core.Command.basic ~summary:""
    (let%map_open.Command cmd =
       flag "-c" (optional string) ~doc:"Command to run in this shell"
     and files = anon (non_empty_sequence_as_list ("file" %: string)) in
     fun () ->
       Elpe.last_built_module := None;
       compile files;
       match !Elpe.last_built_module with
       | None -> Unix._exit 0
       | Some last -> Unix._exit (run_shell last cmd))

let build =
  Core.Command.basic ~summary:""
    (let%map_open.Command files = anon (sequence ("file" %: string)) in
     fun () ->
       Elpe.last_built_module := None;
       compile (if files = [] then [ "build.ml" ] else files);
       let _ =
         match !Elpe.last_built_module with
         | None -> ()
         | Some last -> run_build (last :> Elpe.derivation)
       in
       ())

let command =
  Core.Command.group ~summary:"Elpe" [ ("shell", shell); ("build", build) ]

let () = Command_unix.run command
(* if !input_files != [] then *)
(*   let exit = compile_and_run () in *)
(*   Unix._exit exit *)
(* else Printf.eprintf "\n" *)