%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1997-2012. All Rights Reserved.
%% 
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% %CopyrightEnd%
%%
%%
-module(ic).


-export([sgen/1, gen/1, gen/2, help/0, compile/3]).


%%------------------------------------------------------------
%%
%% Internal stuff
%%
%%------------------------------------------------------------

-export([filter_params/2, handle_preproc/4, do_gen/4]).

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


-include("icforms.hrl").
-include("ic.hrl").

-include_lib("stdlib/include/erl_compile.hrl").

-export([make_erl_options/1]).			% For erlc

-export([main/3, do_scan/1, do_parse/2, do_type/2]).


%%------------------------------------------------------------
%%
%% Entry point
%%
%%------------------------------------------------------------

%% compile(AbsFileName, Outfile, Options)
%%   Compile entry point for erl_compile.

compile(File, _OutFile, Options) ->
    case gen(File, make_erl_options(Options)) of
	ok -> ok;
	Other -> Other
    end.


%% Entry for the -s switch
sgen(ArgList) ->
%%%    io:format("sgen called w ~p~n", [ArgList]),
    apply(?MODULE, gen, ArgList).


gen(File) ->
    gen(File, []).

gen(File, Opts) ->
    G = ic_genobj:new(Opts),
    IdlFile = ic_file:add_dot_idl(File),
    case ic_options:get_opt(G, show_opts) of
	true ->
	    io:format("Opts: ~p~n", [ic_options:which_opts(G)]);
	_ -> ok
    end,
    ic_genobj:set_idlfile(G, IdlFile),
    case catch gen2(G, File, Opts) of
	{_, {'EXIT', R}} -> 
	    ic_genobj:free_table_space(G), %% Free space for all ETS tables
	    io:format("Fatal error : ~p~n",[R]),
	    error;
	{_, {'EXIT', _, R}} -> 
	    ic_genobj:free_table_space(G), %% Free space for all ETS tables
	    io:format("Fatal error : ~p~n",[R]),
	    error;
	{'EXIT', R} -> 
	    ic_genobj:free_table_space(G), %% Free space for all ETS tables
	    io:format("Fatal error : ~p~n",[R]),
	    error;
	{'EXIT', _, R} -> 
	    ic_genobj:free_table_space(G), %% Free space for all ETS tables
	    io:format("Fatal error : ~p~n",[R]),
	    error;	
	%% In this case, the pragma registration 
        %% found errors so this should return error.
	error ->
	    ic_genobj:free_table_space(G), %% Free space for all ETS tables
	    error;
	_ -> 
	    X = ic_error:return(G),
	    ic_genobj:free_table_space(G), %% Free space for all ETS tables
	    X
    end.


gen2(G, File, Opts) ->
    case ic_options:get_opt(G, time) of
	true -> 
	    time("TOTAL                ", ic, main, [G, File, Opts]);
	_ -> 
	    case main(G, File, Opts) of
		error ->
		    error;
		_ ->
		    ok
	    end
    end.



do_gen(erl_corba, G, File, T) ->
    ic_erlbe:do_gen(G, File, T);
do_gen(erl_template, G, File, T) ->
    ic_erl_template:do_gen(G, File, T);
do_gen(erl_genserv, G, File, T) ->
    ic_erlbe:do_gen(G, File, T);
do_gen(c_genserv, G, File, T) ->
    ic_cclient:do_gen(G, File, T);
do_gen(noc, G, File, T) ->
    ic_noc:do_gen(G, File, T);
do_gen(erl_plain, G, File, T) ->
    ic_plainbe:do_gen(G, File, T);
do_gen(c_server, G, File, T) ->
    ic_cserver:do_gen(G, File, T);
do_gen(c_client, G, File, T) ->
    ic_cclient:do_gen(G, File, T);
%% Java backend
do_gen(java, G, File, T) ->
    ic_jbe:do_gen(G, File, T);
%% No language choice
do_gen(_,_,_,_) -> 
    ok.

