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

%%% @doc This module  compares  an Erlang  syntax  tree  to  a  RefactorErl
%%% database and the other way  around, to make sure they're  identical. It
%%% can be used for some intensive testing  of the system  by an  automated
%%% test generator (presumably QuickCheck).

%%% @author Ely Deckers <e.deckers@student.ru.nl>

-module(refqc_tc_compare).

-vsn("$Rev: 17551 $").

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

-author("Ely Deckers <e.deckers@student.ru.nl>").

%%% ============================================================================
%%% Exports
-export([start/1,traverse/2]).
%%% ============================================================================

%% @spec start(File::string()) -> bool() | {error, Error}
%%
%% @doc Starts the comparison process.
start(File) ->
    ?QCTCLOG:clear(),
    Module = find_module_by_filename(File),
    if (Module /= []) ->
        case epp_dodger:parse_file(File) of
            {ok,Tree} ->
                traverse(Module,Tree);
            {error, Info} ->
                "Info: " ++ Info;
            _ ->
                "Unexpected error."
        end;
    true ->
        {error,"Module `" ++ File ++ "` does not exist in
                RefactorErl database."}
    end.

%% @private
%%
%% @spec find_module_by_filename(File::string()) -> list()
%%
%% @doc Returns a module node on basis of the given filename.
find_module_by_filename(File) ->
    ?Query:exec(?Query:seq([?File:find(?File:abs_path(File)),?File:module()])).

%% @spec traverse(Scope::list(),syntaxTree()) -> bool()
%%
%% @doc Traverse the given syntax tree and verify.
traverse(_,[]) -> true;
traverse(Scope,{Atom,NodeId,Value}) ->
    traverse(Scope,[{Atom,NodeId,Value}]);
traverse(Scope,[F|FS]) ->
    IsForm = erl_syntax:is_form(F),

    ?QCTCLOG:writepath("?QCTCCOMPARE:traverse",Scope,F),

    if IsForm ->
        HeadOK = handle_form(Scope,F);
    true ->
        HeadOK = dispatch_node(Scope,F)
    end,

    if HeadOK ->
        TailOK = traverse(Scope,FS),
        (HeadOK and TailOK);
    true ->
        false
    end.

%% @private
%%
%% @spec dispatch_node(Scope::list(),Node::syntaxTree()) -> bool()
%%
%% @doc Dispatches all, but <em>form</em>, Nodes to their respective handlers.
dispatch_node(Scope,Node) ->
    Type = erl_syntax:type(Node),

    case Type of
        clause ->
            ?QCTCCLAUSE:referl_eq(Scope,Node);
        case_expr ->
            ?QCTCEXPR:referl_eq(Scope,Node);
        application ->
            ?QCTCEXPR:referl_eq(Scope,Node);
        _Type ->
            dispatch_primitive(Scope,Node,_Type)
    end.

%% @private
%%
%% @spec dispatch_primitive(Scope::list(),
%% Node::syntaxTree(),Type::atom()) -> bool()
%%
%% @doc Dispatches primitive typed Nodes to their respective handlers.
dispatch_primitive(Scope,Node,Type) ->
    case Type of
        variable ->
            handle_variable(Scope,Node);
        underscore ->
            handle_variable(Scope,Node);
        integer ->
            handle_integer(Scope,Node);
        atom ->
            handle_atom(Scope,Node);
        _ -> false
    end.

%% @private
%%
%% @spec handle_variable(Scope::list(),F::syntaxTree()) -> bool()
%%
%% @doc handles both <em>variable</em> and <em>underscore</em> Nodes, since
%% they're practically the same, but RefactorErl assigns an _ the
%% <em>variable</em> type.
handle_variable(Scope,F) ->
    {ScopeType,_,ReferlValue} = Scope,
    IsVariable = (ScopeType == variable),

    ?QCTCLOG:append(["?QCTCCOMPARE:traverse|variable|ST (",
                atom_to_list(ScopeType),")"]),
    if IsVariable ->
        ErlName = atom_to_list(erl_syntax:variable_name(F)),
        ReferlName = ReferlValue,
        Result = ?QCTCVAR:referl_eq(Scope,F),

        ?QCTCLOG:writepath(["?QCTCCOMPARE:traverse|variable (",ErlName,","
                ,ReferlName,"): ",atom_to_list(Result)],Scope,F),

        Result;
    true ->
        false
    end.

%% @private
%%
%% @spec handle_integer(Scope::list(),F::syntaxTree()) -> bool()
%%
%% @doc Handles <em>integer</em> Nodes.
handle_integer(Scope,F) ->
    {ScopeType,_,ReferlValue} = Scope,
    IsInteger = (ScopeType == integer),
    ?QCTCLOG:append(["?QCTCCOMPARE:traverse|integer|ST (",
        atom_to_list(ScopeType),",",atom_to_list(IsInteger),")"]),
    if IsInteger ->
        ErlValue = erl_syntax:integer_value(F),
        Result = (ErlValue == ReferlValue),
        ?QCTCLOG:writepath(["?QCTCCOMPARE:traverse|integer (",
                                ErlValue,",",ReferlValue,"): ",
        atom_to_list(Result)],Scope,F),
    Result;
    true ->
        false
    end.

%% @private
%%
%% @spec handle_form(Scope::list(),F::syntaxTree()) -> bool()
%%
%% @doc Handles <em>form</em> Nodes.
handle_form(Scope,F) ->
        Description = erl_syntax_lib:analyze_form(F),

        case Description of
            {attribute,{Name,Value}} ->
        handle_attrib(Scope,Name,Value);
            {function,{Name,Arity}} ->
        handle_function(Scope,Name,Arity,F);
            _Other ->
                {error,Description,"Not implemented."}
        end.

%% @private
%%
%% @spec handle_attrib(Scope::list(),Name::atom(),Value::list()) -> bool()
%%
%% @doc Handles <em>attribute</em> Nodes.
handle_attrib(Scope,Name,Value) ->
    AttribOK = ?QCTCATTRIB:referl_eq(Scope,Name,Value),
    if AttribOK ->
        true;
    true ->
        {error,Name,Value,"Faulty attribute"}
    end.

%% @private
%%
%% @spec handle_function(Scope::list(),Name::atom(),
%% Arity::integer(),Node::syntaxTree()) -> bool()
%%
%% @doc Handles <em>function</em> Nodes.
handle_function(Scope,Name,Arity,Node) ->
    FunEq = ?QCTCFUNC:referl_eq(Scope,Name,Arity,Node),
    if FunEq ->
        true;
    true ->
        {error,Node,"Function `" ++ atom_to_list(Name) ++ "/" ++ 
        integer_to_list(Arity) ++ "` does not exists in the RefErl
        database, or it is semantically inequivalent to it's
        erl_syntax peer."}
    end.

%% @private
%%
%% @spec handle_atom(Scope::list(),Node::syntaxTree()) -> bool()
%%
%% @doc Handles <em>atom</em> Nodes.
handle_atom(Scope,Node) ->
    {ScopeType,_,ReferlValue} = Scope,
    IsAtom = (ScopeType == atom),
    if IsAtom ->
        ErlName = erl_syntax:atom_name(Node),
        ReferlName = atom_to_list(ReferlValue),
        Result = ( ReferlName == ErlName),
        
        ?QCTCLOG:writepath(["?QCTCCOMPARE:traverse|atom (",ErlName,","
        ,ReferlName,"): ",atom_to_list(Result)],Scope,Node),

        Result;
    true ->
        false
    end.
