%---------------------------------------------------------------------------%
% vim: ts=4 sw=4 et ft=mercury
%
% Test the array.[approx_]binary_search predicates.
%
%---------------------------------------------------------------------------%

:- module array_binsearch.

:- interface.

:- import_module io.

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

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

:- implementation.

:- import_module array.
:- import_module int.
:- import_module list.
:- import_module string.

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

main(!IO) :-
    SortedList = [1, 1, 1, 3, 3, 3, 5, 5, 7, 7, 8, 8, 10, 10, 11, 11,
        13, 13, 13, 16, 16, 19, 20, 20, 21, 21, 23, 27, 28, 28, 28, 29, 31],
    DistractedSortedList = [401, 1, 301, 403, 103, 303, 5, 5, 207, 307,
        408, 108, 210, 10, 411, 311,
        113, 213, 213, 316, 216, 319, 420, 220, 321, 121,
        323, 227, 28, 228, 28, 29, 231],
    Max = 32,
    run_undistracted_tests(SortedList, Max, !IO),
    run_distracted_tests(DistractedSortedList, Max, !IO).

:- func distraction_ordering(int, int) = comparison_result.

distraction_ordering(A, B) = R :-
    UndistractedA = A rem 100,
    UndistractedB = B rem 100,
    R = ordering(UndistractedA, UndistractedB).

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

:- pred run_undistracted_tests(list(int)::in, int::in, io::di, io::uo) is det.

run_undistracted_tests(SortedList, Max, !IO) :-
    array.from_list(SortedList, Array),
    io.write_string("Undistracted tests\n", !IO),
    io.write(SortedList, !IO),
    io.nl(!IO),
    io.nl(!IO),
    undistracted_exact_test_loop(Array, 0, Max, !IO),
    undistracted_approx_test_loop(Array, 0, Max, !IO).

:- pred undistracted_exact_test_loop(array(int)::in, int::in, int::in,
    io::di, io::uo) is det.

undistracted_exact_test_loop(Array, Cur, Max, !IO) :-
    ( if Cur > Max then
        io.nl(!IO)
    else
        io.format("exact  search for %3d ", [i(Cur)], !IO),
        ( if array.binary_search(ordering, Array, Cur, Index) then
            ( if
                exact_verify_success(ordering, Array, Max, Cur, Index,
                    First, Last)
            then
                io.format("succeeds correctly   at %3d", [i(Index)], !IO),
                io.format(", first %3d, last %3d\n", [i(First), i(Last)], !IO)
            else
                io.format("succeeds INCORRECTLY at %3d\n", [i(Index)], !IO)
            )
        else
            ( if exact_verify_failure(ordering, Array, Max, Cur) then
                io.format("fails    correctly\n", [], !IO)
            else
                io.format("fails    INCORRECTLY\n", [], !IO)
            )
        ),
        undistracted_exact_test_loop(Array, Cur + 1, Max, !IO)
    ).

:- pred undistracted_approx_test_loop(array(int)::in, int::in, int::in,
    io::di, io::uo) is det.

undistracted_approx_test_loop(Array, Cur, Max, !IO) :-
    ( if Cur > Max then
        io.nl(!IO)
    else
        io.format("approx search for %3d ", [i(Cur)], !IO),
        ( if
            array.approx_binary_search(ordering, Array, Cur, Index)
        then
            ( if
                approx_verify_success(distraction_ordering, Array, Max, Cur,
                    Index, First, Last)
            then
                io.format("succeeds correctly   at %3d", [i(Index)], !IO),
                io.format(", first %3d, last %3d\n", [i(First), i(Last)], !IO)
            else
                io.format("succeeds INCORRECTLY at %3d\n", [i(Index)], !IO)
            )
        else
            ( if
                approx_verify_failure(distraction_ordering, Array, Max, Cur)
            then
                io.format("fails    correctly\n", [], !IO)
            else
                io.format("fails    INCORRECTLY\n", [], !IO)
            )
        ),
        undistracted_approx_test_loop(Array, Cur + 1, Max, !IO)
    ).

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

