open Cmdliner
open Driver

type verb = Verbose | Quiet

let input_file_arg =
  let doc = "The input file to be compiled" in
  let docv = "INPUT_FILE" in
  Arg.(required & pos 0 (some string) None & info [] ~docv ~doc)

let output_file_arg =
  let doc =
    "The output file for the compiled LLVM IR (default: input file after \
     removing the extension)"
  in
  let docv = "OUTPUT_FILE" in
  Arg.(value & opt (some string) None & info [ "o"; "output-file" ] ~docv ~doc)

let verb_t =
  let quiet =
    let doc = "Don't print the steps in the compilation pipeline." in
    (Quiet, Arg.info [ "q"; "quiet"; "silent" ] ~doc)
  in
  let verbose =
    let doc = "Print every step in the compilation pipeline." in
    (Verbose, Arg.info [ "v"; "verbose" ] ~doc)
  in
  Arg.(last & vflag_all [ Quiet ] [ quiet; verbose ])

let show_evidence_t =
  let doc = "Show evidence for every term when printing the AST." in
  let docv = "SHOW_EVIDENCE" in
  Arg.(value & flag & info [ "show-evidence" ] ~docv ~doc)

let typechecking_t =
  let options =
    [
      ("static", Config.Static);
      ("dynamic", Config.Dynamic);
      ("gradual_guarded", Config.GradualGuarded);
      ("gradual_mono_values", Config.GradualMonoValues);
      ("gradual_mono_closures", Config.GradualMonoClosures);
      ("gradual_guarded_dfo", Config.GradualGuardedDFO);
      ("gradual_mono_dfo", Config.GradualMonoDFO);
      ("gradual_succinct", Config.GradualSuccinct);
      ("nocheck", Config.Nocheck);
    ]
  in
  let doc =
    Format.sprintf "The typechecking semantics to be used, options are %s"
      (Arg.doc_alts_enum options)
  in
  let docv = "TYPECHECKING" in
  let default = Config.GradualGuarded in
  Arg.(
    value & opt (enum options) default & info [ "t"; "typechecking" ] ~docv ~doc)

let assemble_t =
  let doc = "Only run the compilatioon pipeline up to the assembly stage." in
  let docv = "ASSEMBLE" in
  Arg.(value & flag & info [ "S"; "assemble" ] ~docv ~doc)

let stdlib_dir_t =
  let doc = "The directory where the stdlib is located (default: ./lib)" in
  let docv = "STDLIB_DIR" in
  let default = Filename.concat Filename.current_dir_name "lib" in
  let env =
    let doc = "Overrides the default message to print." in
    Cmd.Env.info "GREV_LIB_DIR" ~doc
  in
  Arg.(value & opt string default & info [ "L"; "stdlib-dir" ] ~env ~docv ~doc)

let cflags_t =
  let doc =
    "Additional options to be passed to the C compiler. Can be used more than \
     once to pass multiple flags"
  in
  let docv = "CFLAGS" in
  Arg.(value & opt_all string [] & info [ "cflags" ] ~docv ~doc)

let target_t =
  let doc = "Target to be passed to Clang and LLVM" in
  let docv = "TARGET" in
  Arg.(value & opt (some string) None & info [ "target" ] ~docv ~doc)

let compile input_file output_file_opt typechecking assemble stdlib_dir cflags
    target verb show_evidence =
  let input_stripped = Filename.remove_extension input_file in
  let ir_file =
    match output_file_opt with
    | Some f -> if assemble then f else Filename.remove_extension f ^ ".ll"
    | None -> input_stripped ^ ".ll"
  in
  let verbose = match verb with Verbose -> true | Quiet -> false in
  let config =
    Config.
      {
        input = InputFile input_file;
        output = OutputFile ir_file;
        typechecking;
        verbose;
        target;
        show_evidence;
      }
  in
  (try Compile.compile config
   with Common.Error.Error e ->
     Format.eprintf "%a\n%!" Common.Error.pp_loc e;
     Printexc.print_backtrace stderr;
     exit 1);
  if not assemble then
    let executable_file =
      match output_file_opt with Some f -> f | None -> input_stripped
    in
    let cflags = cflags @ Config.cflags config in
    let runtime_file = Config.runtime_file config in
    Link.link_file ir_file executable_file stdlib_dir cflags runtime_file target

let compiler_t =
  Term.(
    const compile
    $ input_file_arg
    $ output_file_arg
    $ typechecking_t
    $ assemble_t
    $ stdlib_dir_t
    $ cflags_t
    $ target_t
    $ verb_t
    $ show_evidence_t)

let compiler_cmd =
  let doc = "A simple compiler" in
  let man =
    [
      `S "DESCRIPTION";
      `P
        "Compiles the program in the input file to LLVM IR and writes the \
         result to the output file.";
      `S "EXAMPLES";
      `P "To compile a file named 'program.txt' and output to 'program.ll':";
      `P "$(b,compiler) program.txt -o program.ll";
    ]
  in
  let info = Cmd.info "grev" ~version:"0.0.1" ~doc ~man in
  Cmd.v info compiler_t

let () = exit (Cmd.eval compiler_cmd)
