%%% 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.


%%% ============================================================================
%%% Module information

%%% @author Mate Tejfel <matej@inf.elte.hu>

-module(refqc_libanal_test).
-vsn("$Rev: 4279 $ ").

%% Random module based testing callbacks
-export([prepare/1, perform_and_check/2]).
%% Interface
-export([test_libanal/0, test_libanal_db/2]).

-include_lib("referl_qc/include/qc.hrl").


%% @doc Dictonary analyzer test using the test database.
%% The first parameter determines the library, the second the number of 
%% the analysed files from the library.
%%
%% @spec test_libanal_db(int(), int()) -> boolean() 
test_libanal_db(X,Y) ->
    io:format("The results are:~p~n",  [[db_aux(N,Y) || N <-lists:seq(1, X)]]).


%% @doc Dictonary analyzer test.
%%
%% @spec test_libanal() -> boolean() 
test_libanal() ->
    OldStructs = getStructs(),
    refcore_store_graph:save("prb_k"),
    ri:reset(),
    refcore_store_graph:load("prb_k"),
    NewStructs = getStructs(),    
    ri:reset(),
    lists:foldl(fun({X,Y},Z) 
                   -> case X == Y of
                          false ->  io:format("Differences between:\n~p\n and\n~p\n\n",[X, Y]),
                                   false; 
                          _    ->  Z
                      end end,
                true,
                lists:zip(OldStructs, NewStructs)).      


%% @doc Prepare function for random module based testing.
prepare(Mods) -> [Mods].

%% @doc "Perform and check" function for random module based testing.
perform_and_check(_ModsOrFiles, _Args) -> test_libanal(). 



%%% ============================================================================
%%% Auxulary functions 

%% @private
db_aux(N,Y) ->
    [refcore_fileman:add_file("../db/" ++ integer_to_list(N) ++ "/t" 
        ++ integer_to_list(M) ++ ".erl") || M <- lists:seq(1, Y)],
    test_libanal().

%% @private
myFlatten(List) ->
    lists:flatten(lists:foldl(fun({_,Y},Z) -> Z ++ Y end, [], List)).

%% @private
getFlowsBack(Fun, Ind, Expr, ParamsRets) ->
    FlowBackEdges = ?Query:all([[Y] || Y <- ?Dataflow:back_tags()]),
    Fl2 = ?Query:exec(Expr, FlowBackEdges),
    {{getFunConstr(Fun, name), Ind},backward, 
        lists:sort(lists:flatten(
            getFlowsBack_(Fl2, ParamsRets, FlowBackEdges)))}.

getFlowsBack_(Fl, ParamsRets, Fun) ->
    lists:map(fun(X) -> branchBack(X, ParamsRets, Fun) end, Fl).

%% @private   
branchBack(X, ParamsRets, Fun) ->
    case lists:member(X, ParamsRets) of 
        true -> 
            case ?Query:exec(X, [{fpar,back}]) of
                [] ->
                    case ?Query:exec(X, [{fret, back}]) of
                        [] -> X; %Error in case
                        Ls ->
                           getFunConstr(hd(Ls), fret)  
                    end;
                Ls -> 
                    getFunConstr(hd(Ls), fpar)   
            end; 
        _    ->
            case ?Query:exec(X, Fun) of
                []  -> {?Expr:type(X), ?Expr:value(X)};
                QR  -> getFlowsBack_(QR, ParamsRets, Fun)                      
            end
    end.    

%% @private
getFlowsForw(Fun, Ind, Expr, ParamsRets) ->
    FlowEdges 
        = ?Query:all([[Y] || Y <- ?Dataflow:forw_tags()]), 
    Fl = ?Query:exec(Expr, FlowEdges),
    {{getFunConstr(Fun, name), Ind},forward,
        lists:sort(lists:flatten(
            getFlowsForw_(Fl, ParamsRets, FlowEdges)))}.
    
getFlowsForw_(Fl, ParamsRets, Fun) ->
    lists:map(fun(X) -> branchForw(X, ParamsRets, Fun) end, Fl).

