%----------------------------------------------------------------------------%
% vim: ts=4 sw=4 et ft=mercury
%----------------------------------------------------------------------------%

:- module test_tree_bitset.

:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.

:- import_module test_bitset.

:- import_module bool.
:- import_module enum.
:- import_module int.
:- import_module list.
:- import_module pair.
:- import_module random.
:- import_module require.

%----------------------------------------------------------------------------%

:- type maybe_double
    --->    do_not_double
    ;       do_double.

:- type maybe_write
    --->    do_not_write
    ;       do_write.

:- type which_test
    --->    test_count
    ;       test_foldl
    ;       test_union
    ;       test_intersection
    ;       test_difference
    ;       test_remove_least
    ;       test_delete
    ;       test_delete_list
    ;       test_divide_by_set
    ;       test_all.

%----------------------------------------------------------------------------%

:- func list1 = list(int).

list1 =
    [29, 28, 31, 22, 15, 32, 19, 58, 59, 36, 7, 39, 42,
    34, 25, 40, 59, 2, 19, 44, 47, 38].

:- func list2 = list(int).

list2 =
    [21, 52, 23, 18, 23, 56, 11, 46, 61, 4, 63, 54, 17, 64,
    13, 38, 37, 4, 39, 2, 57, 56, 37, 30, 51, 12, 49,
    58, 31, 48, 61, 42, 53, 44, 55, 14, 9, 40, 43, 50].

main(!IO) :-
    % This control makes it easy to test only a specific operation being
    % being worked on.
    WhichTest = test_all,

    SimpleTest = list1 - list2,
    StressTests = [
        % The values of X and Y on 32 bit machines (or 2X and 2Y on 64 bit
        % machines) are intended to end up in near the start and the end
        % of one interior node, while Z (or 2Z) ends up near the start of
        % the next interior node. These stress tests were derived from a bug
        % in the implementation of the difference operation. The bug was in
        % how the difference operation handled interior nodes at the same level
        % but not at the same starting address.

        [532, 32431] -      [32794],
        [1, 29424] -        [1, 2, 3, 35701],
        [1] -               [2, 35701],
        [101, 102] -        [1, 2, 3, 35699, 35700, 35701],
        [36696, 35702, 35703, 35705] -
                            [1, 2, 3, 33416, 334283],

        % These test the handling of empty sets, which several operations
        % handle with special case code.
        [] -                [2],
        [] -                [2, 35701],
        [2] -               [],
        [2, 35701] -        []
    ],

    % Run one lot of tests with known input lists,
    % to generate some visible output.
    run_test(do_not_double, do_write, WhichTest, SimpleTest, !IO),
    run_tests(do_double, do_write, WhichTest, StressTests, !IO),

    % Run some more tests with random input, checking the output against the
    % output of the corresponding predicatess in set_ordlist.
    % XXX Ten runs with small input sets is not a rigourous test.
    Iterations = 10,
    List1Size = 20,
    List2Size = 40,
    random.init(1, Supply),
    run_random_tests(Iterations, List1Size, List2Size, WhichTest, Supply, !IO).

%----------------------------------------------------------------------------%

:- pred run_random_tests(int::in, int::in, int::in, which_test::in,
    random.supply::mdi, io::di, io::uo) is det.

run_random_tests(Iterations, List1Size, List2Size, WhichTest, !.Supply, !IO) :-
    ( Iterations = 0 ->
        true
    ;
        get_random_numbers(List1Size, [], List1, !Supply),
        get_random_numbers(List2Size, [], List2, !Supply),
        % We cannot write out the random tests, since we cannot anticipate
        % the random numbers in the .exp file.
        run_test(do_not_double, do_not_write, WhichTest, List1 - List2, !IO),

        run_random_tests(Iterations - 1, List1Size, List2Size, WhichTest,
            !.Supply, !IO)
    ).

:- pred get_random_numbers(int::in, list(int)::in, list(int)::out,
    random.supply::mdi, random.supply::muo) is det.

get_random_numbers(Num, List0, List, Supply0, Supply) :-
    ( Num = 0 ->
        List = List0,
        Supply = Supply0
    ;
        % 1048576 = 2^20
        random.random(0, 1048576, RN0, Supply0, Supply1),
        % random.random is broken and can produce numbers out of range.
        RN = abs(RN0),
        get_random_numbers(Num - 1, [RN | List0], List, Supply1, Supply)
    ).

%----------------------------------------------------------------------------%

:- pred run_tests(maybe_double::in, maybe_write::in, which_test::in,
    list(pair(list(int), list(int)))::in, io::di, io::uo) is det.

run_tests(_Double, _Write, _WhichTest, [], !IO).
run_tests(Double, Write, WhichTest, [Test | Tests], !IO) :-
    run_test(Double, Write, WhichTest, Test, !IO),
    run_tests(Double, Write, WhichTest, Tests, !IO).

:- pred run_test(maybe_double::in, maybe_write::in, which_test::in,
    pair(list(int), list(int))::in, io::di, io::uo) is det.

run_test(Double, Write, WhichTest, List1 - List2, !IO) :-
    do_run_test(Write, WhichTest, List1 - List2, !IO),
    (
        Double = do_not_double
    ;
        Double = do_double,
        DoubleList1 = list.map(double, List1),
        DoubleList2 = list.map(double, List2),
        do_run_test(Write, WhichTest, DoubleList1 - DoubleList2, !IO)
    ).

