% -*- mode: Prolog -*-

% TODO: delete this file when all the logic has been moved to protoc-gen-swipl
%       and everything has been completely bootstrapped.
%
% This code has been copied to protoc-gen-swipl, and has not been kept up-to-date
% with subsequent changes. It is mainly of historical interest, but *might* be
% needed if there's a signficant change to how protoc outputs its data (highly
% unlikely, given the amount of change that would require with other plugins).

%% Term expansion for descriptor_proto.pl
%% (which is used by descriptor_proto.pl to expand a
%% descriptor_proto/1 fact -- see the documentation of
%% descriptor_proto.pl for how this term is created.)

:- module(descriptor_proto_expand, [descriptor_proto_expand_FileDescriptorSet/2,
                                    descriptor_proto_expand_FileDescriptorSet//1,
                                    descriptor_proto_expand_FileDescriptorSet_preds/1]).

:- use_module(library(dcg/basics)).
:- use_module(library(dcg/high_order)).
:- use_module(library(debug), [assertion/1]).

:- det(descriptor_proto_expand_FileDescriptorSet_preds/1).
descriptor_proto_expand_FileDescriptorSet_preds(Preds) :-
    Preds = [
     protobufs:proto_meta_normalize/2,               %   protobufs:proto_meta_package(Unnormalized, Normalized)
     protobufs:proto_meta_package/3,                 %   protobufs:proto_meta_package(Package, FileName, Options)
     protobufs:proto_meta_message_type/3,            %   protobufs:proto_meta_message_type(       Fqn,     Package, Name)
     protobufs:proto_meta_field_name/4,              %   protobufs:proto_meta_field_name(         Fqn,     FieldNumber, FieldName, FqnName),
     protobufs:proto_meta_field_json_name/2,         %   protobufs:proto_meta_field_json_name(    FqnName, JsonName)
     protobufs:proto_meta_field_label/2,             %   protobufs:proto_meta_field_label(        FqnName, LabelRepeatOptional) % LABEL_OPTIONAL, LABEL_REQUIRED, LABEL_REPEATED
     protobufs:proto_meta_field_type/2,              %   protobufs:proto_meta_field_type(         FqnName, Type) % TYPE_INT32, TYPE_MESSAGE, etc
     protobufs:proto_meta_field_type_name/2,         %   protobufs:proto_meta_field_type_name(    FqnName, TypeName)
     protobufs:proto_meta_field_default_value/2,     %   protobufs:proto_meta_field_default_value(FqnName, DefaultValue)
     protobufs:proto_meta_field_option_packed/1,     %   protobufs:proto_meta_field_option_packed(FqnName)
     protobufs:proto_meta_enum_type/3,               %   protobufs:proto_meta_enum_type(          FqnName, Fqn, Name)
     protobufs:proto_meta_enum_value/3               %   protobufs:proto_meta_enum_value(         FqnName, Name, Number)
            ].

:- det(descriptor_proto_expand_FileDescriptorSet/2).
descriptor_proto_expand_FileDescriptorSet(Set,
                                          [(:- discontiguous CommaPreds) | Expansion]) :-
    phrase(descriptor_proto_expand_FileDescriptorSet(Set), Expansion),
    (   false  % for debugging
    ->  writeln('***expansion'),
        print_term(Expansion, [right_margin(160)]),
        nl, writeln('***end expansion'), nl
    ;   true
    ),
    descriptor_proto_expand_FileDescriptorSet_preds(Preds),
    list_commalist(Preds, CommaPreds).

:- det(list_commalist/2).
list_commalist([Pred], Pred) :- !.
list_commalist([Pred|Preds], (Pred,CommaList)) :-
    list_commalist(Preds, CommaList).

:- det(lookup_pieces/3).
%! lookup_pieces(+Tag, +DataDict, ?LookupDict) is det.
% Given a =DataDict=, look up the items in =LookupDict= If =DataDict=
% contains any keys that aren't in =LookupDict=, this predicate
% fails. This is to catch typos. For example: =|lookup_pieces(d,
% d{a:1,b:2}, _{a:0-A,bb:0-B,c:[]-C})|= will fail but
% =|lookup_pieces(d, d{a:1,b:2}, _{a:0-A,b:0-B,c:[]-C})|= will succeed
% with =|A=1,B=2,C=[]|=. In other words, =LookupDict= must contain all
% the possible keys in =DataDict= (with suitable defaults, of course).
% @param Tag the tag for =DataDict=
% @param DataDict items in =LookupDict= are looked up in here.
%        Its tag must unify with =Tag= (i.e., =|is_dict(DataDict,Tag)|=).
% @param LookupDict a dict where each entry is of the form =Default-Value=.
%        Each key is looked up in =DataDict= - if it's there, the value
%        from =DataDict= is unified with =Value=; if it's not there,
%        =Value= is unified with =Default=.
lookup_pieces(Tag, DataDict, LookupDict) :-
    is_dict(DataDict, Tag),
    dict_pairs(LookupDict, _, LookupPairs),
    lookup_piece_pairs(LookupPairs, DataDict).

lookup_piece_pairs([], RemainderDict) =>
    RemainderDict = _{}. % For debugging: assertion(RemainderDict = _{})
lookup_piece_pairs([Key-(Default-Value)|KDVs], DataDict0) =>
    dict_create(D0, _, [Key-Value]),
    (   select_dict(D0, DataDict0, DataDict)
    ->  true
    ;   Value = Default,
        DataDict = DataDict0
    ),
    lookup_piece_pairs(KDVs, DataDict).

