% -*- mode: Prolog; coding:utf-8 -*-

% Common stuff for test_read.pl, test_write.pl
% (Originally this just had templates, but other common predicates
% have been added)

:- module(test_templates, [scalars1_template/2,
                           repeated1a_template/2,
                           packed1a_template/2,
                           string_values/8,
                           assertion_eq_dict/2,
                           message_file/2,
                           read_message_codes/2,
                           write_message_codes/2,
                           test_write_template/2,
                           print_term_stderr/1,
                           print_term_stderr/2]).

:- encoding(utf8).

:- autoload(library(pprint)).
:- use_module(library(debug), [assertion/1]).
:- autoload(library(readutil), [read_file_to_codes/3]).
:- autoload(library(protobufs)).
:- autoload(library(lists), [nth0/3]).

%! protobufs:my_enum(?Key:atom, ?Value:atom) is semidet.
% Define the my_enum callback.
protobufs:my_enum(Key, Value) :-
    nth0(Value,
         ['E1',
          'Enum2',
          'AnotherEnum'],
         Key).

%! write_message_codes(+LocalPath:atom, +WireCodes:list(int)) is det.
% Write a "wire stream" to a file with =LocalPath= relative to this
% module, into =WireCodes=.
write_message_codes(LocalPath, WireCodes) :-
    message_file(LocalPath, Path),
    open(Path, write, Stream, [encoding(octet),type(binary)]),
    format(Stream, '~s', [WireCodes]),
    close(Stream).

%! read_message_codes(+LocalPath:atom, -WireCodes:list(int)) is det.
% Read a "wire stream" file with =LocalPath= relative to this
% module, into =WireCodes=.
read_message_codes(LocalPath, WireCodes) :-
    message_file(LocalPath, Path),
    read_file_to_codes(Path, WireCodes, [encoding(octet),type(binary)]),
    assertion(ground(WireCodes)). % TODO: not needed

test_write_template(Path, Template) :-
    protobuf_message(Template, WireCodes),
    write_message_codes(Path, WireCodes).

%! message_file(+LocalPath:atom, -Path:atom) is det.
% Given a =LocalPath=, determine its path relative to the current module.
message_file(LocalPath, Path) :-
    source_file(test_templates:message_file(_,_), MyFile),
    file_directory_name(MyFile, MyDir),
    atomic_list_concat([MyDir, LocalPath], /, Path).

%! assertion_eq_dict(+D1:dict, +D2:dict) is det.
% Convenience predicate - does assertion/2 check for the two dicts, then
% repeats field by field. This produces nicer error messages.
assertion_eq_dict(D1, D2) :-
    ( assertion(D1 == D2) -> true ; true ),
    dict_pairs(D1, _, D1pairs),
    dict_pairs(D2, _, D2pairs),
    assertion_eq(D1pairs, D2pairs).

%! assertion_eq(+D1Fields, +D2Fields) is det.
% Helper for assertion_eq_dict/2 - pair-wise comparison of fields.
assertion_eq([], []).
assertion_eq([V|Vs], []) :-
    assertion(V == '*not-exist*'),
    assertion_eq(Vs, []).
assertion_eq([], [V|Vs]) :-
    assertion('*not-exist*' == V),
    assertion_eq([], Vs).
assertion_eq([K1-V1|Xs], [K2-V2|Ys]) :-
    (   K1 @< K2
    ->  assertion(K1-V1 == '*not-exist*'),
        assertion_eq(Xs, [K2-V2|Ys])
    ;   K1 == K2
    ->  assertion(K1-V1 == K2-V2),
        assertion_eq(Xs, Ys)
    ;   assertion('*not-exist*' == K2-V2),
        assertion_eq([K1-V1|Xs], Ys)
    ).

print_term_stderr(Term) :-
    print_term_stderr('', Term).

print_term_stderr(Msg, Term) :-
    format(user_error, '~n~w', [Msg]),
    print_term(Term, [output(user_error)]),
    nl(user_error).

%! string_values(-S1, -S2, -S3, -S4, -C1, -C2, -C3, -C4) is det.
% Get some test strings.
% Note: Windows version of swipl doesn't support > 0xffff, and surrogate pairs are poorly supported.
string_values(S1, S2, S3, S4, C1, C2, C3, C4) :-
    C1 = [0xe9, 0x63, 0x72, 0x61, 0x6e, 0x20, 0x7db2, 0x76ee, 0x9326, 0x86c7], % "écran 網目錦蛇"
    C2 = [0x7db2, 0x76ee, 0x9326, 0x86c7],  % "網目錦蛇"
    C3 = [0x5b, 0xe0, 0x6d, 0xed, 0x6d, 0xe9, 0x20, 0x6e, 0xed, 0x73, 0x68, 0xed, 0x6b, 0xed, 0x68, 0xe9, 0xa71c, 0x62, 0xec, 0x5d, 0x20, 0x72, 0x65, 0x74, 0x69, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x20, 0x70, 0x79, 0x74, 0x68, 0x6f, 0x6e], % [àmímé níshíkíhéꜜbì] reticulated python"
    C4 = [0xe0, 0x6d, 0xed, 0x6d, 0xe9, 0x20, 0x6e, 0xed, 0x73, 0x68, 0xed, 0x6b, 0xed, 0x68, 0xe9, 0xa71c, 0x62, 0xec], % àmímé níshíkíhéꜜbì"
    string_codes(S1, C1),
    string_codes(S2, C2),
    string_codes(S3, C3),
    string_codes(S4, C4).

