open Common



type kbuild_info = directory list
   and directory = Directory of string (*dirname*) * group list
   and group = Group of filename list

let directories_to_assoc xs =
  xs +> List.map (function (Directory (s, ys)) -> s, ys)
let directories_to_hash xs =
  xs +> directories_to_assoc +> Common.hash_of_list
let files_of_groups xs =
  xs +> List.map (function Group ys -> ys) +> Common.union_all



let adjust_dirs dirs =
  dirs +> Common.map_filter (fun s ->
    match s with
    | s when s =~ "^\\.$" -> None
    | s when s =~ "^\\./\\.git" -> None
    | s when s =~ "^\\./\\.tmp_versions" -> None
    | s when s =~ "^\\./include/config/" -> None
    | s when s =~ "^\\./usr/include" -> None
    | s when s =~ "^\\./\\(.*\\)" -> Some (matched1 s)
    | s -> Some s
  )



let unparse_kbuild_info xs filename =
  Common.with_open_outfile filename (fun (pr_no_nl,chan) ->
    let pr s = pr_no_nl (s ^ "\n") in

    xs +> List.iter (function Directory (s, ys) ->
      pr s;
      ys +> List.iter (function Group zs ->
        pr ("  " ^ (String.concat " " zs));
      );
      pr "";
    )
  )

let parse_kbuild_info filename =
  let xs = cat filename in
  let xs = xs +> List.map (Str.global_replace (Str.regexp "#.*") "" ) in
  let xs = xs +> List.filter (fun s -> not (s =~ "^[ \t]*$")) in

  (* split by header of section *)
  let xs = xs +> Common.split_list_regexp "^[^ ]" in

  xs +> List.map (fun (s, xs) ->
    let groups = xs +> List.map (fun s ->
      assert (s =~ "^[ ]+\\(.*\\)");
      let files = matched1 s in
      let cfiles = Common.split " +" files in
      Group cfiles
    ) in

    Directory (s, groups)
  )

let generate_naive_kbuild_info dirs =
    dirs  +> List.map (fun s ->
      let files = Common.readdir_to_file_list s in
      let files_ext = files +> List.map Common.dbe_of_filename_safe in
      let cfiles = files_ext +> Common.map_filter
        (function
        | Left (d,base, "c") ->
            if base =~ ".*\\.mod$" then None
            else Some base
        | _ -> None
        ) in
      let ys = cfiles +> List.map (fun c -> Group [c ^ ".c"]) in
      Directory (s, ys)
    )




let generate_kbuild_info_from_depcocci dirs outfile =
  Common.with_open_outfile outfile (fun (pr_no_nl, chan) ->
    dirs  +> List.iter (fun s ->
      pr_no_nl (s ^ "\n");
      let depcocci = Common.cat (Filename.concat s "depcocci.dep") in
      depcocci +> List.iter (fun s -> pr_no_nl (s ^ "\n"));
      pr_no_nl "\n";
    )
  )
(*
    dirs  +> List.map (fun s ->
      let groups = depcocci +> List.map (fun s -> Group (Common.split " +" s))
      in
      Directory (s, groups)
    )
*)


type makefile =
  {
    obj_dirs : string stack ref;
    obj_config: (string list) stack ref;
    obj_objs: (string * (string list)) stack ref;
  }
let empty_makefile () =
 failwith "empty_makefile"

