%%% 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(reflib_rules).
-export([

count_if_exprs/1,
no_if_expression/1,
no_tab_file/1,
no_trailing_whitespace_file/1,
nested_level_max/1,
line_max_length_rule/1,
used_ignored_variables_fun/1,
used_ignored_variables/0,
rev_bin/1,
exceeds_nesting_level/2,
test_filtered_prop/3
]).
-include("lib.hrl").

%Root: {'$gn',root,0}
%lists:filter(fun({T,_}) -> T =:= exprcl end, reflib_rules:depth_first_flatlist()). 

%% @doc Returns the total number of if expressions in the file.    
count_if_exprs(File) ->   
    AllExprs = [?Query:exec(File, ?Query:seq([?File:forms(),
                                            ?Form:clauses(),
                                            ?Clause:exprs(),
                                            ?Expr:top_deep_sub()]))], 
                                                                      
    ExprTypes = lists:flatten([ [ ?Expr:type(E)  || E <- Expr]  || Expr <- AllExprs ]),
    count(if_expr,ExprTypes).                                    



count(X,L) ->
    count(X,L,0).
count(_, [], Count) -> Count;
count(X, [X|Rest], Count) -> count(X, Rest, Count+1);
count(X, [_|Rest], Count) -> count(X, Rest, Count).

%% @doc Checks if the file contains any if expression.
no_if_expression(File) ->
    TopExprs = [ ?Query:exec(File, ?Query:seq([ ?File:forms(),
                                                ?Form:clauses(),
                                                ?Clause:exprs()]))],
    match_rule_all(TopExprs, fun no_if_bfs/1).

%Checks whether every element of the list satisfies the rule
match_rule_all(L, Rule) ->
    case lists:dropwhile( Rule, L) of
    [] -> true;
    [_ | _] -> false
  end.
  
    
no_if_bfs([]) ->
    true;
	    
no_if_bfs([X|XS]) ->
    Data = ?Syn:class(X),
    case Data of
	expr -> 
	    case ?Expr:type(X) of
		if_expr ->
		    false;
		_Else -> 
		    ChildrenNoNametag = [Child || {_ , Child} <- ?Syn:children(X)],
		    NewQueue = XS ++ ChildrenNoNametag,
		    no_if_bfs(NewQueue )
	    end;
	_Else ->
	    ChildrenNoNametag = [Child || {_ , Child} <- ?Syn:children(X)],
	    NewQueue = XS ++ ChildrenNoNametag,
	    no_if_bfs(NewQueue )
    end.

%% TODO: rewrite it to make it compatible with queries
line_max_length_rule(Max) ->
    Files = ?Query:exec(?File:all()),
    Forms   = [ {?Query:exec(File, ?File:forms()), Max} || File <- Files],
    %?d(Forms),
    match_rule_all(Forms, fun line_max_length_bfs/1).
    

line_max_length_bfs({[], _}) ->
    true;
line_max_length_bfs({[X|XS], Max}) ->
    %?d(?Syn:flat_text(X)),
    TreeText = list_to_binary(lists:flatten(?Syn:tree_text(X))),
    SplitText = binary:split(TreeText, <<"\n">>),
    %?d(SplitText),
    %LineLengths = [size(Line) || Line <- SplitText],
    %?d(LineLengths),
    Ok = match_rule_all(SplitText, fun(Line) -> size(Line) =< Max end),
    case Ok of
        true ->
            ChildrenNoNametag = [Child || {_ , Child} <- ?Syn:children(X)],
	        NewQueue = XS ++ ChildrenNoNametag,
	        line_max_length_bfs({NewQueue, Max} );
        _Else ->
            false
    end.

%% @doc Checks if the file contans any tab.
no_tab_file(File) ->
    Forms   = [?Query:exec(File, ?File:forms())],
    match_rule_all(Forms, fun no_tab_bfs/1).

no_tab_bfs([]) ->
    true;
no_tab_bfs([X|XS]) ->
    TreeText = list_to_binary(?Syn:flat_text(X)),
    %?d(TreeText),
    case binary:match(TreeText, <<"\t">>) of
        nomatch ->
            ChildrenNoNametag = [Child || {_, Child} <- ?Syn:children(X)],
            NewQueue = XS ++ ChildrenNoNametag,
            no_tab_bfs(NewQueue );
        {_, _} ->
            false
    end.

% no_trailing_whitespace() -> 
%     Files = ?Query:exec(?File:all()),
%     Forms   = [ ?Query:exec(File, ?File:forms()) || File <- Files],
%     match_rule_all(Forms, fun no_trailing_whitespace_bfs/1). 
    
%% @doc Checks if there are lines in the file that ends with trailing whitespace characters.
no_trailing_whitespace_file(File) -> 
    Forms   =  [?Query:exec(File, ?File:forms())],
    match_rule_all(Forms, fun no_trailing_whitespace_bfs/1). 

no_trailing_whitespace_bfs([]) ->
    true;
no_trailing_whitespace_bfs([X|XS]) ->
    %TreeText = ?Syn:flat_text(X),
    BinTreeText = list_to_binary(?Syn:flat_text(X)),
    SplitText = lists:map(fun reflib_rules:rev_bin/1, binary:split(BinTreeText, <<"\n">>)),
    %?d(SplitText),
    case any_starts_with_ws(SplitText) of
        true ->
            %?d(TreeText),
            false;
        _Else ->
            ChildrenNoNametag = [Child || {_, Child} <- ?Syn:children(X)],
            NewQueue = XS ++ ChildrenNoNametag,
            %?d(NewQueue),
            no_trailing_whitespace_bfs(NewQueue )
    end.


