% consult("D:/Cellular Information/Source Code/Prolog/binary.pl").

% -------------------------------------

% ISO Input and Output Streams - http://www.swi-prolog.org/pldoc/man?section=isoIO
% Primitive character I/O - http://www.swi-prolog.org/pldoc/man?section=chario
% SWI-Prolog 2.17.1.4 Syntax for non-decimal numbers - https://www.swi-prolog.org/pldoc/man?section=syntax

% -------------------------------------

:- module(binary,
    [
        get_byte/3,
        get_byte/4,
        get_word/3,
        get_word/4,
        get_word/5,
        get_3_byte/3,
        get_3_byte/4,
        get_3_byte/5,
        get_doubleword/3,
        get_doubleword/4,
        get_doubleword/5,
        get_quadword/3,
        get_quadword/4,
        get_quadword/5,
        get_bytes/3,
        get_bytes/4,
        get_bytes/5,
        peek_16_bytes//1,
        zero_2_bytes//0,
        zero_4_bytes//0,
        zero_8_bytes//0,
        zero_16_bytes//0,
        zero_32_bytes//0,
        zero_64_bytes//0
    ]).

% -------------------------------------

:- use_module(library(clpfd)). % Needed for hexadecimal operators such as <<

% -------------------------------------

get_byte(Stream,Address,Word) :-
    seek(Stream,Address,bof,_New_address),
    get_byte(Stream,Word).
get_byte(Stream,Address0,Address,Byte) :-
    get_byte(Stream,Address0,Byte),
    Address is Address0 + 16'01.

get_word(Stream,Endian,Word) :-
    get_byte(Stream,Byte_0),
    get_byte(Stream,Byte_1),
    (
        Endian == big
    ->
        Word #= (Byte_0 << 8) + Byte_1
    ;
        Word #= (Byte_1 << 8) + Byte_0
    ).
get_word(Stream,Endian,Address,Word) :-
    seek(Stream,Address,bof,_New_address),
    get_word(Stream,Endian,Word).
get_word(Stream,Endian,Address0,Address,Word) :-
    get_word(Stream,Endian,Address0,Word),
    Address is Address0 + 16'02.

get_3_byte(Stream,Endian,Byte_3) :-
    get_byte(Stream,Byte_0),
    get_byte(Stream,Byte_1),
    get_byte(Stream,Byte_2),
    (
        Endian == big
    ->
        Byte_3 #= (Byte_0 << 0x10) + (Byte_1 << 0x08) + Byte_2
    ;
        Byte_3 #= (Byte_2 << 0x10) + (Byte_1 << 0x08) + Byte_0
    ).

get_3_byte(Stream,Endian,Address,Byte_3) :-
    seek(Stream,Address,bof,_New_address),
    get_3_byte(Stream,Endian,Byte_3).
get_3_byte(Stream,Endian,Address0,Address,Byte_3) :-
    get_3_byte(Stream,Endian,Address0,Byte_3),
    Address is Address0 + 16'03.

get_doubleword(Stream,Endian,Doubleword) :-
    get_byte(Stream,Byte_0),
    get_byte(Stream,Byte_1),
    get_byte(Stream,Byte_2),
    get_byte(Stream,Byte_3),
    (
        Endian == big
    ->
        Doubleword #= (Byte_0 << 24) + (Byte_1 << 16) + (Byte_2 << 8) + Byte_3
    ;
        Doubleword #= (Byte_3 << 24) + (Byte_2 << 16) + (Byte_1 << 8) + Byte_0
    ).
get_doubleword(Stream,Endian,Address,Doubleword) :-
    seek(Stream,Address,bof,_New_address),
    get_doubleword(Stream,Endian,Doubleword).
get_doubleword(Stream,Endian,Address0,Address,Doubleword) :-
    get_doubleword(Stream,Endian,Address0,Doubleword),
    Address is Address0 + 16'04.

get_quadword(Stream,Endian,Quadword) :-
    get_byte(Stream,Byte_0),
    get_byte(Stream,Byte_1),
    get_byte(Stream,Byte_2),
    get_byte(Stream,Byte_3),
    get_byte(Stream,Byte_4),
    get_byte(Stream,Byte_5),
    get_byte(Stream,Byte_6),
    get_byte(Stream,Byte_7),
    (
        Endian = big
    ->
        Quadword #= (Byte_0 << 16'38) + (Byte_1 << 16'30) + (Byte_2 << 16'28) + (Byte_3 << 16'20) + (Byte_4 << 16'18) + (Byte_5 << 16'10) + (Byte_6 << 8) + Byte_7
    ;
        Quadword #= (Byte_7 << 16'38) + (Byte_6 << 16'30) + (Byte_5 << 16'28) + (Byte_4 << 16'20) + (Byte_3 << 16'18) + (Byte_2 << 16'10) + (Byte_1 << 8) + Byte_0
    ).
get_quadword(Stream,Endian,Address,Quadword) :-
    seek(Stream,Address,bof,_New_address),
    get_quadword(Stream,Endian,Quadword).
