%-----------------------------------------------------------------------------%
% vim: ts=4 sw=4 et tw=0 wm=0 ft=mercury
%-----------------------------------------------------------------------------%
:- module bitmap_test.

:- interface.

:- import_module io.

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

:- implementation.

:- import_module bitmap.
:- import_module bitmap_tester.
:- import_module bitmap_simple.
:- import_module bool.
:- import_module deconstruct.
:- import_module exception.
:- import_module int.
:- import_module list.
:- import_module string.
:- import_module univ.

main(!IO) :-
    try_io(run_test, Res, !IO),
    (
        Res = succeeded(_)
    ;
        Res = exception(Excp),
        io.set_exit_status(1, !IO),
        ( univ_to_type(Excp, BitmapResErr) ->
            io.nl(!IO),
            write_bitmap_result_error(BitmapResErr, !IO)
        ;
            rethrow(Res)
        )
    ).

:- pred run_test({}::out, io::di, io::uo) is cc_multi.

run_test({}, !IO) :-
    some [!BM] (
        io.write_string("Single byte bitmap\n", !IO),
        !:BM = bitmap_tester.new(4, yes),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        write(!.BM ^ bit(0), !IO),
        nl(!IO),
        !:BM = !.BM ^ bit(1) := no,
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        write(!.BM ^ bit(1), !IO),
        nl(!IO),
        !:BM = !.BM ^ bit(2) := no,
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        write_binary_string(!.BM ^ bits(0, 4), !IO),
        nl(!IO),
        !:BM = !.BM ^ bits(0, 2) := \ (!.BM ^ bits(0, 2)),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        !:BM = !.BM ^ bits(0, 0) := !.BM ^ bits(4, 0),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO)
    ),
    some [!BM] (
        io.write_string("Multi-byte bitmap\n", !IO),
        !:BM = bitmap_tester.new(20, no),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        !:BM = flip(!.BM, 1),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        !:BM = ((((((!.BM  ^ bit(3) := yes)
                        ^ bit(6) := yes)
                        ^ bit(8) := yes)
                        ^ bit(10) := yes)
                        ^ bit(12) := yes)
                        ^ bit(17) := yes),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        write_binary_string(!.BM ^ bits(1, 10), !IO),
        nl(!IO),
        write_binary_string(!.BM ^ bits(8, 12), !IO),
        nl(!IO),
        !:BM = !.BM ^ bits(6, 12) := \ (!.BM ^ bits(6, 12)),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        !:BM = flip(!.BM, 6),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        !:BM = complement(!.BM),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        !:BM = resize(!.BM, 32, yes),
        io.write_string(to_string(!.BM ^ fst), !IO),
        nl(!IO),
        functor(!.BM ^ fst, do_not_allow, Functor, _),
        io.write_string(Functor, !IO),
        nl(!IO)
    ),
    some [!BM] (
        io.write_string("Longer bitmap\n", !IO),
        !:BM = bitmap_tester.new(160, no),
        BytePattern = 0b10111001,
        fill_in_alternating_pattern(BytePattern, !BM),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        !:BM = !.BM ^ bits(6, 12) := \ (!.BM ^ bits(6, 12)),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),
        io.write_string("non-overlapping copy_bits\n", !IO),
        !:BM = copy_bits_in_bitmap(!.BM, 12, 64, 32),
        io.write_string(to_byte_string(!.BM), !IO),
        nl(!IO),

        io.write_string("testing builtin.copy\n", !IO),
        builtin.copy(!.BM ^ fst, CopyBM),
        io.write_string(to_byte_string(CopyBM), !IO),
        nl(!IO),
        ( CopyBM = !.BM ^ fst ->
            io.write_string("Copy succeeded\n", !IO)
        ;
            io.write_string("Copy failed\n", !IO)
        )
    ),

    io.write_string("Test simple aligned byte block copy.\n", !IO),
    test_copy(8, 64, 32, !IO),

    io.write_string("Test byte block copy with extra bits on ends.\n", !IO),
    test_copy(6, 62, 36, !IO),

    io.write_string("Test unaligned copy.\n", !IO),
    test_copy(7, 64, 32, !IO),

    io.write_string("Test unaligned copy with extra bits on ends.\n", !IO),
    test_copy(7, 67, 36, !IO),

    io.write_string("Test overlapping aligned byte block copy.\n", !IO),
    test_copy(8, 0, 36, !IO),

    io.write_string("Test overlapping aligned byte block copy.\n", !IO),
    test_copy(0, 8, 36, !IO),

    io.write_string("Test overlapping unaligned copy.\n", !IO),
    test_copy(2, 1, 36, !IO),

    io.write_string("Test overlapping unaligned copy.\n", !IO),
    test_copy(1, 2, 36, !IO),

    io.write_string("Test copy to same position.\n", !IO),
    test_copy(1, 1, 36, !IO),

    io.write_string("Test copy to end of bitmap.\n", !IO),
    test_copy(0, 1, 166, !IO),

    io.write_string("Test zero bit copies.\n", !IO),
    test_copy(0, 1, 0, !IO),
    test_copy(0, 167, 0, !IO),
    
    test_set_op("union", union, !IO), 
    test_set_op("intersect", intersect, !IO),
    test_set_op("difference", difference, !IO),
    test_set_op("xor", xor, !IO),

    test_binary_op("ordering", bitmap_tester.ordering, !IO),
    test_binary_op("test_unify", bitmap_tester.test_unify, !IO),

    test_text_io(!IO),
    test_binary_io(!IO),
    
    some [!BM] (
        !:BM = bitmap.new(64, yes),
        !:BM = !.BM ^ bits(32, 16) := 0b1011011100100101,
        test_exception(
            ((pred) is semidet :-
                _ = !.BM ^ bit(-1)
            ), !IO),
        test_exception(
            ((pred) is semidet :-
                _ = !.BM ^ bit(64)
            ), !IO),
        test_exception(
            ((pred) is semidet :-
                _ = !.BM ^ bit(73)
            ), !IO),
        test_exception(
            ((pred) is semidet :-
                _ = copy_bits_in_bitmap(copy(!.BM), -1, 1, 32)
            ), !IO),
        test_exception(
            ((pred) is semidet :-
                _ = copy_bits_in_bitmap(copy(!.BM), 33, 32, 32)
            ), !IO),
        test_exception(
            ((pred) is semidet :-
                _ = copy_bits_in_bitmap(copy(!.BM), 32, 33, 32)
            ), !IO),
        test_exception(
            ((pred) is semidet :-
                _ = copy(!.BM) ^ bits(-1, 32)
            ), !IO),
        test_exception(
            ((pred) is semidet :-
                _ = copy(!.BM) ^ bits(33, 32)
            ), !IO),
        test_exception(
            ((pred) is semidet :-
                _ = copy(!.BM) ^ bits(0, 65)
            ), !IO),
        test_exception(
            ((pred) is semidet :-
                _ = copy(!.BM) ^ bits(0, -1)
            ), !IO),
        test_exception(
            ((pred) is semidet :-
                _ = copy(!.BM) ^ bits(65, 0)
            ), !IO),
        test_exception(
            ((pred) is semidet :-
                _ = copy(!.BM) ^ bits(-1, 0)
            ), !IO)
    ).