rev_bin(X) ->
    rev_bin_(X,<<>>).

rev_bin_ (<<>>, Acc) -> 
    Acc;
rev_bin_ (<<H:1/binary, Rest/binary>>, Acc) ->
    rev_bin_(Rest, <<H/binary, Acc/binary>>).

any_starts_with_ws(XS) ->
    not match_rule_all(XS, fun not_starts_with_ws/1).

not_starts_with_ws(<<>>) ->
    true;
not_starts_with_ws(X) ->
    binary:first(X) /= 32.


% for_all_clause(Mod, Fun) ->
%     Mods = ?Query:exec(?Mod:all()),
%     Funs = ?Query:exec(Mods, ?Mod:locals()),
%     Forms   = [ ?Query:exec(F, ?Fun:definition()) || F <- Funs ],
%     Clauses = [ ?Query:exec(Form, ?Form:clauses()) || Form <- Forms ],
%     [apply(Mod, Fun, Clause) || Clause <- Clauses].


nesting_level_count(Node,E,D) ->
    {X, ParentFunNode} = Node,
    %?d(Node),
    C =?Syn:children(X),
    %?d(C),
    % TODO: make it work with semantic fun nodes as well
    Children = [{{Tag, Child},X} || {Tag, Child} <- C, Tag =:= funcl] ++
        [{{Tag, Child},ParentFunNode} || {Tag, Child} <- C, Tag =/= funcl],  
    %?d(Children),
    case Children of
        [] ->
            {ParentFunNode, D};
        _Else ->
            [nesting_level_count({Child,F},[Tag] ++ E,D+1) || {{Tag, Child},F} <- Children, Tag =:= exprcl] ++
                [nesting_level_count({Child,F},E,D) || {{Tag, Child},F} <- Children, Tag =/= exprcl]
    end.

exceeds_nesting_level_(Node,_E,0) ->
    {_X, ParentFunNode} = Node,
    [Func] = ?Query:exec(ParentFunNode, ?Form:func()),
    ?Fun:name(Func);

exceeds_nesting_level_(Node,E,Max) ->
    {X, ParentFunNode} = Node,
    C =?Syn:children(X),
    Children = [{{Tag, Child},X} || {Tag, Child} <- C, Tag =:= funcl] ++
        [{{Tag, Child},ParentFunNode} || {Tag, Child} <- C, Tag =/= funcl],  
    case Children of
        [] ->
            [];
        _Else ->
            [exceeds_nesting_level_({Child,F},[Tag] ++ E,Max-1) || {{Tag, Child},F} <- Children, Tag =:= exprcl] ++
                [exceeds_nesting_level_({Child,F},E,Max) || {{Tag, Child},F} <- Children, Tag =/= exprcl]
    end.


nested_level_max([]) ->
    {none, 0};
nested_level_max([Root]) ->
    nested_level_max(Root);
nested_level_max(Root) ->
    L = lists:flatten(nesting_level_count({Root,{}},[],0)),
    FunNodeList = [{Count, FunNode} ||{FunNode, Count} <- L],
    {M, N} = lists:max(FunNodeList),
    [Func] = ?Query:exec(N, ?Form:func()),
    {?Fun:name(Func),M}.
    

% exceeds_nesting_level_all(Fun,Num,Comp) ->
%     %?d(Comp),
%     exceeds_nesting_level(Fun,Num).
        

exceeds_nesting_level([], _Max) ->
    false;    
exceeds_nesting_level([Root], Max) ->
    exceeds_nesting_level(Root, Max);
exceeds_nesting_level({Gn,func,Azon}, Max) ->
        %?d({Gn,func,Azon}),
        FormNode = reflib_query:exec({Gn,func,Azon},reflib_function:definition()), %TODO: handle the []
        %?d(FormNode),
        exceeds_nesting_level(FormNode, Max);
exceeds_nesting_level(Root, Max) ->
    FunNames = lists:usort(lists:flatten(exceeds_nesting_level_({Root,{}},[],Max))),
    %?d(FunNames),
    FunNames /= [].

    
    
    
used_ignored_variables() ->
    Mods = ?Query:exec(?Mod:all()),
    Funs = ?Query:exec(Mods, ?Mod:locals()),
    [used_ignored_variables_fun(Fun) || Fun <- Funs].

used_ignored_variables_fun(Fun) ->
    Vars = ?Query:exec(Fun, ?Query:seq([?Fun:definition(),
                                 ?Form:clauses(),
                                 ?Clause:variables()])),
    %?d([reflib_variable:name(V) || V <- Vars]),                             
    IgnoredVars = filter_ignored_vars(Vars),
    %?d([reflib_variable:name(V) || V <- IgnoredVars]), 
    VarsRefs = [{reflib_variable:name(Var), ?Query:exec(Var, ?Var:references())} || Var <- IgnoredVars ],
    %?d(VarsRefs),
    used_ignored_variables_(VarsRefs).
    
used_ignored_variables_([]) ->   
    false;
used_ignored_variables_([{"_Else",_}|XS]) ->
    used_ignored_variables_(XS);
used_ignored_variables_([{_VarName,[]}|XS]) -> 
    used_ignored_variables_(XS);
used_ignored_variables_(_X) ->
    true.
    

filter_ignored_vars([]) ->
    [];
filter_ignored_vars([X|XS]) ->
    RegExp = "_\\S+",
    VarName = reflib_variable:name(X),

    case re:run(VarName, RegExp) of
        {match, _} -> 
                %?d(VarName),
            [X | filter_ignored_vars(XS)];
        nomatch -> 
            filter_ignored_vars(XS)
    end.

test_filtered_prop(File,Comp,Num) ->
    {File,Comp,Num}.
