-module(erlxrd).

-export([main/1]).


main( [Progname] ) ->
	main( [Progname, '.'] );
main( [Progname, Dir] ) ->
	Directory = erlang:atom_to_list( Dir ),
	% xref:m() needs Directory in code path
	code:add_patha(Directory),
	% use {xref_mode, modules} since we have no debug info in beam files
	{ok, _Pid} = xref:start( Progname, [{xref_mode, modules}] ),
	Modules = add_modules( Progname, Directory ),
	Undefined_functions = lists:foldl( fun undefined_functions/2, [], Modules ),
	io:format( "~p~n", [Undefined_functions] ),
	xref:stop( Progname ),
	init:stop().


% return list() of modules, in directory, that have been
% added to the Xref server.
add_modules( Xref, Directory ) ->
	case xref:add_directory( Xref, Directory ) of
	{ok, Modules} ->
		Modules;
	_Error ->
		add_modules_one_by_one( Xref, Directory )
	end.

add_modules_one_by_one( Xref, Directory ) ->
	{ok, Files} = file:list_dir( Directory ),
	Beam_files = lists:filter( fun is_beam_file/1, Files ),
	Fun = fun( File, Acc ) ->
			case xref:add_module( Xref, lists:append( [Directory, "/", File] ) ) of
			{ok, Module} ->
				[Module|Acc];
			{error, Module_with_error, Reason} ->
				io:format( "Error:~p:~p~n~n", [Module_with_error, Reason] ),
				Acc
			end
		end,
	lists:foldl( Fun, [], Beam_files ).
	
is_beam_file( File ) ->
	case string:tokens( File, "." ) of
	[_Name, "beam"] ->
		true;
	_Else ->
		false
	end.


% return list()
% list is empty if everything is defined
% otherwise list is filled with tuple() {Module : atom(), Undefined : list()}
% ''things'' that are Undefined in Module
undefined_functions(Module, Acc) ->
    case xref:m(Module) of
	{error, Module_with_error, Reason} ->
	    io:format("Error:~p:~p~n~n",
		      [Module_with_error, Reason]),
	    Acc;
	Result ->
	    Fun = fun ({undefined, []}, A) -> A;
		      ({undefined, Undefined}, A) ->
			  [{Module, Undefined} | A];
		      ({deprecated, []}, A) -> A;
		      ({deprecated, Deprecated}, A) ->
			  io:format("Deprecated:~p:~p~n~n", [Module, Deprecated]),
			  A
		  end,
	    lists:foldl(Fun, Acc, Result)
    end.


% return tuple(), with caller module and undefined calls.
% filter the {module, func, arity} that are undefined, on module.
% only keep the ones where the first 3 letters of caller,
% are the same as  the first 3 letters of module (the same block).
keep_block_calls( {Caller, Undefined_calls}, Acc ) ->
	Block = string:substr(erlang:atom_to_list( Caller ), 1, 3),
	Fun = fun ( {Module, _Function, _Arity} ) ->
			case string:substr(erlang:atom_to_list( Module ), 1, 3) of 
			Block ->
				true;
			_Else ->
				false
			end
		end,
	case lists:filter( Fun, Undefined_calls ) of
	[] ->
		Acc;
	Still_Undefined ->
		[{Caller, Still_Undefined}|Acc]
	end.