:- pred test_exception((pred)::in((pred) is semidet),
    io::di, io::uo) is cc_multi.

test_exception(Pred, !IO) :-
    try((pred({}::out) is semidet :- Pred), Result),
    (
        Result = succeeded(_),
        io.write_string("Error: test succeeded, expected exception\n", !IO)
    ;
        Result = failed,
        io.write_string("Error: test failed, expected exception\n", !IO)
    ;    
        Result = exception(Exception),
        io.write_string("Found exception as expected: ", !IO),
        io.write(univ_value(Exception), !IO),
        io.nl(!IO)
    ).

    % Do the copy tests to a few different bitmaps, to make sure
    % correct results aren't a fluke of the original contents, and
    % to check that the copy isn't disturbing bits outside the
    % destination range.
    %
:- pred test_copy(bit_index::in, bit_index::in, num_bits::in,
    io::di, io::uo) is det.

test_copy(SrcStart, DestStart, NumBits, !IO) :-
    BMLength = 167,
    some [!DestBM, !SrcBM] (
        io.format("Copy %i %i %i\n", [i(SrcStart), i(DestStart), i(NumBits)],
            !IO),

        !:SrcBM = bitmap_tester.new(BMLength, no),
        BytePattern = 0b10111001,
        fill_in_alternating_pattern(BytePattern, !SrcBM),

        io.write_string("Copy to zeroed bitmap\n", !IO),
        !:DestBM = bitmap_tester.new(BMLength, no),
        !:DestBM = copy_bits(!.SrcBM, SrcStart, !.DestBM, DestStart, NumBits),
        io.write_string(to_byte_string(!.DestBM), !IO),
        nl(!IO),

        io.write_string("Copy to filled bitmap\n", !IO),
        !:DestBM = bitmap_tester.new(BMLength, yes),
        !:DestBM = copy_bits(!.SrcBM, SrcStart, !.DestBM, DestStart, NumBits),
        io.write_string(to_byte_string(!.DestBM), !IO),
        nl(!IO),

        io.write_string("Copy to alternating bitmap\n", !IO),
        !:DestBM = bitmap_tester.new(BMLength, yes),
        fill_in_alternating_pattern(0b10101010, !DestBM),
        !:DestBM = copy_bits(!.SrcBM, SrcStart, !.DestBM, DestStart, NumBits),
        io.write_string(to_byte_string(!.DestBM), !IO),
        nl(!IO),

        io.write_string("Copy to same bitmap\n", !IO),
        !:DestBM = copy_bits_in_bitmap(!.SrcBM, SrcStart, DestStart, NumBits),
        io.write_string(to_byte_string(!.DestBM), !IO),
        nl(!IO)
    ).