:- pred run_distracted_tests(list(int)::in, int::in, io::di, io::uo) is det.

run_distracted_tests(SortedList, Max, !IO) :-
    array.from_list(SortedList, Array),
    io.write_string("Distracted tests\n", !IO),
    io.write(SortedList, !IO),
    io.nl(!IO),
    io.nl(!IO),
    distracted_exact_test_loop(Array, 0, 0, Max, !IO),
    distracted_exact_test_loop(Array, 1, 0, Max, !IO),
    distracted_exact_test_loop(Array, 2, 0, Max, !IO),
    distracted_exact_test_loop(Array, 3, 0, Max, !IO),
    distracted_exact_test_loop(Array, 4, 0, Max, !IO),
    distracted_approx_test_loop(Array, 0, 0, Max, !IO),
    distracted_approx_test_loop(Array, 1, 0, Max, !IO),
    distracted_approx_test_loop(Array, 2, 0, Max, !IO),
    distracted_approx_test_loop(Array, 3, 0, Max, !IO),
    distracted_approx_test_loop(Array, 4, 0, Max, !IO).

:- pred distracted_exact_test_loop(array(int)::in, int::in, int::in, int::in,
    io::di, io::uo) is det.

distracted_exact_test_loop(Array, Distraction, Cur, Max, !IO) :-
    ( if Cur > Max then
        io.nl(!IO)
    else
        DCur = Distraction * 100 + Cur,
        io.format("exact  search for %3d ", [i(DCur)], !IO),
        ( if
            array.binary_search(distraction_ordering, Array, DCur, Index)
        then
            ( if
                exact_verify_success(distraction_ordering, Array, Max, DCur,
                    Index, First, Last)
            then
                io.format("succeeds correctly   at %3d", [i(Index)], !IO),
                io.format(", first %3d, last %3d\n", [i(First), i(Last)], !IO)
            else
                io.format("succeeds INCORRECTLY at %3d\n", [i(Index)], !IO)
            )
        else
            ( if
                exact_verify_failure(distraction_ordering, Array, Max, DCur)
            then
                io.format("fails    correctly\n", [], !IO)
            else
                io.format("fails    INCORRECTLY\n", [], !IO)
            )
        ),
        distracted_exact_test_loop(Array, Distraction, Cur + 1, Max, !IO)
    ).

:- pred distracted_approx_test_loop(array(int)::in, int::in, int::in, int::in,
    io::di, io::uo) is det.

distracted_approx_test_loop(Array, Distraction, Cur, Max, !IO) :-
    ( if Cur > Max then
        io.nl(!IO)
    else
        DCur = Distraction * 100 + Cur,
        io.format("approx search for %3d ", [i(DCur)], !IO),
        ( if
            array.approx_binary_search(distraction_ordering, Array,
                DCur, Index)
        then
            ( if
                approx_verify_success(distraction_ordering, Array, Max, DCur,
                    Index, First, Last)
            then
                io.format("succeeds correctly   at %3d", [i(Index)], !IO),
                io.format(", first %3d, last %3d\n", [i(First), i(Last)], !IO)
            else
                io.format("succeeds INCORRECTLY at %3d\n", [i(Index)], !IO)
            )
        else
            ( if
                approx_verify_failure(distraction_ordering, Array, Max, DCur)
            then
                io.format("fails    correctly\n", [], !IO)
            else
                io.format("fails    INCORRECTLY\n", [], !IO)
            )
        ),
        distracted_approx_test_loop(Array, Distraction, Cur + 1, Max, !IO)
    ).

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

:- pred exact_verify_success(comparison_func(int)::in, array(int)::in,
    int::in, int::in, int::in, int::out, int::out) is semidet.

exact_verify_success(Cmp, Array, Max, Value, FoundIndex,
        FirstIndex, LastIndex) :-
    array.lookup(Array, FoundIndex, ValueAtFoundIndex),
    Cmp(ValueAtFoundIndex, Value) = Res,
    Res = (=),
    require_det (
        exact_search_back(Cmp, Array, Value, FoundIndex, FirstIndex),
        exact_search_forward(Cmp, Array, Max, Value, FoundIndex, LastIndex)
    ).