let parse_makefile file =
  let xs = Common.cat file in
  let s = Common.unlines xs in
  let s = Str.global_replace (Str.regexp "\\\\\n") "" s in
  let xs = Common.lines_with_nl s in
  let xs = xs +> List.map (Str.global_replace (Str.regexp "#.*") "" ) in
  let xs = xs +> List.filter (fun s -> not (s =~ "^[ \t]*$")) in
  let _m = empty_makefile () in

  xs +> List.iter (fun s ->
    match s with
    | s when s =~ "obj-\\$(CONFIG_.*)[ \t]*[\\+:]=\\(.*/\\)" ->
        pr2_no_nl ("DIR: " ^ s)
    | s when s =~ "obj-y[ \t]*\\+=\\(.*/\\)" ->
        pr2_no_nl ("DIR: " ^ s)
    | s when s =~ "obj-\\$(CONFIG_.*)[ \t]*[\\+:]=\\(.*\\)" ->
        let s = matched1 s in
        let objs = Common.split "[ \t]+" s in
        assert(List.for_all (fun s -> thd3 (Common.dbe_of_filename s) = "o")
                  objs);

        pr2 ("OBJS: " ^ (String.concat "|" objs))

    | s when s =~ "[a-zA-Z0-9_]+-objs[ \t]*[\\+:]=\\(.*\\)" ->
        let s = matched1 s in
        let objs = Common.split "[ \t]+" s in

        pr2 ("OBJSMODULE: " ^ (String.concat "|" objs))

    | s  ->
        pr2_no_nl ("OTHER: " ^ s)

  )


let generate_less_naive_kbuild_info dirs =
    dirs  +> List.map (fun s ->
      let files = Common.readdir_to_file_list s in
      let files_ext = files +> List.map Common.dbe_of_filename_safe in
      let cfiles = files_ext +> Common.map_filter
        (function
        | Left (d,base, "c") ->
            if base =~ ".*\\.mod$" then None
            else Some base
        | _ -> None
        ) in
      match cfiles with
      | [] -> Directory (s, [])
      | _::_ ->
          if Common.lfile_exists (Filename.concat s "Makefile")
          then
            let _res = parse_makefile (Filename.concat s "Makefile") in
            let ys = cfiles +> List.map (fun c -> Group [c ^ ".c"]) in
            Directory (s, ys)
          else
            failwith ("no Makefile found in: " ^ s)

    )



(* a = current info file, in general manually extended; b = generated one *)
let check_up_to_date a b  =
  let das = directories_to_assoc a in
  let dbs = directories_to_assoc b in
  let all_dirs = (das +> List.map fst) $+$ (dbs +> List.map fst) in
  all_dirs +> List.iter (fun dir ->
    match
      optionise (fun () -> List.assoc dir das),
      optionise (fun () -> List.assoc dir dbs)
    with
    | None, None -> raise (Impossible 57)
    | None, Some gbs -> pr2 ("new directory appeared:" ^ dir)
    | Some gas, None -> pr2 ("old directory disappeared:" ^ dir)
    | Some gas, Some gbs ->
        let afiles = files_of_groups gas in
        let bfiles = files_of_groups gbs in
        let all_files = afiles $+$ bfiles in
        all_files +> List.iter (fun file ->
          match List.mem file afiles, List.mem file bfiles with
          | false, false -> raise (Impossible 58)
          | false, true -> pr2 ("new file appeared:" ^ file ^ " in " ^ dir)
          | true, false -> pr2 ("old file disappeared:" ^ file  ^ " in " ^ dir)
          | true, true -> ()
        )
  )


let files_in_dirs dirs kbuild_info =
  dirs +> List.map (fun dir ->
    let dir = Common.chop_dirsymbol dir in
    (* could use assoc, but we accept "parasite" prefix *)
    let gooddirs =
      kbuild_info +> Common.map_filter (function (Directory (s, groups)) ->
        if dir =~ ("\\(.*\\)" ^ s ^ "$")
        then
          let prefix = matched1 dir in
          Some (prefix, s, groups)
        else None
      )
    in

    (match gooddirs with
    | [prefix, dir, groups] ->
        groups +> List.map (function (Group xs) ->
          Group (xs +> List.map (fun s ->
            Filename.concat (prefix ^ dir) s))
        )

    | [] ->
        pr2 ("can't find kbuild info for directory :" ^ dir);
        []
    | x::y::ys ->
        pr2 ("too much kbuild info candidate for directory :" ^ dir);
        []
    )
  ) +> List.concat
