%%% 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 is mimics a random code generator, and is used as a
%%% proof-of-concept.

%%% @author Ely Deckers <e.deckers@student.ru.nl>
-module(refqc_tc_generator).

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

-export([gen/0,gen_file/0,load_generated_code/1]).

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

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

%% @spec gen() -> syntaxTree()
%%
%% @doc a dummy 'random code generator'. The output of this function
%% can be used for testing purposes.
gen() ->
    %Return some 'random' code.
    Dummies = [dummy1(),dummy2(),dummy3()],

    hd(?QCCOMMON:get_n(1,Dummies)).

%% @spec() -> string()
%%
%% @doc Generates a file with randomly generated code and returns its filename.
gen_file() ->
    %Generate binary
    SyntaxTree = gen(),

    %Determine Filename
    ModuleName = get_module_name(SyntaxTree),
    FileName = "generated/" ++ atom_to_list(ModuleName) ++ ".erl",

    %Save code to .erl file.
    save_generated_code(SyntaxTree,FileName),

    %Return the filename of the file that was just generated.
    FileName.
    

%%% ============================================================================
%%% Code generation

%% @private
%%
%% @spec dummy1() -> syntaxTree()
%%
%% @doc Generates:
%%
%% -module(dummy1).
%% -export([f/0]).
%% f() -> 42.
dummy1() ->
    Module = gen_module(dummy1),
    Export = gen_export([{f,0}]),

    FunClause = erl_syntax:clause(none,[erl_syntax:integer(42)]),
    F = erl_syntax:function(erl_syntax:atom(f),[FunClause]),

    [Module,Export,F].

%% @private
%%
%% @spec dummy2() -> syntaxTree()
%%
%% @doc Generates:
%%
%% -module(dummy2).
%% -export([f/0]).
%% f() -> 42.
dummy2() ->
    Module = gen_module(dummy2),
    Export = gen_export([{f,1}]),

    FArgument = erl_syntax:variable('X'),

    Case7 = erl_syntax:clause(
            [erl_syntax:integer(7)],none,[erl_syntax:integer(5)]),
    CaseElse = erl_syntax:clause(
            [erl_syntax:underscore()],none,[FArgument]),
    CaseClause = erl_syntax:case_expr(FArgument,[Case7,CaseElse]),

    FFunClause = erl_syntax:clause([FArgument],none,[CaseClause]),
    F = erl_syntax:function(erl_syntax:atom(f),[FFunClause]),

    [Module,Export,F].

%% @private
%%
%% @spec dummy3() -> syntaxTree()
%%
%% @doc Takes a random Erlang file from the <em>examples</em> directory,
%% and returns its Erlang Syntax Tree. 
dummy3() ->
    ExampleErlFiles = ?QCCOMMON:get_erl_files("examples"),

    % If no files exist in the `examples` directory,
    % then generate some code.
    if (ExampleErlFiles /= []) ->
        FileName = ?QCCOMMON:get_n(1,ExampleErlFiles),

        {ok,Tree} = epp_dodger:parse_file(FileName),

        Tree;
    true ->
        dummy1()
    end.

%%% ============================================================================
%%% Syntax tree actions

%%% ----------------------------------------------------------------------------
%%% Saving and loading trees.

%% @spec load_generated_code(syntaxTree()) -> {module, Module} | {error, What}
%%
%% @doc Compile and load code that is fed to the function as a valid
%% syntax tree.
load_generated_code(SyntaxTree) ->
    RevertedForms = erl_syntax:revert_forms(SyntaxTree),
    {ok,ModuleName,BinaryOrCode} = compile:forms(RevertedForms),
    code:load_binary(ModuleName, ModuleName, BinaryOrCode).

%% @private
%%
%% @spec save_generated_code(syntaxTree(),string()) -> ok | {error, Reason}
%%
%% @doc Save code represented in a syntax tree to a .erl file.
save_generated_code(SyntaxTree,FileName) ->
    % Convert the generated code to a 'normal'
    % .erl representation (list of strings).
    PlainText = [ (erl_prettypr:format(Tree) ++ "\n") ||
            Tree <- SyntaxTree ],

    % Save the generated code to a .erl file.
    {ok,Handle} = file:open( FileName, [write] ),
    file:write(Handle,PlainText),
    file:close(Handle).

%%% ----------------------------------------------------------------------------
%%% Node generators.

%% @private
%%
%% @spec gen_module(string()) -> syntaxTree()
%%
%% @doc Generate a module node.
gen_module(Name) ->
    erl_syntax:attribute(erl_syntax:atom(module),[erl_syntax:atom(Name)]).

%% @private
%%
%% @spec gen_export(list()) -> syntaxTree()
%%
%% @doc Generate an export node.
gen_export(Headers) ->
    Exports = [ erl_syntax:arity_qualifier( erl_syntax:atom(FunName),
                        erl_syntax:integer(Arity)) ||
            {FunName,Arity} <- Headers ],

    erl_syntax:attribute(erl_syntax:atom(export),
                [erl_syntax:list(Exports)]).

%%% ============================================================================
%%% Helpers

%% @spec get_module_name(syntaxTree()) -> atom()
%%
%% @doc Given a list of nodes, returns the module name (as an atom). The
%% functionexpects the first node to be a basic (-module(name)) Module-node.
get_module_name([]) -> none;
get_module_name([ModuleNode|_]) ->
    [Argument] = erl_syntax:attribute_arguments(ModuleNode),
    erl_syntax:atom_value(Argument).