:- pred test_set_op(string, (func(tbitmap, tbitmap) = tbitmap), io, io).
:- mode test_set_op(in, (func(tbitmap_ui, tbitmap_di) = tbitmap_uo is det),
    di, uo) is det.

test_set_op(OpStr, Op, !IO) :-
    test_binary_op(OpStr, Op, 
            (pred(TBM::in, !.IO::di, !:IO::uo) is det :-
                io.write_string(to_byte_string(TBM ^ fst), !IO)
            ), !IO).

:- pred test_binary_op(string, (func(tbitmap, tbitmap) = T), io, io).
:- mode test_binary_op(in, (func(tbitmap_ui, tbitmap_di) = out is det),
    di, uo) is det.

test_binary_op(OpStr, Op, !IO) :-
    test_binary_op(OpStr, Op, io.write, !IO).

:- pred test_binary_op(string, (func(tbitmap, tbitmap) = T),
    pred(T, io, io), io, io).
:- mode test_binary_op(in, (func(tbitmap_ui, tbitmap_di) = out is det),
    (pred(in, di, uo) is det), di, uo) is det.

test_binary_op(OpStr, Op, Writer, !IO) :-
    test_binary_op(8, OpStr, Op, Writer, !IO),
    test_binary_op(64, OpStr, Op, Writer, !IO). 

:- pred test_binary_op(int, string, (func(tbitmap, tbitmap) = T),
    pred(T, io, io), io, io).
:- mode test_binary_op(in, in, (func(tbitmap_ui, tbitmap_di) = out is det),
    (pred(in, di, uo) is det), di, uo) is det.

test_binary_op(BMLength, OpStr, Op, Writer, !IO) :-
    ZeroedBM = bitmap_tester.new(BMLength, no),
    OnesBM = bitmap_tester.new(BMLength, yes),
    
    PatternBM0 = bitmap_tester.new(BMLength, no),
    BytePattern = 0b10111001,
    fill_in_alternating_pattern(BytePattern, PatternBM0, PatternBM),

    AlternatingBM0 = bitmap_tester.new(BMLength, yes),
    fill_in_alternating_pattern(0b10101010, AlternatingBM0, AlternatingBM),

    test_binary_op_2("zeroes", ZeroedBM, OpStr, Op,
        "pattern", PatternBM, Writer, !IO),
    test_binary_op_2("ones", OnesBM, OpStr, Op,
        "pattern", PatternBM, Writer, !IO),
    test_binary_op_2("pattern", PatternBM, OpStr, Op,
        "ones", OnesBM, Writer, !IO),
    test_binary_op_2("pattern", PatternBM, OpStr, Op,
        "zeroes", ZeroedBM, Writer, !IO),
    test_binary_op_2("pattern", PatternBM, OpStr, Op,
        "alternating", AlternatingBM, Writer, !IO),
    test_binary_op_2("pattern", PatternBM, OpStr, Op,
        "pattern", PatternBM, Writer, !IO),
    test_binary_op_2("alternating", AlternatingBM, OpStr, Op,
        "alternating", AlternatingBM, Writer, !IO).

:- pred test_binary_op_2(string, tbitmap, string, (func(tbitmap, tbitmap) = T),
    string, tbitmap, pred(T, io, io), io, io).
:- mode test_binary_op_2(in, tbitmap_ui,
    in, (func(tbitmap_ui, tbitmap_di) = tbitmap_uo is det),
    in, tbitmap_ui, (pred(in, di, uo) is det), di, uo) is det.

