%%% This file is part of RefactorErl.
%%%
%%% RefactorErl is free software: you can redistribute it and/or modify
%%% it under the terms of the GNU Lesser General Public License as published
%%% by the Free Software Foundation, either version 3 of the License, or
%%% (at your option) any later version.
%%%
%%% RefactorErl is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%%% GNU Lesser General Public License for more details.
%%%
%%% You should have received a copy of the GNU Lesser General Public License
%%% along with RefactorErl.  If not, see <http://plc.inf.elte.hu/erlang/>.
%%%
%%% The Original Code is RefactorErl.
%%%
%%% The Initial Developer of the Original Code is Eötvös Loránd University.
%%% Portions created  by Eötvös Loránd University and ELTE-Soft Ltd.
%%% are Copyright 2007-2025 Eötvös Loránd University, ELTE-Soft Ltd.
%%% and Ericsson Hungary. All Rights Reserved.


%%% @author Matyas Karacsonyi <k_matyas@inf.elte.hu >

-module(refcore_loadbeam).
-vsn("$Rev: 4099 $ ").

-export([start/3]).

-include("core.hrl").

%% @spec start(FileName, string(), boolean()) -> Result
%%            FileName = atom() | list()
%%            Result   = {ok, node()} | {error, string()}
%% @doc Creates a syntactic tree from a BEAM file, which was compiled using
%% `debug_info' option.
start(FileName, OutputDir, ToSave) ->
    case filelib:is_dir(OutputDir) of
        true ->
            Module = filename:basename(FileName, ".beam"),
            NewName = filename:join(OutputDir, Module++".erl"), %@todo fixme
            case ToSave andalso filelib:is_file(NewName) of
                false ->
                    io:format("Adding: ~p...~n", [FileName]),
                    perform(FileName, NewName, false);
                true -> 
                    error_logger:info_msg("Output file " ++ NewName ++ " already exists"),
                    {error, "Output file already exists"}
            end;
        false -> 
            error_logger:info_msg("Output directory " ++ OutputDir ++ " does not exist"),
            {error, "Output directory does not exist"}
    end.

perform(FileName, _NewName, ToSave) ->
    case beam_lib:chunks(FileName, [abstract_code]) of
	    {ok, {_, [{abstract_code, {_, AbstractCode}}]}} ->
            %?d(AbstractCode),%?d(transform_abs(AbstractCode, FileName)),
            %?d(FileName),
            case ?Graph:path(?Graph:root(), [{file, {path, '==', FileName}} ]) of
                [] -> 
                    add_file(FileName, AbstractCode, erlang:phash2(AbstractCode), ToSave);
                [FileNode] ->
                    Hash = erlang:phash2(AbstractCode),
                    case ?Graph:data(FileNode) of
                        #file{hash=Hash} -> 
                            io:format("Module ~p already added.~n", [FileName]),
                            {ok, FileNode};
                        _ -> 
                            io:format("Module ~p is changed. Updating the database... ~n", [FileName]),
                            ?FileMan:drop_file(FileName),
                            add_file(FileName, AbstractCode, Hash, ToSave)
 %                           error_logger:info_msg("Module " ++ FileName ++ " is already loaded. To update the database please drop the module at first and run add again."),
                            %{ok, FileNode}
                    end
            end;
	    Reason ->
            error_logger:info_msg("Abstract code is not available for " ++ FileName),
            error_logger:info_msg(Reason),
	        {error, "No abstract code"}
    end.

add_file(FileName, AbstractCode, Hash, ToSave) ->
    FileNode = ?Syn:construct(transform_abs(AbstractCode, FileName)),
    ?Graph:update(FileNode, (?Graph:data(FileNode))#file{hash=Hash}),
	ToSave andalso ?FileMan:save_file(FileNode), %% todo save the file with NewName if needed
    ?ESG:finalize(),
    ?FileMan:handle_export_all(FileNode),
    io:format("Added: ~p~n", [FileName]),
	{ok, FileNode}.

% %% @spec is_loaded(string()) -> boolean()
% %% @doc Checks whether the file is already loaded.
% is_loaded(ModuleName) ->
%     LoadedFiles = [{Node, (?Graph:data(Node))#file.path} || Node <- ?Graph:path(?Graph:root(), [file])],
%     ErlNames = [{Node, filename:basename(Path, ".erl")} || {Node, Path} <- LoadedFiles],
%     BeamNames = [{Node, filename:basename(Path, ".beam")} || {Node, Path} <- LoadedFiles],

%     [Node || {Node, FileName} <- ErlNames ++ BeamNames, FileName == ModuleName].

%% @spec transform_abs([AbstractCode], string()) -> AbstractCodeTree
%%   AbstractCodeTree = {atom(), Value} |
%%                      {{atom(), atom()}, Value} |
%%                      {{atom(), atom(), int()}, Value}
%%   Value = atom() | float() | int() | list()
%% @doc Creates a tree description from an Erlang abstract code, which
%% easily can be loaded into RefactorErl by using {@link
%% erl_syntax:construct/1}.

transform_abs(AbsCode, FileName) ->
    transform_abs(AbsCode, FileName, []).

transform_abs([{attribute, Pos, file, {Orig, _}} | AbsCode], FileName, Collect) ->
    {{Pos, {file, FileName, Orig}},
     [Node || Node <- [transform_abs(Node) || Node <- lists:reverse(Collect) ++ AbsCode, 
                                              element(3, Node)/=spec, 
                                              element(3, Node)/=type,
                                              element(3, Node) /= '__info__'], Node /= []]};
transform_abs([Else | Rest], FileName, Collect) ->
    transform_abs(Rest, FileName, [Else | Collect]);
transform_abs([], FileName, Collect) ->
    case beam_lib:chunks(FileName, [compile_info]) of
	    {ok, {_, [{compile_info, Options}]}} -> 
            case lists:keyfind(source, 1, Options) of
                {source, Source} -> Orig = Source;
                _ -> Orig = FileName
            end;
        _ ->
            Orig = FileName
    end,
    transform_abs([ {attribute, {0,0}, file, {Orig, 0}} | lists:reverse(Collect)], FileName, []).


transform_abs({attribute, Pos, module, Name}) ->
    {{Pos, {form, module, Name}}, []};
transform_abs({attribute, Pos, export, List}) ->
    {{Pos, {form, export}}, [{{Pos, funlist}, [{{Pos, funref}, get_type(Name), get_type(Arity)}
                                || {Name, Arity} <- List, Name /= '__info__']}]};
transform_abs({attribute, Pos, import, {Module, List}}) ->
    {{Pos, {form, import}}, [get_type(Module), {{Pos, funlist}, 
					 [{{Pos, funref}, get_type(Name), get_type(Arity)}
					  || {Name, Arity} <- List]}]};
transform_abs({attribute, Pos, file, {Path, Line}}) ->
    {{Pos, {form, file}}, {{Pos, string}, Path}, {{Pos, integer}, Line}};
transform_abs({attribute, Pos, record, {Name, Fields}}) ->
    {{Pos, {form, record, Name}},
     [case F of
	  {record_field, _Pos1, {atom, Pos2, FieldName}} -> %% Pos1 == Pos2 ?
	      {{Pos2, {spec_field, FieldName}}, []};
      {typed_record_field, {record_field, _Pos1, {atom, Pos2, FieldName}}, _Type} -> %% Pos1 == Pos2 ?
        {{Pos2, {spec_field, FieldName}}, []};
      {record_field, _Pos1, {atom, Pos2, FieldName}, DefaultVal} -> %% Pos1 == Pos2 ?
        {{Pos2, {spec_field, FieldName}}, transform_abs(DefaultVal)};
      {typed_record_field, {record_field, _Pos1, {atom, Pos2, FieldName}, DefaultVal}, _Type} -> %% Pos1 == Pos2 ?
	    {{Pos2, {spec_field, FieldName}}, transform_abs(DefaultVal)}
      end || F <- Fields]};

%%% todo: -opaque
transform_abs({attribute, Pos, type, {Name, Params, []}}) ->
    {{Pos, {typedef, Name}}, transform_abs(Params)};
transform_abs({attribute, Pos, spec, {{Name, _Arity}, ParTypes, RetType}}) ->
    {{Pos, {spec, Name}}, [transform_abs(ParT) || ParT <- ParTypes], transform_abs(RetType)};
transform_abs({attribute, Pos, spec, {{Name, _Arity}, [FunSig]}}) ->
    {{Pos, {spec, Name}}, transform_abs(FunSig)};
transform_abs({attribute, Pos, spec, {{Name, _Arity}, Clauses}}) ->
    {{Pos, {spec, Name}}, {spec_union, [transform_abs(Cl) || Cl <- Clauses]}};

transform_abs({attribute, Pos, Value, Attr}) ->
    {{Pos, {attrib, Value}}, get_type(Attr)};


transform_abs({type, Pos, union, Elements}) ->
    Type = fun(T = {type, _, _, _}) -> T;
              ({Type, Pos1, Value }) -> {type_const, Pos1, Type, Value}
           end,
    {{Pos, union}, [transform_abs(E) || E <- lists:map(Type, Elements)]};
%transform_abs({type, _, union, Elements}) ->
%    {union, [transform_abs(E) || E <- Elements]};
transform_abs({type, Pos, 'fun', [{type, _, product, ParTypes}, RetType]}) ->
    {{Pos, spec_clause}, [transform_abs(ParT) || ParT <- ParTypes], transform_abs(RetType)};
transform_abs({type, Pos, Name, []}) ->
    {{Pos, type}, Name};
transform_abs({type_const, Pos, Type, Value}) ->
    {{Pos, type_const}, [Type, Value]};
transform_abs({type, Pos, Name, Params}) ->
    {{Pos, {type, Name}}, [transform_abs(P) || P <- Params]};
transform_abs({paren_type, Pos, Elements}) ->
    {{Pos, paren_type}, [transform_abs(E) || E <- Elements]};

transform_abs({function, Pos, Name, _Ary, Clauses}) ->
    {{Pos, func}, [transform_abs({fun_clause, get_type(Name), C}) || C <- Clauses]};

transform_abs({Kind, Header, {clause, Pos, Pattern, Guard, Body}}) ->
    {{Pos, Kind}, Header, %% Todo: pos for header
	   [transform_abs(P) || P <- Pattern],
	   guard_disj(Guard),
	   [transform_abs(B) || B <- Body]};
transform_abs({'catch', {clause, Pos, Pattern, Guard, Body}}) ->
    {{Pos, pattern}, [tuple_to_infix(Pattern)],
              guard_disj(Guard),
              [transform_abs(B) || B <- Body]};
transform_abs({guard, {clause, Pos, [], Guard, Body}}) ->
    {{Pos, guard}, guard_disj(Guard), [transform_abs(B) || B <- Body]};
transform_abs({Kind, {Name, {clause, Pos, Pattern, Guard, Body}}}) ->
    {{Pos, Kind}, {var, Name}, [transform_abs(P) || P <- Pattern],
	   guard_disj(Guard),
	   [transform_abs(B) || B <- Body]};
transform_abs({Kind, {clause, Pos, Pattern, Guard, Body}}) ->
    {{Pos, Kind}, [transform_abs(P) || P <- Pattern],
	   guard_disj(Guard),
	   [transform_abs(B) || B <- Body]};

transform_abs({record, Pos, Name, Attr}) ->
    {{Pos, {record_expr, Name}}, [transform_abs(A) || A <- Attr]};
transform_abs({record, Pos, Var, Name, Fields}) ->
    {{Pos, {record_update, Name}}, transform_abs(Var), [transform_abs(F) || F <- Fields]};
transform_abs({record_field, Pos, {atom, _, Field}, Value}) ->
    {{Pos, {record_field, Field}}, transform_abs(Value)};
transform_abs({record_field, Pos, {var, _, Field}, Value}) -> %% todo: check the resulted graph
    {{Pos, {record_field, Field}}, transform_abs(Value)};
transform_abs({record_field, Pos, Access, Field, Value}) ->
    {{Pos, {record_access, Field}}, transform_abs(Access), transform_abs(Value)};
transform_abs({record_index, Pos, Name, Field}) ->
    {{Pos, {record_index, Name}}, transform_abs(Field)};
 
%% TODO exact map
transform_abs({map, Pos, []}) ->
    {{Pos, empty_map_expr}};
transform_abs({map, Pos, Fields=[{map_field_exact, _, _, _} | _]}) ->
    {{Pos, exact_map_expr}, [transform_abs(F) || F <- Fields]};
transform_abs({map, Pos, Fields}) ->
    {{Pos, assoc_map_expr}, [transform_abs(F) || F <- Fields]};
transform_abs({map_field_assoc, Pos, Key, Val}) ->
    {{Pos, {infix_expr, '=>'}}, transform_abs(Key), transform_abs(Val)};
transform_abs({map, Pos, MapVar, Fields}) ->
    {{Pos, map_update}, transform_abs(MapVar), [transform_abs(F) || F <- Fields]};
transform_abs({map_field_exact, Pos, Key, Val}) ->
    {{Pos, {infix_expr, ':='}}, transform_abs(Key), transform_abs(Val)};

transform_abs({mc, Pos, HeadExpr, Generator}) ->
    {{Pos, map_comp}, transform_abs(HeadExpr),
		[transform_abs({comp, G}) || G <- Generator]};
transform_abs({comp, {m_generate, Pos, Pattern, Clause}}) ->
    {{Pos, map_gen},  transform_abs(Pattern), transform_abs(Clause)};
transform_abs({comp, {m_generate_strict, Pos, Pattern, Clause}}) ->
    {{Pos, map_gen_strict},  transform_abs(Pattern), transform_abs(Clause)};

transform_abs({bin, Pos, Parameters}) ->
    {{Pos, bin}, [transform_abs(P) || P <- Parameters]};
transform_abs({bc, Pos, HeadExpr, Generator}) ->
    {{Pos, bin_comp}, transform_abs(HeadExpr),
	       [transform_abs({comp, G}) || G <- Generator]};
transform_abs({comp, {b_generate, Pos, Pattern, Clause}}) ->
    {{Pos, binary_gen}, transform_abs(Pattern), transform_abs(Clause)};
transform_abs({comp, {b_generate_strict, Pos, Pattern, Clause}}) ->
    {{Pos, binary_gen_strict}, transform_abs(Pattern), transform_abs(Clause)};
transform_abs({bin_element, Pos, Value, Size, TSL}) ->
    {{Pos, binary_field},
     [case Size of
	  default -> transform_abs(Value);
	  _       -> {size_qualifier, transform_abs(Value), transform_abs(Size)}
      end | 
      get_tsl(TSL)]};

transform_abs({named_fun, Pos, Name, Clauses}) ->
    {{Pos, 'fun'}, [transform_abs({fun_scope, {Name, C}}) || C <- Clauses]};   
transform_abs({'fun', Pos, {clauses, Clauses}}) ->
    {{Pos, 'fun'}, [transform_abs({fun_scope, C}) || C <- Clauses]};
%transform_abs({'fun', Pos, Attr}) ->
%    transform_abs(Attr);
%transform_abs({clauses, Clauses}) ->
%    {'fun', [transform_abs({fun_scope, C}) || C <- Clauses]};

transform_abs({'fun', Pos, {function, Module, Name, Arity}}) ->
    {{Pos, implicit_fun}, {{infix_expr, ':'}, transform_abs(Module), transform_abs(Name)}, transform_abs(Arity)};
                                                %% check whether remote calls are always tree elements
transform_abs({'fun', Pos, {function, Name, Arity}}) ->    
    {{Pos, implicit_fun}, get_type(Name), get_type(Arity)};
% transform_abs({function, Module, Name, Arity}) ->
%     {implicit_fun, {{infix_expr, ':'}, get_type(Module), get_type(Name)}, get_type(Arity)};
% transform_abs({function, Name, Arity}) ->    
%     {implicit_fun, get_type(Name), get_type(Arity)};

transform_abs({block, Pos, Clauses}) ->
    {{Pos, block_expr}, [transform_abs(C) || C <- Clauses]};
transform_abs({'case', Pos, HeadCl, Branches}) ->
    {{Pos, 'case'}, transform_abs(HeadCl),
     [transform_abs({pattern, B}) || B <- Branches]};
transform_abs({'catch', Pos, Expr}) ->
    {{Pos, 'catch'}, transform_abs(Expr)};
transform_abs({'if', Pos, Clauses}) ->
    {{Pos, 'if'}, [transform_abs({guard, C}) || C <- Clauses]};
transform_abs({'receive', Pos, Clauses}) ->
    {{Pos, 'receive'}, [transform_abs({pattern, C}) || C <- Clauses], []};
transform_abs({'receive', Pos, Clauses, TimeOut, TBody}) ->
    {{Pos, 'receive'}, [transform_abs({pattern, C}) || C <- Clauses],
                [transform_abs(TimeOut), [transform_abs(B) || B <- TBody]]};
transform_abs({'try', Pos, HeadCl, ExprCl, CatchCl, AfterCl}) ->
    {{Pos, 'try'}, [transform_abs(H) || H <- HeadCl],
            [transform_abs({pattern, E}) || E <- ExprCl],
            [transform_abs({'catch', C}) || C <- CatchCl],
            [transform_abs(A) || A <- AfterCl]};
transform_abs({'maybe', Pos, HeadCl, {'else', _, ExprCls}}) ->
    {{Pos, 'maybe_expr'}, 
     [transform_abs(H) || H <- HeadCl],
     [transform_abs({pattern, E}) || E <- ExprCls]};
transform_abs({'maybe', Pos, HeadCl}) ->
    {{Pos, 'maybe_expr'}, 
     [transform_abs(H) || H <- HeadCl],
     []};

transform_abs(List) when (element(1, List) == nil) or (element(1, List) == cons) ->
    abslist_to_list(List);
transform_abs({lc, Pos, HeadExpr, Generator}) ->
    {{Pos, list_comp}, transform_abs(HeadExpr),
		[transform_abs({comp, G}) || G <- Generator]};
transform_abs({comp, {generate, Pos, Pattern, Clause}}) ->
    {{Pos, list_gen},  transform_abs(Pattern), transform_abs(Clause)};
transform_abs({comp, {generate_strict, Pos, Pattern, Clause}}) ->
    {{Pos, list_gen_strict},  transform_abs(Pattern), transform_abs(Clause)};
transform_abs({comp, {zip, Pos, Gens}}) ->
    {{Pos, zip_gen},  [transform_abs({comp, Gen}) || Gen <- Gens]};
transform_abs({comp, FilterExpr}) ->
    {{element(2, FilterExpr), filter},  transform_abs(FilterExpr)};

transform_abs({tuple, Pos, Attr}) ->
    {{Pos, tuple}, [transform_abs(A) || A <- Attr]};

transform_abs({match, Pos, LeftOp, RightOp}) ->
    {{Pos, match_expr}, transform_abs(LeftOp), transform_abs(RightOp)};
transform_abs({maybe_match, Pos, LeftOp, RightOp}) ->
    {{Pos, maybe_match_expr}, transform_abs(LeftOp), transform_abs(RightOp)};
transform_abs({op, Pos, '!', LeftOp, RightOp}) ->
    {{Pos, send_expr}, transform_abs(LeftOp), transform_abs(RightOp)};
transform_abs({op, Pos, Operator, LeftOp, RightOp}) when (Operator == 'and') or 
                                                   (Operator == 'or') or
                                                   (Operator == 'andalso') or 
                                                   (Operator == 'orelse') ->
    {{Pos, {infix_expr, Operator}}, {paren, transform_abs(LeftOp)},
			     {paren, transform_abs(RightOp)}};
transform_abs({op, Pos, Operator, LeftOp, RightOp}) ->
    {{Pos, {infix_expr, Operator}}, transform_abs(LeftOp),
			     transform_abs(RightOp)};
transform_abs({op, Pos, Operator, Operand}) ->
    {{Pos, {prefix_expr, Operator}}, transform_abs(Operand)};



%transform_abs({call, _, Name, Parameters}) ->
%    {app, transform_abs(Name), [transform_abs(P) || P <- Parameters]};
transform_abs({call, Pos, Name, Parameters}) ->
    {{Pos, app}, transform_abs(Name), [transform_abs(P) || P <- Parameters]};
transform_abs({remote, Pos, Module, Fun}) ->
    {{Pos, {infix_expr, ':'}}, transform_abs(Module), transform_abs(Fun)};

transform_abs({var, Pos, '_'}) ->
    {{Pos, joker}, []};
transform_abs({atom, [{text, _T}, {location, Pos}], 'else'}) -> 
    %{atom,[{text,"'else'"},{location,{1263,12}}],else} - the scanner returns this annotation for the 'else' atom
    {{Pos, atom}, 'else'};
transform_abs({Kind, Pos, Value}) ->
    {{Pos, Kind}, Value};

transform_abs(Else) ->
    ?d(Else),
    [].
    

%% TODO: position

%% @spec abslist_to_list(tuple()) -> list()
%% @doc
abslist_to_list(List) ->
    abslist_to_list(List, []).

abslist_to_list({nil, _}, List) ->
    case List of
        [] ->
            {cons, []};
        List ->
            {cons, {list, List}}
    end;
abslist_to_list({cons, _, Value, Rest}, List) ->
    abslist_to_list(Rest, List ++ [transform_abs(Value)]);
abslist_to_list(Value, List) ->
    {cons, {list, List}, transform_abs(Value)}.

%% @spec get_tsl(Value) -> list()
%%       Value = atom() | list()
%% @doc
get_tsl(default) ->
    [];
get_tsl([]) ->
    [];
get_tsl([Type | Rest]) ->
    [case Type of
	{T, Size} ->
	    {{bit_size_expr, Size}, get_type(T)};
	_ ->
	    get_type(Type)
    end | get_tsl(Rest)];
get_tsl(Type) ->
    get_type(Type).

%% @spec guard_disj(list()) -> tuple()
%% @doc
guard_disj([]) ->
    [];
guard_disj([Head|Rest]) ->
    case Rest of
	[] ->
	    guard_conj(Head);
	_ ->
	    {{infix_expr, ';'}, guard_conj(Head), guard_disj(Rest)}
    end.

%% @spec guard_conj(list()) -> tuple()
%% @doc
guard_conj([Head|Rest]) ->
    case Rest of
	[] ->
	    transform_abs(Head);
	_ ->
	    {{infix_expr, ','}, transform_abs(Head), guard_conj(Rest)}
    end.

%% @spec tuple_to_infix(tuple()) -> tuple()
%% @doc
tuple_to_infix([{tuple, Pos, [Class, Pattern, _]}]) ->
    case Class of
        'throw' ->
            transform_abs(Pattern);
        Class ->
            {{Pos, {infix_expr, ':'}}, transform_abs(Class), transform_abs(Pattern)}
    end.

%% @spec get_type(Value) -> {Type, Value}
%%    Value = atom() | integer() | float() | list() | tuple()
%%    Type  = atom()
%% @doc
get_type(Value) when is_atom(Value) ->
    {atom, Value};
get_type(Value) when is_integer(Value) ->
    {integer, Value};
get_type(Value) when is_float(Value) ->
    {float, Value};
get_type(Value) when is_tuple(Value) ->
    {tuple, [get_type(element(I, Value)) || I <- lists:seq(1, size(Value))]};
get_type(Value) when ((hd(Value) >= $A) and (hd(Value) =< $Z)) orelse
		     ((hd(Value) >= $a) and (hd(Value) =< $z))->
    {string, Value};
get_type(Value) when is_binary(Value) ->
    {bin, [{binary_field, [{string, binary_to_list(Value)}]}]};
get_type(Value) when is_map(Value) ->
    {assoc_map_expr, maps:fold(fun(K, V, L) ->
                                    [{{infix_expr, '=>'}, get_type(K), get_type(V)} | L] 
                               end, [], Value)};
get_type(Value) ->
    {cons, {list, [get_type(Element) || Element <- Value]}}.
