%%% 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_dict_test).
-vsn("$Rev: 4279 $ ").

-export([test_part1/1, test_part2/0, info/0, info2/0, test/0, test_tmp/1]).

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

%% @doc Printing information stored by the dictonary analyzer.
%%
%% @spec info() -> ok
info()  ->
    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()),
    io:format("Infos :~p\n", [{{ModFuns, ModExps, FunCalls}, {},
      {getNames(ModFuns), getNames(ModExps), getNamesFun(FunCalls)}}]).


info2()  ->
    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)),           
    io:format("Struct1:~p\n\n",[Struct1]),
    io:format("SortedStruct1:~p\n\n",[SortedStruct1]),
    io:format("Struct2:~p\n\n",[Struct2]),
    io:format("SortedStruct2:~p\n\n",[SortedStruct2]),
    io:format("Infos :~p\n", [{{ModFuns, ModExps, FunCalls, Pars, Rets}, {},
        {getNames(ModFuns), getNames(ModExps), getNamesFun(FunCalls), 
            getNum(Pars), getNum(Rets)}}]).


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

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, Rets, Fun) ->
    lists:map(fun(X) -> branchBack(X, Rets, Fun) end, Fl).
   
branchBack(X, Rets, Fun) ->
    case lists:member(X, Rets) 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, Rets, Fun)                      
            end
    end.    

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

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

test_tmp(Path) ->
    test_part1(Path),
    test_part2().

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

%% @doc Dictonary analyzer test.
%%
%% @spec test() -> boolean() 
test() ->
    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 The first part of the dictonary analyzer test.
%%
%% @spec test_part1(atom()) -> boolean() 
test_part1(Path) ->
    refcore_fileman:add_file(Path),
    Moduls_old = ?Query:exec(?Mod:all()), 
    ModFuns_old = getPairs(Moduls_old, ?Mod:locals()),
    ModExps_old = getPairs(Moduls_old, ?Mod:exports()),
    Funs_old = ?Query:exec(?Query:seq([?Mod:all(),?Mod:locals()])),
    FunCalls_old = getPairs(Funs_old, ?Fun:funcalls()), 
    ModFuns_old_Names = getNames(ModFuns_old),
    ModExps_old_Names = getNames(ModExps_old),
    FunCalls_old_Names = getNamesFun(FunCalls_old),
    
    refcore_store_graph:save("prb_k"),
    refcore_fileman:drop_file(hd(?Query:exec(?File:find(Path)))),
    
    %refcore_store_graph:load("prb_k"),

    %Moduls_new = ?Query:exec(?Mod:all()), 
    %ModFuns_new = getPairs(Moduls_new, ?Mod:locals()),
    %ModExps_new = getPairs(Moduls_new, ?Mod:exports()),
    %Funs_new = ?Query:exec(?Query:seq([?Mod:all(),?Mod:locals()])),
    %FunCalls_new = getPairs(Funs_new, ?Fun:funcalls()),
    %io:format("Test1 infos:~p\n", [{{ModFuns_old, ModExps_old, FunCalls_old}, 
    %  {}, {ModFuns_new, ModExps_new, FunCalls_new}}]),
    io:format("Test1 infos (by name):~p\n", [{ModFuns_old_Names, 
           ModExps_old_Names, FunCalls_old_Names}]), 
    %io:format("Test1 infos (by name):~p\n", [{{ModFuns_old_Names, 
    %       ModExps_old_Names, FunCalls_old_Names}, 
      %{}, {getNames(ModFuns_new), getNames(ModExps_new), getNamesFun(FunCalls_new)}}]),  
    %{ModFuns_old_Names, ModExps_old_Names, FunCalls_old_Names} ==
      %{getNames(ModFuns_new), getNames(ModExps_new), getNamesFun(FunCalls_new)}.    
    true.

%% @doc The second part of the dictonary analyzer test.
%%
%% @spec test_part2() -> boolean() 
test_part2() ->
    refcore_store_graph:load("prb_k"),

    Moduls_new = ?Query:exec(?Mod:all()), 
    ModFuns_new = getPairs(Moduls_new, ?Mod:locals()),
    ModExps_new = getPairs(Moduls_new, ?Mod:exports()),
    Funs_new = ?Query:exec(?Query:seq([?Mod:all(),?Mod:locals()])),
    FunCalls_new = getPairs(Funs_new, ?Fun:funcalls()),
    io:format("Test1 infos (by name):~p\n", [{getNames(ModFuns_new), 
          getNames(ModExps_new), getNamesFun(FunCalls_new)}]),  
    true.    


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.


getNum(List) -> 
    case List of
        []        -> [];
        [X | XS]  ->
            case X of
                {Fun, Args} -> [{getFunConstr(Fun, name),length(Args)} | getNum(XS)];
                _            -> getNum(XS)
            end
    end.   

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.            

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. 