%! scalars1_template(-Template, -Vars:list) is det.
% A protobufs template and the variables in it.
scalars1_template(Template, Vars) :-
    % See test.Scalars1
    Template = protobuf([
                         double(       1, V_double),
                         float(        2, V_float),
                         signed32(   103, V_int32),
                         signed64(   127, V_int64),
                         unsigned(   128, V_uint32),
                         unsigned(   666, V_uint64),
                         integer(    777, V_sint32),
                         integer(    888, V_sint64),
                         integer32(  999, V_fixed32),
                         integer64( 1010, V_fixed64),
                         integer32( 1011, V_sfixed32),
                         integer64( 1012, V_sfixed64),
                         boolean(   1013, V_bool),
                         string(    1014, V_string),
                         codes(     1015, V_bytes),
                         enum(      1016, my_enum(V_enum)),
                         utf8_codes(1017, V_utf8_codes),
                         embedded(  9999, protobuf([string(15,  V_key),
                                                    string(128, V_value)]))
                        ]),
    Vars = [                             V_double,
                                         V_float,
                                         V_int32,
                                         V_int64,
                                         V_uint32,
                                         V_uint64,
                                         V_sint32,
                                         V_sint64,
                                         V_fixed32,
                                         V_fixed64,
                                         V_sfixed32,
                                         V_sfixed64,
                                         V_bool,
                                         V_string,
                                         V_bytes,
                                         V_enum,
                                         V_utf8_codes,
                                         V_key,
                                         V_value
           ].

repeated1a_template(Template, Vars) :-
    % See test.Scalars1, test_write.py - repeated1a
    Template = protobuf([
                         repeated(    1, double(V_double)),
                         repeated(   12, float(V_float)),
                         repeated( 1103, signed32(V_int32)),
                         repeated( 1127, signed64(V_int64)),
                         repeated( 1128, unsigned(V_uint32)),
                         repeated( 1666, unsigned(V_uint64)),
                         repeated( 1777, integer(V_sint32)),
                         repeated( 1888, integer(V_sint64)),
                         repeated( 1999, unsigned32(V_fixed32)),
                         repeated(11010, unsigned64(V_fixed64)),
                         repeated(11011, integer32(V_sfixed32)),
                         repeated(11012, integer64(V_sfixed64)),
                         repeated(11013, boolean(V_bool)),
                         repeated(11014, string(V_string)),
                         repeated(11015, codes(V_bytes)),
                         repeated(11016, enum(my_enum(V_enum))),
                         repeated(11017, utf8_codes(V_utf8_codes)),
                         repeated_embedded(99999,
                                           protobuf([string(15, _Key),
                                                     string(128, _Value)]),
                                           V_key_values)
                        ]),

    Vars = [                             V_double,
                                         V_float,
                                         V_int32,
                                         V_int64,
                                         V_uint32,
                                         V_uint64,
                                         V_sint32,
                                         V_sint64,
                                         V_fixed32,
                                         V_fixed64,
                                         V_sfixed32,
                                         V_sfixed64,
                                         V_bool,
                                         V_string,
                                         V_bytes,
                                         V_enum,
                                         V_utf8_codes,
                                         V_key_values
           ].

packed1a_template(Template, Vars) :-
    % repeated1a_template/2, using packed where possible
    Template = protobuf([
                         packed(      1, double(V_double)),
                         packed(     12, float(V_float)),
                         packed(   1103, signed32(V_int32)),
                         packed(   1127, signed64(V_int64)),
                         packed(   1128, unsigned(V_uint32)),
                         packed(   1666, unsigned(V_uint64)),
                         packed(   1777, integer(V_sint32)),
                         packed(   1888, integer(V_sint64)),
                         packed(   1999, unsigned32(V_fixed32)),
                         packed(  11010, unsigned64(V_fixed64)),
                         packed(  11011, integer32(V_sfixed32)),
                         packed(  11012, integer64(V_sfixed64)),
                         packed(  11013, boolean(V_bool)),
                         repeated(11014, string(V_string)),
                         repeated(11015, codes(V_bytes)),
                         packed(  11016, enum(my_enum(V_enum))),
                         repeated(11017, utf8_codes(V_utf8_codes)),
                         repeated_embedded(99999,
                                           protobuf([string(15, _Key),
                                                     string(128, _Value)]),
                                           V_key_values)
                        ]),

    Vars = [                           V_double,
                                       V_float,
                                       V_int32,
                                       V_int64,
                                       V_uint32,
                                       V_uint64,
                                       V_sint32,
                                       V_sint64,
                                       V_fixed32,
                                       V_fixed64,
                                       V_sfixed32,
                                       V_sfixed64,
                                       V_bool,
                                       V_string,
                                       V_bytes,
                                       V_enum,
                                       V_utf8_codes,
                                       V_key_values
           ].

