%module{Google::ProtocolBuffers::Dynamic};

#include "upbwrapper.h"

%typemap{const Google::ProtocolBuffers::Dynamic::MessageDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::FieldDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::OneofDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::EnumDef *}{simple}{
    %xs_type{O_OBJECT};
};

%name{Google::ProtocolBuffers::Dynamic::MessageDef} class upb::MessageDef {
    const char *full_name() const;

    int field_count() const;
    int oneof_count() const;

    %name{find_field_by_number} const Google::ProtocolBuffers::Dynamic::FieldDef *FindFieldByNumber(uint32_t number) const;
    %name{find_field_by_name} const Google::ProtocolBuffers::Dynamic::FieldDef *FindFieldByName(const char *name) const;
    %name{find_oneof_by_name} const Google::ProtocolBuffers::Dynamic::OneofDef *FindOneofByName(const char *name) const;

    SV* fields() const %code{%
        AV *fields = newAV();

        for (upb::MessageDef::field_iterator it = THIS->field_begin(), en = THIS->field_end(); it != en; ++it) {
            SV *field = newSV(0);

            sv_setref_iv(field, "Google::ProtocolBuffers::Dynamic::FieldDef", (IV) *it);
            av_push(fields, field);
        }

        RETVAL = newRV_noinc((SV*) fields);
    %};

    SV* oneofs() const %code{%
        AV *oneofs = newAV();

        for (upb::MessageDef::oneof_iterator it = THIS->oneof_begin(), en = THIS->oneof_end(); it != en; ++it) {
            SV *field = newSV(0);

            sv_setref_iv(field, "Google::ProtocolBuffers::Dynamic::OneofDef", (IV) *it);
            av_push(oneofs, field);
        }

        RETVAL = newRV_noinc((SV*) oneofs);
    %};

    %name{is_map_entry} bool mapentry() const;
};

%name{Google::ProtocolBuffers::Dynamic::FieldDef} class upb::FieldDef {
    const char *name() const;
    const char *full_name() const;
    uint32_t number() const;
    bool is_extension() const;
    int label();

    %name{is_packed} bool packed() const;
    %name{is_message} bool IsSubMessage() const;
    %name{is_string} bool IsString() const;
    %name{is_repeated} bool IsSequence() const;
    %name{is_primitive} bool IsPrimitive() const %code{%
        RETVAL = upb_fielddef_isprimitive(THIS);
    %};
    %name{is_map} bool IsMap() const;

    // TODO JSON name

    int descriptor_type() const;
    %name{value_type} int type() const;

    SV *default_value() const %code{%
        switch (THIS->type()) {
        case UPB_TYPE_FLOAT:
            RETVAL = newSVnv(THIS->default_float());
            break;
        case UPB_TYPE_DOUBLE:
            RETVAL = newSVnv(THIS->default_double());
            break;
        case UPB_TYPE_BOOL:
            RETVAL = THIS->default_bool() ? &PL_sv_yes : &PL_sv_no;
            break;
        case UPB_TYPE_STRING:
        case UPB_TYPE_BYTES: {
             size_t length;
             const char *bytes = THIS->default_string(&length);

             RETVAL = newSVpv(bytes, length);
             if (THIS->type() == UPB_TYPE_STRING)
                SvUTF8_on(RETVAL);
        }
            break;
        case UPB_TYPE_MESSAGE:
            RETVAL = &PL_sv_undef;
            break;
        case UPB_TYPE_ENUM:
        case UPB_TYPE_INT32:
            RETVAL = newSViv(THIS->default_int32());
            break;
        case UPB_TYPE_INT64:
            RETVAL = newSViv(THIS->default_int64());
            break;
        case UPB_TYPE_UINT32:
            RETVAL = newSVuv(THIS->default_uint32());
            break;
        case UPB_TYPE_UINT64:
            RETVAL = newSVuv(THIS->default_uint64());
            break;
        }
    %};

    const Google::ProtocolBuffers::Dynamic::MessageDef *containing_type() const;
    const Google::ProtocolBuffers::Dynamic::OneofDef *containing_oneof() const;

    %name{enum_type} const Google::ProtocolBuffers::Dynamic::EnumDef *enum_subdef() const;
    %name{message_type} const Google::ProtocolBuffers::Dynamic::MessageDef *message_subdef() const;
};

