-module(hipe_testsuite_driver).

-export([create_all_suites/1, run/3]).

-include_lib("kernel/include/file.hrl").

-type testcase()  :: atom().
-type file_type() :: 'device' | 'directory' | 'regular' | 'other'.
-type ext_posix() :: file:posix() | 'badarg'.

-define(suite_suffix, "_SUITE").
-define(data_folder, "_data").
-define(suite_data, ?suite_suffix ++ ?data_folder).

-record(suite, {suitename  :: string(),
		outputfile :: file:io_device(),
		testcases  :: [testcase()]}).

-spec create_all_suites([string()]) -> 'ok'.

create_all_suites(SuitesWithSuiteSuffix) ->
    Suites = get_suites(SuitesWithSuiteSuffix),
    lists:foreach(fun create_suite/1, Suites).

-spec get_suites([string()]) -> [string()].

get_suites(SuitesWithSuiteSuffix) ->
    Prefixes = [suffix(F, ?suite_suffix) || F <- SuitesWithSuiteSuffix],
    [S || {yes, S} <- Prefixes].

suffix(String, Suffix) ->
    case string:split(String, Suffix, trailing) of
	[Prefix,[]] -> {yes, Prefix};
        _ -> no
    end.

-spec file_type(file:filename()) -> {ok, file_type()} | {error, ext_posix()}.

file_type(Filename) ->
    case file:read_file_info(Filename) of
        {ok, FI} -> {ok, FI#file_info.type};
        Error    -> Error
    end.

-spec create_suite(string()) -> 'ok'.

create_suite(SuiteName) ->
    {ok, Cwd} = file:get_cwd(),
    SuiteDirN = filename:join(Cwd, SuiteName ++ ?suite_data),
    OutputFile = generate_suite_file(Cwd, SuiteName),
    generate_suite(SuiteName, OutputFile, SuiteDirN).

generate_suite_file(Cwd, SuiteName) ->
    F =	filename:join(Cwd, SuiteName ++ ?suite_suffix ++ ".erl"),
    case file:open(F, [write]) of
	{ok, IoDevice} -> IoDevice;
	{error, _} = E -> exit({E, F})
    end.

generate_suite(SuiteName, OutputFile, SuiteDirN) ->
    TestCases = list_testcases(SuiteDirN),
    Suite = #suite{suitename = SuiteName, outputfile = OutputFile,
		   testcases = TestCases},
    write_suite(Suite),
    file:close(OutputFile).

list_testcases(Dirname) ->
    {ok, Files} = list_dir(Dirname, ".erl", true),
    [list_to_atom(filename:basename(F, ".erl")) || F <- Files].

-spec list_dir(file:filename(), string(), boolean()) ->
                      {error, ext_posix()} | {ok, [file:filename()]}.

list_dir(Dir, Extension, Dirs) ->
    case file:list_dir(Dir) of
        {error, _} = Error -> Error;
        {ok, Filenames} ->
            FullFilenames = [filename:join(Dir, F) || F <- Filenames],
            Matches1 = case Dirs of
                           true ->
                               [F || F <- FullFilenames,
                                     file_type(F) =:= {ok, 'directory'}];
                           false -> []
                       end,
            Matches2 = [F || F <- FullFilenames,
                             file_type(F) =:= {ok, 'regular'},
                             filename:extension(F) =:= Extension],
            {ok, lists:sort(Matches1 ++ Matches2)}
    end.

write_suite(Suite) ->
    write_header(Suite),
    write_testcases(Suite).