test_binary_op_2(BMStr1, BM1, OpStr, Op, BMStr2, BM2, Writer, !IO) :-
    io.write_string(OpStr, !IO),
    io.write_string("(", !IO),
    io.write_string(BMStr1, !IO),
    io.write_string(", ", !IO),
    io.write_string(BMStr2, !IO),
    io.write_string(") = ", !IO),
    Writer(BM1 `Op` copy(BM2), !IO),
    io.nl(!IO).

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

test_binary_io(!IO) :-
    FileName = "bitmap_test_output",
    BMa0 = bitmap.new(64, yes),
    BMa = BMa0 ^ bits(32, 16) := 0b1011011100100101,
    BMb0 = bitmap.new(47, yes),
    BMb = BMb0 ^ bits(11, 16) := 0b1011010110100101,
    io.open_binary_output(FileName, OpenRes, !IO),
    (
        OpenRes = ok(Stream),
        io.write_bitmap(Stream, BMa, !IO),
        io.write_bitmap(Stream, BMb, 2, 3, !IO),
        io.close_binary_output(Stream, !IO),
        io.open_binary_input(FileName, OpenInputRes, !IO),
        (
            OpenInputRes = ok(IStream),
            InputBMa0 = bitmap.new(64, no),
            io.read_bitmap(IStream, InputBMa0, InputBMa,
                BytesReadA, ReadResA, !IO),
            (
                ReadResA = ok,
                BytesReadA = 8,
                InputBMa = BMa
            ->
                io.write_string("First read succeeded\n", !IO) 
            ;
                io.write_string("First read failed\n", !IO)
            ),
            InputBMb0 = bitmap.new(32, no),
            io.read_bitmap(IStream, InputBMb0, InputBMb1,
                BytesReadB, ReadResB, !IO),

            % Check that we're at eof.
            io.read_bitmap(IStream, InputBMb1, InputBMb,
                BytesReadB2, ReadResB2, !IO),
            (
                ReadResB = ok,
                BytesReadB = 3,
                ReadResB2 = ok,
                BytesReadB2 = 0,
                BMb ^ bits(16, 24) = InputBMb ^ bits(0, 24)
            ->
                io.write_string("Second read succeeded\n", !IO) 
            ;
                io.write_string("Second read failed\n", !IO)
            ),
            io.close_binary_input(IStream, !IO),
            io.remove_file(FileName, _, !IO)
        ;
            OpenInputRes = error(Error),
            throw(Error)
        )
    ;
        OpenRes = error(Error),
        throw(Error)
    ).

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

test_text_io(!IO) :-
    FileName = "bitmap_test_output2",
    BMa0 = bitmap.new(64, yes),
    BMa = BMa0 ^ bits(32, 16) := 0b1011011100100101,
    BMb0 = bitmap.new(47, yes),
    BMb = BMb0 ^ bits(11, 16) := 0b1011010110100101,
    io.write_string("BMa = ", !IO),
    io.write(BMa, !IO),
    io.write_string(".\n", !IO),
    io.write_string("BMb = ", !IO),
    io.write(BMb, !IO),
    io.write_string(".\n", !IO),
    io.open_output(FileName, OpenRes, !IO),
    (
        OpenRes = ok(Stream),

        io.write(Stream, BMa, !IO),
        io.write_string(Stream, ".\n", !IO),
        io.write(Stream, BMb, !IO),
        io.write_string(Stream, ".\n", !IO),
        io.close_output(Stream, !IO),
        io.open_input(FileName, OpenInputRes, !IO),
        (
            OpenInputRes = ok(IStream),
            io.read(IStream, ReadResA, !IO),
            ( ReadResA = ok(BMa) ->
                io.write_string("First read succeeded\n", !IO) 
            ;
                io.write_string("First read failed\n", !IO),
                io.close_input(IStream, !IO),
                throw(ReadResA)
            ),
            io.read(IStream, ReadResB, !IO),
            (
                ReadResB = ok(BMb)
            ->
                io.write_string("Second read succeeded\n", !IO) 
            ;
                io.write_string("Second read failed\n", !IO),
                io.close_input(IStream, !IO),
                throw(ReadResB)
            ),
            io.close_input(IStream, !IO),
            io.remove_file(FileName, _, !IO)
        ;
            OpenInputRes = error(Error),
            throw(Error)
        )
    ;
        OpenRes = error(Error),
        throw(Error)
    ).

    
:- pred fill_in_alternating_pattern(byte::in,
            tbitmap::tbitmap_di, tbitmap::tbitmap_uo) is det.