:- pred exact_search_back(comparison_func(int)::in, array(int)::in,
    int::in, int::in, int::out) is det.

exact_search_back(Cmp, Array, Value, CurIndex, FirstIndex) :-
    % invariant: Array[CurIndex] = Value
    NextIndex = CurIndex - 1,
    ( if NextIndex >= 0 then
        array.lookup(Array, NextIndex, ValueAtNextIndex),
        ( if Cmp(ValueAtNextIndex, Value) = (=) then
            exact_search_back(Cmp, Array, Value, NextIndex, FirstIndex)
        else
            FirstIndex = CurIndex
        )
    else
        FirstIndex = CurIndex
    ).

:- pred exact_search_forward(comparison_func(int)::in, array(int)::in,
    int::in, int::in, int::in, int::out) is det.

exact_search_forward(Cmp, Array, Max, Value, CurIndex, LastIndex) :-
    % invariant: Array[CurIndex] = Value
    NextIndex = CurIndex + 1,
    ( if NextIndex < Max then
        array.lookup(Array, NextIndex, ValueAtNextIndex),
        ( if Cmp(ValueAtNextIndex, Value) = (=) then
            exact_search_forward(Cmp, Array, Max, Value, NextIndex, LastIndex)
        else
            LastIndex = CurIndex
        )
    else
        LastIndex = CurIndex
    ).

:- pred exact_verify_failure(comparison_func(int)::in, array(int)::in,
    int::in, int::in) is semidet.

exact_verify_failure(Cmp, Array, Max, Value) :-
    exact_verify_failure_loop(Cmp, Array, 0, Max, Value).

:- pred exact_verify_failure_loop(comparison_func(int)::in, array(int)::in,
    int::in, int::in, int::in) is semidet.

exact_verify_failure_loop(Cmp, Array, Cur, Max, Value) :-
    ( if Cur = Max then
        true
    else
        array.lookup(Array, Cur, CurValue),
        Cmp(CurValue, Value) \= (=),
        exact_verify_failure_loop(Cmp, Array, Cur + 1, Max, Value)
    ).

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

:- pred approx_verify_success(comparison_func(int)::in, array(int)::in,
    int::in, int::in, int::in, int::out, int::out) is semidet.

approx_verify_success(Cmp, Array, Max, Value, FoundIndex,
        FirstIndex, LastIndex) :-
    array.lookup(Array, FoundIndex, ValueAtFoundIndex),
    Cmp(ValueAtFoundIndex, Value) = Res,
    (
        Res = (=),
        require_det (
            exact_search_back(Cmp, Array, Value, FoundIndex, FirstIndex),
            exact_search_forward(Cmp, Array, Max, Value, FoundIndex, LastIndex)
        )
    ;
        Res = (<),
        require_det (
            exact_search_back(Cmp, Array, ValueAtFoundIndex,
                FoundIndex, FirstIndex),
            exact_search_forward(Cmp, Array, Max, ValueAtFoundIndex,
                FoundIndex, LastIndex)
        ),
        FollowIndex = LastIndex + 1,
        ( if FollowIndex < Max then
            array.lookup(Array, FollowIndex, ValueAtFollowIndex),
            Cmp(Value, ValueAtFollowIndex) = (<)
        else
            true
        )
    ).

:- pred approx_verify_failure(comparison_func(int)::in, array(int)::in,
    int::in, int::in) is semidet.

approx_verify_failure(Cmp, Array, Max, Value) :-
    approx_verify_failure_loop(Cmp, Array, 0, Max, Value).

:- pred approx_verify_failure_loop(comparison_func(int)::in, array(int)::in,
    int::in, int::in, int::in) is semidet.

approx_verify_failure_loop(Cmp, Array, Cur, Max, Value) :-
    ( if Cur = Max then
        true
    else
        array.lookup(Array, Cur, CurValue),
        Cmp(CurValue, Value) = (>),
        approx_verify_failure_loop(Cmp, Array, Cur + 1, Max, Value)
    ).

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