(* Experimental builtins for accelerating ocamldep postprocessing *)

include Omake_pos.Make (struct let name = "Omake_builtin_ocamldep" end)
open Printf
open Omake_ir

(*
 * \begin{doc}
 * \fun{ocamldep-print-buildable-deps}
 *
 * \begin{verbatim}
 *    $(ocamldep-postproc path, enable_cmx)
 *        path : Dir array
 *        enable_cmx : Bool
 * \end{verbatim}
 *
 * Postprocesses an ocamldep invocation with -modules switch.
 *
 * Accesses the global \verb+EXT_OBJ+ for getting the ".o" suffix.
 * \end{doc}
 *)

let target_is_buildable cache venv pos node =
  try
    Omake_target.target_is_buildable cache venv pos node
  with
    Omake_value_type.RaiseException(_, obj) when Omake_env.venv_instanceof obj Omake_symbol.unbuildable_exception_sym ->
    false

let ocamldep_postproc venv pos loc args =
  let cache = Omake_env.venv_cache venv in

  let search_in_path path name =
    try
      let name1 = String.uncapitalize_ascii name in
      let name2 = String.capitalize_ascii name in
      let names = (* check name with orignal case first *)
        if name1 = name then
          [ name1; name2 ]
        else
          [ name2; name1 ] in
      let node_opt =
        Omake_target.target_is_buildable_in_path_1
          cache venv pos path names in
      match node_opt with
        | Some node ->
            Some(Omake_env.venv_nodename venv node)
        | None ->
            None
    with
      | Omake_value_type.RaiseException(_, obj) when Omake_env.venv_instanceof obj Omake_symbol.unbuildable_exception_sym ->
          None in

  let accumulate_over_path path deps suffix =
    List.rev
      (List.fold_left
         (fun acc dep ->
            match search_in_path path (dep ^ suffix) with
              | Some node -> node :: acc
              | None -> acc
         )
         []
         deps
      ) in

  let pos = string_pos "ocamldep-print-buildable-deps" pos in

  let stdin_var = Omake_env.venv_find_var venv pos loc Omake_var.stdin_var in
  let stdin_prim, stdin_close_flag = 
    Omake_value.in_channel_of_any_value venv pos stdin_var in
  let stdin_fd = Omake_env.venv_find_channel venv pos stdin_prim in

  let stdout_var = Omake_env.venv_find_var venv pos loc Omake_var.stdout_var in
  let stdout_prim, stdout_close_flag = 
    Omake_value.out_channel_of_any_value venv pos stdout_var in
  let stdout_fd = Omake_env.venv_find_channel venv pos stdout_prim in

  let ext_obj_val = Omake_builtin_util.get_sym venv pos loc "EXT_OBJ" in
  let ext_obj = Omake_value.string_of_value venv pos ext_obj_val in

  let output_dep path target deps enable_cmx =
    let cmideps = accumulate_over_path path deps ".cmi" in
    let cmideps_str = 
      String.concat " " (List.map String.escaped cmideps) in
    if Filename.check_suffix target ".mli" then (
      let targetbase = Filename.chop_suffix target ".mli" in
      if cmideps <> [] then
        Lm_channel.output_string
          stdout_fd
          (sprintf
             "%s.cmi: %s\n"
             (String.escaped targetbase)
             cmideps_str
          )
    )
    else
      if Filename.check_suffix target ".ml" then (
        let targetbase = Filename.chop_suffix target ".ml" in
        let targetbase_esc = String.escaped targetbase in
        let cmxdeps =
          if enable_cmx then
            accumulate_over_path path deps ".cmx"
          else
            [] in
        let alldeps = cmideps @ cmxdeps in
        let alldeps_str = 
          String.concat " " (List.map String.escaped alldeps) in
        if cmideps <> [] || cmxdeps <> [] then (
          if cmideps <> [] then
            Lm_channel.output_string
              stdout_fd
              (sprintf
                 "%s.cmo: %s\n"
                 targetbase_esc
                 cmideps_str
              );
          Lm_channel.output_string
            stdout_fd
            (sprintf
               "%s.cmx %s%s: %s\n"
               targetbase_esc
               targetbase_esc
               ext_obj
               alldeps_str
            )
        )
      )
      else
        raise (Omake_value_type.OmakeException (loc_pos loc pos, StringError "illegal filename suffix")) in
    
  match args with
    | [path; enable_cmx] ->
         let path = Omake_value.values_of_value venv pos path in
         let path = Omake_eval.path_of_values venv pos path "." in
         let path = List.flatten (List.map snd path) in
         let path = 
           List.map
             (fun dir -> dir, Omake_env.venv_lookup_target_dir venv dir)
             path in
         let enable_cmx = Omake_value.bool_of_value venv pos enable_cmx in

         let current = ref None in
         let process_current() =
           match !current with
             | None -> ()
             | Some(target, deps) ->
                  current := None;
                  output_dep path target deps enable_cmx in
         ( try
             while true do
               let line = Lm_channel.input_line stdin_fd in
               if Lm_string_util.contains line ':' then (
                 process_current();
                 let k = Lm_string_util.strchr line ':' in
                 let target = String.sub line 0 k in
                 let rest = 
                   String.sub line (k+1) (String.length line - k - 1) in
                 let words =
                   Lm_string_util.tokens "" Lm_string_util.white rest in
                 current := Some(target, words)
               )
               else (
                 match !current with
                   | None -> ()
                   | Some(target,words) ->
                        let more_words =
                          Lm_string_util.tokens "" Lm_string_util.white line in
                        current := Some(target, words @ more_words)
               )
             done;
             assert false
           with
             | End_of_file -> ()
         );
         process_current();

         if stdin_close_flag then
           Omake_env.venv_close_channel venv pos stdin_prim;
         if stdout_close_flag then
           Omake_env.venv_close_channel venv pos stdout_prim;
         Omake_value_type.ValNone

    | _ ->
         raise (Omake_value_type.OmakeException (loc_pos loc pos, ArityMismatch (ArityExact 2, List.length args)))



(* register *)

let () =
  let builtin_funs =
    [true, "ocamldep-postproc", ocamldep_postproc, ArityExact 2;
    ] in
  let builtin_info =
    { Omake_builtin_type.builtin_empty with
      builtin_funs = builtin_funs;
    }
  in
  Omake_builtin.register_builtin builtin_info