fill_in_alternating_pattern(Byte, !BM) :-
    NumBits = !.BM ^ fst ^ num_bits,
    NumBytes = NumBits / bits_per_byte,
    fill_in_alternating_pattern(0, NumBytes, Byte, !BM),
    LastByteNumBits = NumBits `rem` bits_per_byte,
    ( LastByteNumBits \= 0 ->
        !:BM = !.BM ^ bits(NumBytes * bits_per_byte, LastByteNumBits) := Byte
    ;
        true
    ).

:- pred fill_in_alternating_pattern(byte_index::in, num_bytes::in, byte::in,
            tbitmap::tbitmap_di, tbitmap::tbitmap_uo) is det.

fill_in_alternating_pattern(Index, NumBytes, Pattern, !BM) :-
    ( Index >= NumBytes ->
        true
    ;
        ( Index rem 2 = 0 ->
            BytePattern = Pattern
        ;    
            BytePattern = \Pattern
        ),
        !:BM = !.BM ^ byte(Index) := BytePattern,
        fill_in_alternating_pattern(Index + 1, NumBytes, Pattern, !BM)
    ).

:- pred write_binary_string(word::in, io::di, io::uo) is det.

write_binary_string(Int, !IO) :-
    io.write_string(binary_string(Int), !IO).

:- func binary_string(word) = string.

binary_string(Int) = string.int_to_base_string(Int, 2).

:- pred write_bitmap_result_error(bitmap_result_error::in,
            io::di, io::uo) is det.

write_bitmap_result_error(query(Op, Input, OtherArgs, Output), !IO) :-
    io.write_string("Error in `", !IO),
    io.write_string(Op, !IO),
    io.write_string("(", !IO),
    io.write(OtherArgs, !IO),
    io.write_string(")'\ninput bitmap: ", !IO),
    io.write_string(to_byte_string(Input), !IO),
    io.nl(!IO),
    io.write_string("output = ", !IO),
    io.write(Output ^ fst, !IO),
    io.nl(!IO),
    io.write_string("expected output = ", !IO),
    io.write(Output ^ snd, !IO),
    io.nl(!IO).

write_bitmap_result_error(binary_query(Op, Input1, Input2, OtherArgs, Output),
        !IO) :-
    io.write_string("Error in `", !IO),
    io.write_string(Op, !IO),
    io.write_string("(", !IO),
    io.write(OtherArgs, !IO),
    io.write_string(")'\ninput bitmap 1: ", !IO),
    io.write_string(to_byte_string(Input1), !IO),
    io.write_string("\ninput bitmap 2: ", !IO),
    io.write_string(to_byte_string(Input2), !IO),
    io.nl(!IO),
    io.write_string("output = ", !IO),
    io.write(Output ^ fst, !IO),
    io.nl(!IO),
    io.write_string("expected output = ", !IO),
    io.write(Output ^ snd, !IO),
    io.nl(!IO).

write_bitmap_result_error(one_argument(Op, Input, OtherArgs, Output), !IO) :-
    io.write_string("Error in `", !IO),
    io.write_string(Op, !IO),
    io.write_string("(", !IO),
    io.write(OtherArgs, !IO),
    io.write_string(")'\ninput bitmap: ", !IO),
    io.write_string(to_byte_string(Input), !IO),
    io.nl(!IO),
    io.write_string("output = ", !IO),
    io.write_string(to_byte_string(Output ^ fst), !IO),
    io.nl(!IO),
    io.write_string("expected output = ", !IO),
    io.write_string(to_byte_string(Output ^ snd), !IO),
    io.nl(!IO).

write_bitmap_result_error(two_arguments(Op, Input1, Input2, OtherArgs, Output),
        !IO) :-
    io.write_string("Error in `", !IO),
    io.write_string(Op, !IO),
    io.write_string("(", !IO),
    io.write(OtherArgs, !IO),
    io.write_string(")'\ninput bitmap 1: ", !IO),
    io.write_string(to_byte_string(Input1), !IO),
    io.nl(!IO),
    io.write_string("\ninput bitmap 2: ", !IO),
    io.write_string(to_byte_string(Input2), !IO),
    io.nl(!IO),
    io.write_string("output = ", !IO),
    io.write_string(to_byte_string(Output ^ fst), !IO),
    io.nl(!IO),
    io.write_string("expected output = ", !IO),
    io.write_string(to_byte_string(Output ^ snd), !IO),
    io.nl(!IO).