:- det(descriptor_proto_expand_FileDescriptorSet//1).
descriptor_proto_expand_FileDescriptorSet(Set) -->
    { lookup_pieces('FileDescriptorSet', Set,
                    _{
                      file: []-File
                     }) },
    sequence(descriptor_proto_expand_FileDescriptorProto, File).

:- det(descriptor_proto_expand_FileDescriptorProto//1).
descriptor_proto_expand_FileDescriptorProto(File) -->
    { lookup_pieces('FileDescriptorProto', File,
                    _{
                      name:              ''              -File_name,
                      package:           ''              -File_package,
                      dependency:        []              -_,
                      public_dependency: []              -_,
                      weak_dependency:   []              -_,
                      message_type:      []              -File_message_type,
                      enum_type:         []              -File_enum_type,
                      service:           []               -_,
                      extension:         []              -File_extension,
                      options:           'FileOptions'{} -File_options,
                      source_code_info:  _               -_,
                      syntax:            ''              -_
                     }) },
    % TODO: The following is a quick hack - see protoc-gen-swipl for the correct
    %       way to define proto_meta_normalize/2.
    %       No need to fix this if we do a full bootstrap.
    [ (protobufs:proto_meta_normalize(X, X) :- !) ],
    { assertion(File_extension == []) }, % TODO: handle this?
    { add_to_fqn('', File_package, Package) },
    [ protobufs:proto_meta_package(Package, File_name, File_options) ],
    sequence(expand_DescriptorProto(Package), File_message_type),
    sequence(expand_EnumDescriptorProto(Package), File_enum_type).

:- det(expand_DescriptorProto//2).
expand_DescriptorProto(Fqn, MessageType) -->
    { lookup_pieces('DescriptorProto', MessageType,
                    _{
                      name:            ''      -MessageType_name,
                      field:           []      -MessageType_field,
                      extension:       []      -_,
                      nested_type:     []      -MessageType_nested_type,
                      enum_type:       []      -MessageType_enum_type,
                      extension_range: []      -_,
                      oneof_decl:      []      -_,
                      options:         []      -_,
                      reserved_range:  []      -_,
                      reserved_name:   []      -_
                     }) },
    { add_to_fqn(Fqn, MessageType_name, FqnName) },
    [ protobufs:proto_meta_message_type(FqnName, Fqn, MessageType_name) ],
    sequence(expand_FieldDescriptorProto(FqnName), MessageType_field),
    sequence(expand_DescriptorProto(FqnName), MessageType_nested_type),
    sequence(expand_EnumDescriptorProto(FqnName), MessageType_enum_type).

:- det(expand_FieldDescriptorProto//2).
expand_FieldDescriptorProto(Fqn, Field) -->
    { lookup_pieces('FieldDescriptorProto', Field,
                    _{
                      name:            ''               -Field_name,
                      number:          0                -Field_number,
                      label:           0                -Field_label,  % enum Label
                      type:            0                -Field_type,  % enum Type
                      type_name:       ''               -Field_type_name,
                      extendee:        _                -_,
                      default_value:   ''               -Field_default_value,
                      oneof_index:     _                -_,
                      json_name:       ''               -Field_json_name,
                      options:         'FieldOptions'{} -Field_options,
                      proto3_optional: _                -_
                     }) },
    { add_to_fqn(Fqn, Field_name, FqnName) },
    [ protobufs:proto_meta_field_name(Fqn, Field_number, Field_name, FqnName) ],
    [ protobufs:proto_meta_field_json_name(FqnName, Field_json_name) ],
    [ protobufs:proto_meta_field_label(FqnName, Field_label) ],
    [ protobufs:proto_meta_field_type(FqnName, Field_type) ],
    [ protobufs:proto_meta_field_type_name(FqnName, Field_type_name) ],
    [ protobufs:proto_meta_field_default_value(FqnName, Field_default_value) ],
    expand_FieldOptions(FqnName, Field_options).

:- det(expand_FieldOptions//2).
expand_FieldOptions(FqnName, Options) -->
    { lookup_pieces('FieldOptions', Options,
                    _{
                      ctype:                _     -_,
                      packed:               false -Option_packed,
                      jstype:               _     -_,
                      lazy:                 false -_,
                      deprecated:           false -_, % TODO: output warning if a deprecated field is used
                      weak:                 false -_,
                      uninterpreted_option: _     -_
                     }) },
    (   { Option_packed = true }
    ->  [ protobufs:proto_meta_field_option_packed(FqnName) ]
    ;   [ ]
    ).

:- det(expand_EnumDescriptorProto//2).
expand_EnumDescriptorProto(Fqn, EnumType) -->
    { lookup_pieces('EnumDescriptorProto', EnumType,
                    _{
                      name:           '' -EnumType_name,
                      value:          [] -EnumType_value,
                      options:        _  -_,
                      reserved_range: _  -_,
                      reserved_name:  _  -_
                      }) },
    { add_to_fqn(Fqn, EnumType_name, FqnName) },
    [ protobufs:proto_meta_enum_type(FqnName, Fqn, EnumType_name) ],
    sequence(expand_EnumValueDescriptorProto(FqnName), EnumType_value).

:- det(expand_EnumValueDescriptorProto//2).
expand_EnumValueDescriptorProto(Fqn, Value) -->
    { lookup_pieces('EnumValueDescriptorProto', Value,
                    _{
                      name:    ''-Value_name,
                      number:  0-Value_number,
                      options: _-_
                      }) },
    [ protobufs:proto_meta_enum_value(Fqn, Value_name, Value_number) ].

add_to_fqn(Fqn, Name, FqnName) :-
    atomic_list_concat([Fqn, Name], '.', FqnName).

end_of_file.
