#!/usr/bin/env escript
%% -*- erlang -*-
-mode(compile).

%% We expect the list of Erlang source and header files to arrive on
%% stdin, with the entries colon-separated.
main([TargetFile, EbinDir]) ->
    ErlsAndHrls = [ string:strip(S,left) ||
                      S <- string:tokens(io:get_line(""), ":\n")],
    ErlFiles = [F || F <- ErlsAndHrls, lists:suffix(".erl", F)],
    Modules = sets:from_list(
                [list_to_atom(filename:basename(FileName, ".erl")) ||
                    FileName <- ErlFiles]),
    HrlFiles = [F || F <- ErlsAndHrls, lists:suffix(".hrl", F)],
    IncludeDirs = lists:usort([filename:dirname(Path) || Path <- HrlFiles]),
    Headers = sets:from_list(HrlFiles),
    Deps = lists:foldl(
             fun (Path, Deps1) ->
                     dict:store(Path, detect_deps(IncludeDirs, EbinDir,
                                                  Modules, Headers, Path),
                                Deps1)
             end, dict:new(), ErlFiles),
    {ok, Hdl} = file:open(TargetFile, [write, delayed_write]),
    dict:fold(
      fun (_Path, [], ok) ->
              ok;
          (Path, Dep, ok) ->
              Module = filename:basename(Path, ".erl"),
              ok = file:write(Hdl, [EbinDir, "/", Module, ".beam: ",
                                   Path]),
              ok = sets:fold(fun (E, ok) -> file:write(Hdl, [" ", E]) end,
                             ok, Dep),
              file:write(Hdl, ["\n"])
      end, ok, Deps),
    ok = file:write(Hdl, [TargetFile, ": ", escript:script_name(), "\n"]),
    ok = file:sync(Hdl),
    ok = file:close(Hdl).

detect_deps(IncludeDirs, EbinDir, Modules, Headers, Path) ->
    {ok, Forms} = epp:parse_file(Path, IncludeDirs, [{use_specs, true}]),
    lists:foldl(
      fun ({attribute, _LineNumber, Attribute, Behaviour}, Deps)
          when Attribute =:= behaviour orelse Attribute =:= behavior ->
              case sets:is_element(Behaviour, Modules) of
                  true  -> sets:add_element(
                             [EbinDir, "/", atom_to_list(Behaviour), ".beam"],
                             Deps);
                  false -> Deps
              end;
          ({attribute, _LineNumber, file, {FileName, _LineNumber1}}, Deps) ->
              case sets:is_element(FileName, Headers) of
                  true  -> sets:add_element(FileName, Deps);
                  false -> Deps
              end;
          (_Form, Deps) ->
              Deps
      end, sets:new(), Forms).