:- func double(int) = int.

double(X) = 2 * X.

%----------------------------------------------------------------------------%

:- pred do_run_test(maybe_write::in, which_test::in,
    pair(list(int), list(int))::in, io::di, io::uo) is det.

do_run_test(Write, WhichTest, List1 - List2, !IO) :-
    (
        Write = do_write,
        io.nl(!IO),
        io.write_string("List1: ", !IO),
        io.write_list(list.sort(List1), ", ", io.write_int, !IO),
        io.nl(!IO),
        io.write_string("List2: ", !IO),
        io.write_list(list.sort(List2), ", ", io.write_int, !IO),
        io.nl(!IO),
        io.nl(!IO)
    ;
        Write = do_not_write
    ),
    Set1 = test_bitset.list_to_set(List1),
    Set2 = test_bitset.list_to_set(List2),

    (
        ( WhichTest = test_count
        ; WhichTest = test_all
        )
    ->
        io.write_string("testing count\n", !IO),
        Count1 = test_bitset.count(Set1),
        Count2 = test_bitset.count(Set2),
        (
            Write = do_write,
            io.write_string("count: ", !IO),
            io.write_int(Count1, !IO),
            io.write_string(" ", !IO),
            io.write_int(Count2, !IO),
            io.nl(!IO)
        ;
            Write = do_not_write
        )
    ;
        true
    ),

    (
        ( WhichTest = test_foldl
        ; WhichTest = test_all
        )
    ->
        io.write_string("testing foldl\n", !IO),
        Sum = (func(Elem, Acc) = Elem + Acc),
        Result1 = test_bitset.foldl(Sum, Set1, 0),
        Result2 = test_bitset.foldl(Sum, Set2, 0),
        (
            Write = do_write,
            io.write_string("Sum of List1 = ", !IO),
            io.write_int(Result1, !IO),
            io.nl(!IO),
            io.write_string("Sum of List2 = ", !IO),
            io.write_int(Result2, !IO),
            io.nl(!IO)
        ;
            Write = do_not_write
        )
    ;
        true
    ),

    (
        ( WhichTest = test_union
        ; WhichTest = test_all
        )
    ->
        io.write_string("testing union\n", !IO),
        Union = test_bitset.union(Set1, Set2),
        maybe_write_bitset(Write, Union, !IO)
    ;
        true
    ),

    (
        ( WhichTest = test_intersection
        ; WhichTest = test_all
        )
    ->
        io.write_string("testing intersection\n", !IO),
        Intersection = test_bitset.intersect(Set1, Set2),
        maybe_write_bitset(Write, Intersection, !IO)
    ;
        true
    ),

    (
        ( WhichTest = test_difference
        ; WhichTest = test_all
        )
    ->
        io.write_string("testing difference\n", !IO),
        Difference = test_bitset.difference(Set1, Set2),
        maybe_write_bitset(Write, Difference, !IO)
    ;
        true
    ),

    (
        ( WhichTest = test_remove_least
        ; WhichTest = test_all
        )
    ->
        io.write_string("testing remove_least\n", !IO),
        ( test_bitset.remove_least(Least, Set1, RemovedLeast) ->
            (
                Write = do_write,
                io.write_int(Least, !IO),
                io.nl(!IO)
            ;
                Write = do_not_write
            ),
            maybe_write_bitset(Write, RemovedLeast, !IO)
        ;
            (
                Write = do_write,
                io.write_string("call failed\n", !IO)
            ;
                Write = do_not_write
            )
        )
    ;
        true
    ),

    (
        ( WhichTest = test_delete
        ; WhichTest = test_all
        )
    ->
        io.write_string("testing delete\n", !IO),
        list.foldl(test_bitset.delete, List2, Set1, Delete2From1),
        maybe_write_bitset(Write, Delete2From1, !IO),

        list.foldl(test_bitset.delete, List1, Set1, Delete1From1),
        require(unify(Delete1From1, init), "Delete1From1 is not empty")
    ;
        true
    ),

    (
        ( WhichTest = test_delete_list
        ; WhichTest = test_all
        )
    ->
        io.write_string("testing delete_list\n", !IO),
        test_bitset.delete_list(List2, Set1, DeleteList2From1),
        maybe_write_bitset(Write, DeleteList2From1, !IO),

        test_bitset.delete_list(List1, Set1, DeleteList1From1),
        require(unify(DeleteList1From1, init), "DeleteList1From1 is not empty")
    ;
        true
    ),

    (
        ( WhichTest = test_divide_by_set
        ; WhichTest = test_all
        )
    ->
        io.write_string("testing divide_by_set\n", !IO),
        test_bitset.divide_by_set(Set1, Set2, InSet, OutSet),
        maybe_write_bitset(Write, InSet, !IO),
        maybe_write_bitset(Write, OutSet, !IO)
    ;
        true
    ).

%----------------------------------------------------------------------------%

:- pred maybe_write_bitset(maybe_write::in, test_bitset(int)::in,
    io::di, io::uo) is det.
:- pragma no_inline(maybe_write_bitset/4).

maybe_write_bitset(Write, Set, !IO) :-
    (
        Write = do_write,
        test_bitset.to_sorted_list(Set, List),
        io.write(List, !IO),
        io.nl(!IO)
    ;
        Write = do_not_write
    ).

%----------------------------------------------------------------------------%