get_quadword(Stream,Endian,Address0,Address,Quadword) :-
    get_quadword(Stream,Endian,Address0,Quadword),
    Address is Address0 + 16'08.

get_bytes(_Stream,0,[]) :- !.
get_bytes(Stream,Size0,[Byte|Bytes]) :-
    get_byte(Stream,Byte),
    Size is Size0 - 1,
    get_bytes(Stream,Size,Bytes).

get_bytes(Stream,Address,Size,Bytes) :-
    seek(Stream,Address,bof,_New_address),
    get_bytes(Stream,Size,Bytes).
get_bytes(Stream,Address0,Address,Size,Bytes) :-
    get_bytes(Stream,Address0,Size,Bytes),
    Address is Address0 + Size.

peek_16_bytes([Byte_00,Byte_01,Byte_02,Byte_03,Byte_04,Byte_05,Byte_06,Byte_07,Byte_08,Byte_09,Byte_0A,Byte_0B,Byte_0C,Byte_0D,Byte_0E,Byte_0F]),
    [Byte_00,Byte_01,Byte_02,Byte_03,Byte_04,Byte_05,Byte_06,Byte_07,Byte_08,Byte_09,Byte_0A,Byte_0B,Byte_0C,Byte_0D,Byte_0E,Byte_0F] -->
    [Byte_00,Byte_01,Byte_02,Byte_03,Byte_04,Byte_05,Byte_06,Byte_07,Byte_08,Byte_09,Byte_0A,Byte_0B,Byte_0C,Byte_0D,Byte_0E,Byte_0F].

zero_2_bytes -->
    [16'00,16'00].

zero_4_bytes -->
    [16'00,16'00,16'00,16'00].

zero_8_bytes -->
    [16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00].

zero_16_bytes -->
    [16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00].

zero_32_bytes -->
    [16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00].

zero_64_bytes -->
    [16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00,16'00].

% -----------------------------------------------------------------------------

:- begin_tests(read).

test(001) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    get_byte(Stream,Byte),
    assertion( Byte == 16'01 ).

test(002) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    get_byte(Stream,16'01,Byte),
    assertion( Byte == 16'02 ).

test(003) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    get_byte(Stream,16'01,Address,Byte),
    assertion( Byte == 16'02 ),
    assertion( Address == 16'02 ).

test(004) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    binary:get_word(Stream,big,Word),
    assertion( Word == 16'0102 ).

test(005) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    binary:get_word(Stream,big,16'02,Word),
    assertion( Word == 16'0304 ).

test(006) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    binary:get_word(Stream,big,16'02,Address,Word),
    assertion( Word == 16'0304 ),
    assertion( Address == 16'04 ).

test(007) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    binary:get_doubleword(Stream,big,Doubleword),
    assertion( Doubleword == 16'01020304 ).

test(008) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    binary:get_doubleword(Stream,big,16'04,Doubleword),
    assertion( Doubleword == 16'05060708 ).

test(009) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    binary:get_doubleword(Stream,big,16'04,Address,Doubleword),
    assertion( Doubleword == 16'05060708 ),
    assertion( Address == 16'08 ).

test(010) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    binary:get_quadword(Stream,big,Quadword),
    assertion( Quadword == 16'0102030405060708 ).

test(011) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    binary:get_quadword(Stream,big,16'08,Quadword),
    assertion( Quadword == 16'090A0B0C0D0E0F10 ).

test(012) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    binary:get_quadword(Stream,big,16'08,Address,Quadword),
    assertion( Quadword == 16'090A0B0C0D0E0F10 ),
    assertion( Address == 16'10 ).

test(013) :-
    Path = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',
    open(Path,read,Stream,[type(binary)]),
    read_stream_to_codes(Stream,Codes),
    close(Stream),
    DCG = peek_16_bytes(Bytes),
    phrase(DCG,Codes,Rest),
    assertion( Rest == [16'01,16'02,16'03,16'04,16'05,16'06,16'07,16'08,16'09,16'0A,16'0B,16'0C,16'0D,16'0E,16'0F,16'10] ),
    assertion( Bytes == [16'01,16'02,16'03,16'04,16'05,16'06,16'07,16'08,16'09,16'0A,16'0B,16'0C,16'0D,16'0E,16'0F,16'10] ).

% To get a zero padded value will probably need to use format_predicate - http://www.swi-prolog.org/pldoc/man?section=format
test(019) :-
    format(string(String),'~16r ~16R',[16'0A,16'0A]),
    assertion( String == "a A" ).

:- end_tests(read).

% -----------------------------------------------------------------------------

% read_file :-
%     Input_file = 'D:/Cellular Information/Reactome/reactome.org/download-data/reactome.graphdb.v68/neostore.propertystore.db.strings',  % Lines ???
%     % Input_file = 'D:/Cellular Information/Source Code/Prolog/binary__test_data__001.dat',

%     % open(Input_file,read,Input),
%     % read_strings,
%     % close(Input),