write_header(#suite{suitename = SuiteName, outputfile = OutputFile,
		    testcases = TestCases}) ->
    Exports = format_export(TestCases),
    TimeLimit = 6,	%% with 1, 2, or 3 it fails on some slow machines...
    io:format(OutputFile,
	      "%% ATTENTION!\n"
	      "%% This is an automatically generated file. Do not edit.\n\n"
	      "-module(~s).\n\n"
	      "-export([suite/0, init_per_suite/0, init_per_suite/1,\n"
	      "         end_per_suite/1, all/0]).\n"
	      "~s\n\n"
	      "-include_lib(\"common_test/include/ct.hrl\").\n\n"
	      "suite() ->\n"
	      "  [{timetrap, {minutes, ~w}}].\n\n"
	      "init_per_suite() ->\n"
	      "  [].\n\n"
	      "init_per_suite(Config) ->\n"
	      "  case erlang:system_info(hipe_architecture) of\n"
	      "    undefined -> {skip, \"HiPE not available or enabled\"};\n"
	      "    _ -> Config\n"
	      "  end.\n\n"
	      "end_per_suite(_Config) ->\n"
	      "  ok.\n\n"
	      "all() ->\n"
	      "  ~p.\n\n"
	      "test(Config, TestCase) ->\n"
	      "  Dir = ?config(data_dir, Config),\n"
	      "  OutDir = ?config(priv_dir, Config),\n"
	      "  hipe_testsuite_driver:run(TestCase, Dir, OutDir)."
	      "\n\n",
	      [SuiteName ++ ?suite_suffix, Exports, TimeLimit, TestCases]).

format_export(TestCases) ->
    TL = [list_to_atom(atom_to_list(N)++"/1") || N <- TestCases],
    TestCaseString = io_lib:format("-export(~p).", [TL]),
    strip_quotes(lists:flatten(TestCaseString), []).

strip_quotes([], Result) ->
    lists:reverse(Result);
strip_quotes([$' |Rest], Result) ->
    strip_quotes(Rest, Result);
strip_quotes([$\, |Rest], Result) ->
    strip_quotes(Rest, [$\ , $\, |Result]);
strip_quotes([C|Rest], Result) ->
    strip_quotes(Rest, [C|Result]).

write_testcases(#suite{outputfile = OutputFile, testcases = TestCases}) ->
    lists:foreach(fun (T) -> write_testcase(OutputFile, T) end, TestCases).

write_testcase(OutputFile, TestCase) ->
    io:format(OutputFile,
	      "~p(Config) ->\n"
	      "  test(Config, ~p).\n\n",
	      [TestCase, TestCase]).

-spec run(atom(), string(), string()) -> 'ok'.

run(TestCase, Dir, _OutDir) ->
    F = filename:join(Dir, atom_to_list(TestCase) ++ ".erl"),
    {ok, TestCase} = compile:file(F),
    ok = try TestCase:prepare_for_test() catch _:_ -> ok end,
    %% DataFiles = try TestCase:datafiles() catch _:_ -> [] end,
    %% lists:foreach(fun (DF) ->
    %% 			  Src = filename:join(Dir, DF),
    %% 			  Dst = filename:join(OutDir, DF),
    %% 			  {ok, _} = file:copy(Src, Dst)
    %% 		  end, DataFiles),
    %% try
    ok = TestCase:test(),
    HiPEOpts0 = try TestCase:hipe_options() catch error:undef -> [] end,
    HiPEOpts = HiPEOpts0 ++ hipe_options(),
    {ok, TestCase} = hipe:c(TestCase, HiPEOpts),
    ok = TestCase:test(),
    {ok, TestCase} = hipe:c(TestCase, [o1|HiPEOpts]),
    ok = TestCase:test(),
    {ok, TestCase} = hipe:c(TestCase, [o0|HiPEOpts]),
    ok = TestCase:test(),
    ToLLVM = try TestCase:to_llvm() catch error:undef -> true end,
    case ToLLVM andalso hipe:erllvm_is_supported() of
	true ->
	    {ok, TestCase} = hipe:c(TestCase, [to_llvm|HiPEOpts]),
	    ok = TestCase:test();
	false -> ok
    end.
    %% after
    %% 	lists:foreach(fun (DF) -> ok end, % = file:delete(DF) end,
    %% 		      [filename:join(OutDir, D) || D <- DataFiles])
    %% end.

hipe_options() ->
    [verify_gcsafe].