%%% 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 Ely Deckers <e.deckers@student.ru.nl>
-module(refqc_regression).

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

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

-export([go/0,go/2]).

%% @doc start the test on a default test-set of files located in `./tests'.
%%
%% @spec go() -> [boolean()]
go() ->
    go(dir,"./tests").

%% @doc start the test on a set of test files, by providing either a directory
%% or a list of paths to Erlang files.
%%
%% @spec go(atom(),string()|list()) -> [boolean()]
go(dir,Dir) ->
    % determine the set of test files that exists in the provided directory
    TestSet = ?QCCOMMON:get_erl_files(Dir),

    % go ahead with testing if the test set is not empty. throw error otherwise
    if (TestSet /= []) ->
        go(files,TestSet);
    true ->
        {error,"There exist no Erlang files in the provided directory"}
    end;
go(files,TestSet) ->
    % get a list of all files in the current directory, presumably the list of
    % QuickCheck testing modules.
    QCFiles = ?QCCOMMON:get_erl_files("."),

    % build a list of QuickCheck modules that compiles without errors
    % the user might be working on a checkout with uncommited files that don't
    % compile.
    ValidMods = get_valid_mods(QCFiles),

    % alias for has_regtest/1
    HasRegTestFilter = fun(Mod) -> has_regtest(Mod) end,

    % determine what QuickCheck modules have a `gen_test/1' function
    TestMods = lists:filter(HasRegTestFilter,ValidMods),

    % execute `gen_test/1' on all QuickCheck modules that contain this function
    io:format("Results:~n"),
    lists:map(fun(Mod) -> {Mod,exec_regtest(Mod,TestSet)} end,TestMods).

%% @doc calls the `regtest' function on a certain module (and copies the
%% set of testfiles to a dedicated directory and initializes the database)
%%
%% @spec exec_regtest(Mod::atom(),ErlFiles::[string()]) -> any()
exec_regtest(Mod,ErlFiles) ->
    io:format("~p",[ErlFiles]),
    io:format("~nInitializing test directories for `~p'.~n",[Mod]),

	 AbsPath = fun(X) -> filename:absname(X) end,
AbsFiles = lists:map(AbsPath,ErlFiles),
    TestSet = init_fileset(Mod,AbsFiles),

    if (TestSet /= []) ->
        io:format("Performing regtest on `~p'~n",[Mod]),

        % initialize the database with the test set.
        ?QCCOMMON:initialize(files,TestSet,[{block,true}]),

        Mod:regtest(TestSet);
    true ->
        io:format("You provided an empty list of "
        "files to test with `~p'~n.",[Mod]),
        true
    end.

init_fileset(Mod,ErlFiles) ->
    %TODO: No hardcoded directory
    %TODO: Clean up this code overall.

    Dir = "./regtestresults",
    ModDir = Dir ++ "/" ++ atom_to_list(Mod),

    MdRes = file:make_dir(Dir),
    ResDirExists =  ((MdRes == ok) or (MdRes == {error,eexist})),

    ModDirExists =  if ResDirExists ->
                        MdMod = file:make_dir(ModDir),
                        ((MdMod == ok) or (MdMod == {error,eexist}));
                    true ->
                        false
                    end,

    if ModDirExists ->
        CopyMap = fun(ErlFile) ->
                      BaseName = filename:basename(ErlFile),
                      TargetFile = lists:flatten(ModDir ++ "/" ++ BaseName),

                      case file:copy(ErlFile,TargetFile) of
                          {ok,_} -> TargetFile;
                          _ -> []
                      end
                  end,
        lists:map(CopyMap,ErlFiles);
    true ->
        []
    end.

%% @doc compiles a list of files and returns a list of module names that
%% compile without errors.
%%
%% @spec get_valid_mods(Files::[string()]) -> [atom()]
get_valid_mods(Files) ->
    % compile the list of files (basic validation, no use for warnings etc.)
    CompiledMods = [compile:file(File,[]) || File <- Files],

    % Filter for selecting only modules that compiled without errors
    ValidFilter = fun({ok,_}) -> true; (_) -> false end,

    % return a list of module names that compile without errors
    [ModName || {ok, ModName} <- lists:filter(ValidFilter, CompiledMods)].

%% @doc determines wheter or not a certain module contains a `regtest/1'
%% function
%%
%% @spec has_regtest(ModName::atom()) -> boolean()
has_regtest(ModName) ->
    % retrieve list of exports from the selected module
    Exports = ModName:module_info(exports),

    % function for filtering out every unwanted export
    Filter = fun(Exp)-> (Exp == {regtest,1}) end,

    % filter the list of exports.
    (lists:filter(Filter, Exports) /= []).