%name{Google::ProtocolBuffers::Dynamic::OneofDef} class upb::OneofDef {
    const char *name() const;

    int field_count();

    %name{find_field_by_number} const Google::ProtocolBuffers::Dynamic::FieldDef *FindFieldByNumber(uint32_t number) const;
    %name{find_field_by_name} const Google::ProtocolBuffers::Dynamic::FieldDef *FindFieldByName(const char *name) const %code%{
        RETVAL = ((const upb::OneofDef *) THIS)->FindFieldByName(name);
    %};

    SV* fields() const %code{%
        AV *fields = newAV();

        for (upb::OneofDef::iterator it = THIS->begin(), en = THIS->end(); it != en; ++it) {
            SV *field = newSV(0);

            sv_setref_iv(field, "Google::ProtocolBuffers::Dynamic::FieldDef", (IV) *it);
            av_push(fields, field);
        }

        RETVAL = newRV_noinc((SV*) fields);
    %};

    const Google::ProtocolBuffers::Dynamic::MessageDef *containing_type() const;
};

%name{Google::ProtocolBuffers::Dynamic::EnumDef} class upb::EnumDef {
    const char *full_name() const;
    int32_t default_value() const;

    int value_count() const;

    SV *find_number_by_name(const char *name) %code{%
       int32_t number;
       bool found = THIS->FindValueByName(name, &number);

       RETVAL = found ? newSViv(number) : &PL_sv_undef;
    %};

    SV *find_name_by_number(int32_t number) %code{%
       const char *name = THIS->FindValueByNumber(number);

       RETVAL = name ? newSVpv(name, 0) : &PL_sv_undef;
    %};

    SV *values() const %code{%
        HV *values = newHV();
        upb_enum_iter it;

        for (upb_enum_begin(&it, THIS); !upb_enum_done(&it); upb_enum_next(&it)) {
            const char *name = upb_enum_iter_name(&it);
            SV **value = hv_fetch(values, name, strlen(name), 1);

            sv_setiv(*value, upb_enum_iter_number(&it));
        }

        RETVAL = newRV_noinc((SV *) values);
    %};
};

%{

#define DESCRIPTOR_TYPE(name) \
    gpd::define_constant(aTHX_ "DESCRIPTOR_" #name, "descriptor", UPB_DESCRIPTOR_TYPE_##name);
#define VALUE_TYPE(name) \
    gpd::define_constant(aTHX_ "VALUE_" #name, "values", UPB_TYPE_##name);
#define LABEL_TYPE(name) \
    gpd::define_constant(aTHX_ "LABEL_" #name, "labels", UPB_LABEL_##name);

BOOT:
    LABEL_TYPE(OPTIONAL);
    LABEL_TYPE(REPEATED);
    LABEL_TYPE(REQUIRED);

    DESCRIPTOR_TYPE(DOUBLE);
    DESCRIPTOR_TYPE(FLOAT);
    DESCRIPTOR_TYPE(INT64);
    DESCRIPTOR_TYPE(UINT64);
    DESCRIPTOR_TYPE(INT32);
    DESCRIPTOR_TYPE(FIXED64);
    DESCRIPTOR_TYPE(FIXED32);
    DESCRIPTOR_TYPE(BOOL);
    DESCRIPTOR_TYPE(STRING);
    DESCRIPTOR_TYPE(GROUP);
    DESCRIPTOR_TYPE(MESSAGE);
    DESCRIPTOR_TYPE(BYTES);
    DESCRIPTOR_TYPE(UINT32);
    DESCRIPTOR_TYPE(ENUM);
    DESCRIPTOR_TYPE(SFIXED32);
    DESCRIPTOR_TYPE(SFIXED64);
    DESCRIPTOR_TYPE(SINT32);
    DESCRIPTOR_TYPE(SINT64);

    VALUE_TYPE(FLOAT);
    VALUE_TYPE(DOUBLE);
    VALUE_TYPE(BOOL);
    VALUE_TYPE(STRING);
    VALUE_TYPE(BYTES);
    VALUE_TYPE(MESSAGE);
    VALUE_TYPE(ENUM);
    VALUE_TYPE(INT32);
    VALUE_TYPE(UINT32);
    VALUE_TYPE(INT64);
    VALUE_TYPE(UINT64);

%}