do_scan(G) ->
    icscan:scan(G, ic_genobj:idlfile(G)).
    

do_parse(G, Tokens) ->
    case icparse:parse(Tokens) of
	{ok, L} -> L;
	X when element(1, X) == error -> 
	    Err = element(2, X),
	    ic_error:fatal_error(G, {parse_error, element(1, Err), 
				  element(3, Err)});
	X -> exit(X)
    end.


do_type(G, Form) ->
    ictype:type_check(G, Form).
	
time(STR,M,F,A) ->
    case timer:tc(M, F, A) of
	{_, {'EXIT', R}} -> exit(R);
	{_, {'EXIT', _, R}} -> exit(R);
	{_, _X} when element(1, _X)==error -> throw(_X);
	{_T, _R} -> 
	    io:format("Time for ~s:  ~10.2f~n", [STR, _T/1000000]),
	    _R
    end.



%% Filters parameters so that only those with certain attributes are
%% seen. The filter parameter is a list of attributes that will be
%% seen, ex. [in] or [inout, out]
filter_params(Filter, Params) ->
    lists:filter(fun(P) ->
		    lists:member(get_param_attr(P#param.inout), Filter) end,
		 Params).


%% Access primitive to get the attribute name (and discard the line
%% number).
get_param_attr({A, _N}) -> A.


%%
%% Fixing the preproc directives
%%
handle_preproc(G, _N, line_nr, X) ->
    Id = ic_forms:get_id2(X),
    Flags = X#preproc.aux,
    case Flags of
	[] -> ic_genobj:push_file(G, Id);
	_ ->
	    foldr(fun({_, _, "1"}, Gprim) -> ic_genobj:push_file(Gprim, Id);
		     ({_, _, "2"}, Gprim) -> ic_genobj:pop_file(Gprim, Id);
		     ({_, _, "3"}, Gprim) -> ic_genobj:sys_file(Gprim, Id) end,
		  G, Flags)
    end;
handle_preproc(G, _N, _Other, _X) ->
    G.



%%------------------------------------------------------------
%%
%% The help department
%%
%% 
%%
%%------------------------------------------------------------

help() ->
    io:format("No help available at the moment~n", []),
    ok.

print_version_str(G) ->
    case {ic_options:get_opt(G, silent), ic_options:get_opt(G, silent2)} of
	{true, _} -> ok;
	{_, true} -> ok;
	_ -> 
	    io:format("Erlang IDL compiler version ~s~n", [?COMPILERVSN])
    end.



%%
%% Converts generic compiler options to specific options.
%% 
%% Used by erlc
%%

make_erl_options(Opts) ->

    %% This way of extracting will work even if the record passed
    %% has more fields than known during compilation.

    Includes1 = Opts#options.includes,
    Defines = Opts#options.defines,
    Outdir = Opts#options.outdir,
    Warning = Opts#options.warning,
    Verbose = Opts#options.verbose,
    Specific = Opts#options.specific,
    Optimize = Opts#options.optimize,
    PreProc = 
	lists:flatten(
	  lists:map(fun(D) -> io_lib:format("-I\"~s\" ", [ic_util:to_list(D)]) end,
		    Includes1)++
	  lists:map(
	    fun ({Name, Value}) ->
		    io_lib:format("-D~s=~s ", [ic_util:to_list(Name), ic_util:to_list(Value)]);
		(Name) ->
		    io_lib:format("-D~s ", [ic_util:to_list(Name)])
	    end,
	    Defines)),
    Options =
	case Verbose of
	    true ->  [];
	    false -> []
	end ++
	case Warning of
	    0 -> [nowarn];
	    _ -> ['Wall']
	end ++
	case Optimize of
	    0 -> [];
	    _ -> []
	end,
    
    Options++[{outdir, Outdir}, {preproc_flags, PreProc}]++Specific.


%%%
%%% NEW main, avoids memory fragmentation
%%%
main(G, File, _Opts) ->
    print_version_str(G),
    ?ifopt(G, time, io:format("File ~p compilation started  :   ~p/~p/~p ~p:~2.2.0p~n", 
			      [ic_genobj:idlfile(G),
			       element(1,date()),
			       element(2, date()),
			       element(3, date()),
			       element(1, time()),
			       element(2, time())])),

    case ic_options:get_opt(G, help) of
	true -> help();

	_ ->
	    scanning(G, File)
    end.



scanning(G, File) ->
    S = ?ifopt2(G, time,
	       time("input file scanning  ", ic, do_scan, [G]),
	       ic:do_scan(G)),
    ?ifopt2(G, tokens, io:format("TOKENS: ~p~n", [S]), 
				 parsing(G, File, S)).

parsing(G, File, S) ->
    T = ?ifopt2(G, 
		time, 
		time("input file parsing   ", ic, do_parse, [G,S]),
		ic:do_parse(G,S)),
    ?ifopt2(G, form, io:format("PARSE FORM: ~p~n", [T]), 
	     pragma(G, File, T)).



pragma(G, File, T) ->
    case ?ifopt2(G, 
		 time,
		 time("pragma registration  ", ic_pragma, pragma_reg, [G,T]),
		 ic_pragma:pragma_reg(G,T)) of
	%% All pragmas were successfully applied
	{ok,Clean} ->
	    typing(G, File, Clean);
       
	error ->
	    error
    end.


typing(G, File, Clean) ->
    case catch ?ifopt2(G, 
		       time,
		       time("type code appliance  ", ic, do_type, [G,Clean]),
		       ic:do_type(G,Clean)) of
	{'EXIT',Reason} ->
	    io:format("Error under type appliance : ~p~n",[Reason]),
	    error;
       
	T2 ->	    
	    ?ifopt2(G, tform, io:format("TYPE FORM: ~p~n", [T2]),
		    generation(G, File, T2))
    end.



generation(G, File, T2) ->
    case ic_options:get_opt(G, multiple_be) of
	false ->
	    single_generation(G, File, T2);
	List ->
	    OutDir = 
		case ic_options:get_opt(G, outdir) of
		    false -> 
			[];
		    Dir ->
			Dir
		end,
	    
	    case ic_options:get_opt(G, be) of
		false ->
		    ok;
		Be ->
		    %% Generate this first
		    ic_options:add_opt(G,[{outdir,OutDir++atom_to_list(Be)}],true),
		    single_generation(G, File, T2)
	    end,
	    multiple_generation(G, File, T2, OutDir, List)
    end.

multiple_generation(_G, _File, _T2, _RootDir, []) ->
    ok;
multiple_generation(G, File, T2, RootDir, [Be|Bes]) ->
    ic_options:add_opt(G,[{outdir,RootDir++atom_to_list(Be)}],true),
    ic_options:add_opt(G,[{be,Be}],true),
    single_generation(G, File, T2),

    case ic_error:get_error_count(G) of
	0 ->
	     multiple_generation(G,File,T2,RootDir,Bes);
	 _ ->
	     %% Errors reported, abort
	     ok
     end.


single_generation(G, File, T2) ->
    case ic_error:get_error_count(G) of
	0 ->
	    %% Check if user has sett backend option
	    case ic_options:get_opt(G, be) of
		false ->
		    %% Use default backend option
		    DefaultBe = ic_options:defaultBe(),
		    ic_options:add_opt(G,[{be,DefaultBe}],true),
		    
		    ?ifopt2(G, 
			    time,
			    time("code generation      ", ic, do_gen, [DefaultBe, G, File, T2]),
			    ic:do_gen(DefaultBe, G, File, T2));
		Be ->
		    %% Use user defined backend		    
		    ?ifopt2(G, 
			    time,
			    time("code generation      ", ic, do_gen, [Be, G, File, T2]),
			    ic:do_gen(Be, G, File, T2))
	    end;
	_ ->
	    ok	    %% Does not matter
    end.
	


