%%
%%  user_default.erl --
%%
%%     Extends the Erlang shell with Wings utilities.
%%
%%  Copyright (c) 2001-2004 Bjorn Gustavsson
%%
%%  See the file "license.terms" for information on usage and redistribution
%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%
%%     $Id: user_default.erl,v 1.19 2004/11/12 05:29:33 bjorng Exp $
%% 

-module(user_default).

-export([help/0,wh/0,
	 wx/0,wxe/0,wxu/1,wxu/3,wxunref/0,wxundef/0,
	 wldiff/1,
	 lm/0,mm/0]).

-import(lists, [foldl/3]).

help() ->
    shell_default:help(),
    p("** Other good stuff **\n"),
    p("mm()       -- lists modified modules on disk not loaded\n"),
    p("lm()       -- reload all modules that newer on disk\n"),
    p("** Wings commands **\n"),
    p("wh()       -- print help for Wings\n"),
    ok.

wh() ->
    p("** Xref for Wings modules **\n"),
    p("wx()       -- collect xref information\n"),
    p("wxe()      -- add xref information for ESDL\n"),
    p("wxunref()  -- print unused functions\n"),
    p("wxundef()  -- print calls to undefined functions\n"),
    p("wxu(M)     -- print uses of module M\n"),
    p("wxu(M, F, A) -- print uses of M:F/A\n"),
    p("** Language support **\n"),
    p("wldiff(Lang) -- diff language files against English templates\n"),
    ok.

%%%
%%% Xref support.
%%%

wx() ->
    WingsLib = code:lib_dir(wings),
    WingsEbin = filename:join(WingsLib, "ebin"),
    xref:start(s),
    xref:set_default(s, [{verbose,false},{warnings,false},{builtins,true}]),
    xref:set_library_path(s, code:get_path() -- [WingsEbin]),
    {ok,Ms} = xref:add_directory(s, WingsEbin),
    Dirs = get_plugin_dirs(),
    foldl(fun(D, N) -> 
		  {ok,PMs} = xref:add_directory(s, D),
		  N+length(PMs)
	  end, length(Ms), Dirs).

wxe() ->
    Dir = filename:dirname(code:which(gl)),
    {ok,Ms} = xref:add_directory(s, Dir),
    length(Ms).

wxu(Mod) when is_atom(Mod) ->
    result(xref:q(s, make_query("domain(E || ~p) - ~p", [Mod,Mod])));
wxu({M,_,_}=MFA) ->
    result(xref:q(s, make_query("domain(E || ~p) - ~p", [MFA,M]))).

wxu(M, F, A) ->
    MFA = {M,F,A},
    result(xref:q(s, make_query("domain(E || ~p) - ~p", [MFA,M]))).

wxundef() ->
    xref:analyze(s, undefined_function_calls).

wxunref() ->
    {ok,Unref0} = xref:analyze(s, exports_not_used),
    Unref = filter_unref(Unref0),
    io:format("~p\n", [Unref]).

filter_unref([{M,F,A}=MFA|T]) ->
    case filter_unref(M, F, A) of
	true -> [MFA|filter_unref(T)];
	false -> filter_unref(T)
    end;
filter_unref([]) -> [].

filter_unref(user_default, _, _) -> false;
filter_unref(wings_start, start, 0) -> false;
filter_unref(wings_start, start, 1) -> false;
filter_unref(wings_start, start_halt, 0) -> false;
filter_unref(wings_start, start_halt, 1) -> false;
filter_unref(M, F, A) ->
    case atom_to_list(M) of
	"wpc_"++_ ->
	    filter_standard_plugin(F, A);
	"wp9_"++_ ->
	    filter_ui_plugin(F, A);
	"wpf_"++_ ->
	    filter_font_plugin(F, A);
	_ ->
	    true
    end.

filter_standard_plugin(init, 0) -> false;
filter_standard_plugin(command, 2) -> false;
filter_standard_plugin(menu, 2) -> false;
filter_standard_plugin(_, _) -> true.

filter_ui_plugin(init, 1) -> false;
filter_ui_plugin(_, _) -> true.

filter_font_plugin(char, 1) -> false;
filter_font_plugin(desc, 0) -> false;
filter_font_plugin(draw, 1) -> false;
filter_font_plugin(height, 0) -> false;
filter_font_plugin(width, 0) -> false;
filter_font_plugin(height, 1) -> false;
filter_font_plugin(width, 1) -> false;
filter_font_plugin(_, _) -> true.

result({ok,List}) ->
    io:format("~p\n", [List]);
result(Other) -> Other.

make_query(Format, Args) ->
    lists:flatten(io_lib:format(Format, Args)).

%%%
%%% Language support.
%%%

wldiff(Lang) when is_list(Lang) ->
    case filelib:wildcard("*_"++Lang++".lang") of
	[] ->
	    io:format("No ~p language files found.\n", [Lang]),
	    error;
	Files -> wldiff_1(Files)
    end;
wldiff(Lang) when is_atom(Lang) ->
    wldiff(atom_to_list(Lang)).

wldiff_1([F|Fs]) ->
    io:format("Diffing ~p (with English)\n", [F]),
    wings_lang:diff(F),
    wldiff_1(Fs);
wldiff_1([]) -> ok.

%%%
%%% Load or show modified modules. (Thanks to Vladimir Sekissov.)
%%%

lm() ->
    [c:l(M) || M <- mm()].

mm() ->
  modified_modules().

modified_modules() ->
  [M || {M, _} <-  code:all_loaded(), module_modified(M) == true].

module_modified(Module) ->
  case code:is_loaded(Module) of
    {file, preloaded} ->
      false;
    {file, Path} ->
      CompileOpts = proplists:get_value(compile, Module:module_info()),
      CompileTime = proplists:get_value(time, CompileOpts),
      Src = proplists:get_value(source, CompileOpts),
      module_modified(Path, CompileTime, Src);
    _ ->
      false
  end.

module_modified(Path, PrevCompileTime, PrevSrc) ->
  case find_module_file(Path) of
    false ->
      false;
    ModPath ->
      {ok, {_, [{_, CB}]}} = beam_lib:chunks(ModPath, ["CInf"]),
      CompileOpts =  binary_to_term(CB),
      CompileTime = proplists:get_value(time, CompileOpts),
      Src = proplists:get_value(source, CompileOpts),
      not (CompileTime == PrevCompileTime) and (Src == PrevSrc)
  end.

find_module_file(Path) ->
  case file:read_file_info(Path) of
    {ok, _} ->
      Path;
    _ ->
      %% may be the path was changed?
      case code:where_is_file(filename:basename(Path)) of
	non_existing ->
	  false;
	NewPath ->
	  NewPath
      end
  end.
    
%%%
%%% Internal functions.
%%%

p(String) ->
    io:put_chars(String).

get_plugin_dirs() ->
    Prefix = filename:join(code:lib_dir(wings), "plugins"),
    Path = code:get_path(),
    get_plugin_dirs(Path, Prefix, []).

get_plugin_dirs([D|Ds], Prefix, Acc) ->
    case lists:prefix(Prefix, D) of
	false -> get_plugin_dirs(Ds, Prefix, Acc);
	true -> get_plugin_dirs(Ds, Prefix, [D|Acc])
    end;
get_plugin_dirs([], _, Acc) -> Acc.