%% @private   
branchForw(X, ParamsRets, Fun) ->
    case lists:member(X, ParamsRets) of 
        true -> 
            case ?Query:exec(X, [{fpar,back}]) of
                [] ->
                    case ?Query:exec(X, [{fret, back}]) of
                        [] -> X; %Error in case
                        Ls ->
                           getFunConstr(hd(Ls), fret)
                    end;
                Ls -> 
                    getFunConstr(hd(Ls), fpar)  
            end; 
        _    ->
            case ?Query:exec(X, Fun) of
                []  -> [];
                QR  -> getFlowsForw_(QR, ParamsRets, Fun)                      
            end
    end.    

%% @private
getFunConstr(Fun, Constr) ->
    Mod = hd(?Query:exec(Fun, ?Fun:module())),
    {Constr, ?Mod:name(Mod), ?Fun:name(Fun), ?Fun:arity(Fun)}. 

%% @private
getStructs() ->
    Moduls = ?Query:exec(?Mod:all()), 
    ModFuns = getPairs(Moduls, ?Mod:locals()),
    ModExps = getPairs(Moduls, ?Mod:exports()),
    Funs = ?Query:exec(?Query:seq([?Mod:all(),?Mod:locals()])),
    FunCalls = getPairs(Funs, ?Fun:funcalls()), 
    Pars   = getPairs(Funs, [fpar]),
    Rets   = getPairs(Funs, [fret]),
    FParsRets = myFlatten(Pars ++ Rets),
    Struct1 
        = lists:map(
              fun({X,Y}) -> 
                  lists:map(fun({Z, I}) 
                      -> getFlowsBack(X,{I,par},Z,FParsRets) end, 
                          ?MISC:index_list(Y,1)) 
              end, Pars) ++
          lists:map(
              fun({X,Y}) -> 
                  lists:map(fun({Z, I}) 
                      -> getFlowsBack(X,{I,ret},Z,FParsRets) end, 
                          ?MISC:index_list(Y,1)) 
              end, Rets),
    SortedStruct1 = lists:keysort(1, lists:flatten(Struct1)),          
    Struct2 
        = lists:map(
              fun({X,Y}) -> 
                  lists:map(fun({Z, I}) 
                      -> getFlowsForw(X,{I,par},Z,FParsRets) end, 
                          ?MISC:index_list(Y,1)) 
              end, Pars) ++
          lists:map(
              fun({X,Y}) -> 
                  lists:map(fun({Z, I}) 
                      -> getFlowsForw(X,{I,ret},Z,FParsRets) end, 
                          ?MISC:index_list(Y,1)) 
              end, Rets),
    SortedStruct2 = lists:keysort(1, lists:flatten(Struct2)),               
    ModFuns_Names  = getNames(ModFuns),
    ModExps_Names  = getNames(ModExps),
    FunCalls_Names = getNamesFun(FunCalls),
    Pars_Num       = getNum(Pars),
    Rets_Num       = getNum(Rets),
    [ModFuns_Names, ModExps_Names, FunCalls_Names, Pars_Num, Rets_Num, 
        SortedStruct1, SortedStruct2].

                        
%% @private
getPairs(List, Fun) ->
    case List of
        []           ->     [];
        [X | XS] ->
            TmpList = [Y || Y <- ?Query:exec(X, Fun)],  
            case TmpList of
                [] -> getPairs(XS, Fun);
                _  -> [{X, TmpList} | getPairs(XS, Fun)]
            end
    end.

%% @private
getNum(List) -> 
    case List of
        []        -> [];
        [X | XS]  ->
            case X of
                {Fun, Args} -> [{getFunConstr(Fun, name),length(Args)} | getNum(XS)];
                _            -> getNum(XS)
            end
    end.   
    
%% @private
getNames(List) -> 
    case List of
        []        -> [];
        [X | XS]  ->
            case X of
                {Modul, Fun} -> [{?Mod:name(Modul),lists:map(fun(Y)-> 
                    getFunConstr(Y, name) end, Fun)}| getNames(XS)];
                _            -> getNames(XS)
            end
    end.            

%% @private
getNamesFun(List) -> 
    case List of
        []        -> [];
        [X | XS]  ->
            case X of
                {Fun, Funs} -> [{getFunConstr(Fun, name), 
                   lists:map(fun(Y)-> getFunConstr(Y, name) end, Funs)}
                        | getNamesFun(XS)];
                _            -> getNamesFun(XS)
            end
    end. 
