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

#include "perl_unpollute.h"
#include "introspection.h"

#include <google/protobuf/descriptor.h>
#include <google/protobuf/descriptor.pb.h>

%typemap{const Google::ProtocolBuffers::Dynamic::MessageDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::MessageOptionsDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::FieldDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::FieldOptionsDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::OneofDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::OneofOptionsDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::EnumDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::EnumOptionsDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::ServiceDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::ServiceOptionsDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::MethodDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::MethodOptionsDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::FileDef *}{simple}{
    %xs_type{O_OBJECT};
};
%typemap{const Google::ProtocolBuffers::Dynamic::FileOptionsDef *}{simple}{
    %xs_type{O_OBJECT};
};

%{

#define GPD_INTROSPECTION_RETURN_ARRAY(descriptor_field, package)   \
        int count = THIS->descriptor_field ## _count();         \
        AV *array = newAV();                                    \
                                                                \
        av_extend(array, count);                                \
        for (int i = 0; i < count; ++i) {                       \
            SV *item = newSV(0);                                \
                                                                \
            sv_setref_iv(                                       \
                item,                                           \
                "Google::ProtocolBuffers::Dynamic::" #package,  \
                (IV) THIS->descriptor_field(i)                  \
            );;                                                 \
            av_push(array, item);                               \
        }                                                       \
                                                                \
        RETVAL = newRV_noinc((SV*) array);

%}

%name{Google::ProtocolBuffers::Dynamic::DescriptorOptionsDef} class gpd::intr::DescriptorOptionsWrapper {
    ~DescriptorOptionsWrapper();

    SV *custom_option_by_name(const std::string &name);
    SV *custom_option_by_number(int numbere);

    SV *AUTOLOAD() %code%{
        if (!THIS->get_attribute(cv, &RETVAL)) {
            croak("Unknown option '%.*s'", SvCUR(cv), SvPVX(cv));
        }
    %};
};

%name{Google::ProtocolBuffers::Dynamic::MessageDef} class google::protobuf::Descriptor {
    std::string name();
    std::string full_name() const;

    int field_count() const;
    %name{oneof_count} int oneof_decl_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 std::string &name) const;
    %name{find_oneof_by_name} const Google::ProtocolBuffers::Dynamic::OneofDef *FindOneofByName(const std::string &name) const;

    SV* fields() const %code{%
        GPD_INTROSPECTION_RETURN_ARRAY(field, FieldDef);
    %};

    SV* oneofs() const %code{%
        GPD_INTROSPECTION_RETURN_ARRAY(oneof_decl, OneofDef);
    %};

    bool is_map_entry() const %code{%
        RETVAL = THIS->options().map_entry();
    %};

    const Google::ProtocolBuffers::Dynamic::FileDef *file() const;

    const Google::ProtocolBuffers::Dynamic::MessageOptionsDef *options() const %code%{
        RETVAL = gpd::intr::options_make_wrapper<gpd::intr::MessageOptionsWrapper>(aTHX_ THIS);
    %};
};

%name{Google::ProtocolBuffers::Dynamic::MessageOptionsDef} class gpd::intr::MessageOptionsWrapper : public %name{Google::ProtocolBuffers::Dynamic::DescriptorOptionsDef} gpd::intr::DescriptorOptionsWrapper {
    bool deprecated();
};

%name{Google::ProtocolBuffers::Dynamic::FieldDef} class google::protobuf::FieldDescriptor {
    std::string name() const;
    std::string full_name() const;

    uint32_t number() const;
    bool is_extension() const;
    int label();

    %name{is_packed} bool is_packed() const;
    bool is_message() const %code%{
        RETVAL = THIS->cpp_type() == google::protobuf::FieldDescriptor::CppType::CPPTYPE_MESSAGE;
    %};
    bool is_string() const %code%{
        RETVAL = THIS->cpp_type() == google::protobuf::FieldDescriptor::CppType::CPPTYPE_STRING;
    %};
    bool is_repeated() const;
    bool is_primitive() const %code{%
        RETVAL = gpd::intr::field_is_primitive(THIS);
    %};

    bool is_map() const;

    // TODO JSON name

    %name{descriptor_type} int type() const;
    int value_type() const %code{%
        RETVAL = gpd::intr::field_value_type(THIS);
    %};

    SV *default_value() const %code{%
        RETVAL = gpd::intr::field_default_value(aTHX_ THIS);
    %};

    const Google::ProtocolBuffers::Dynamic::MessageDef *containing_type() const;
    const Google::ProtocolBuffers::Dynamic::OneofDef *containing_oneof() const;
#if GOOGLE_PROTOBUF_VERSION >= 3012000
    const Google::ProtocolBuffers::Dynamic::OneofDef *real_containing_oneof() const;
#else
    const Google::ProtocolBuffers::Dynamic::OneofDef *real_containing_oneof() const %code{% PERL_UNUSED_VAR(THIS); RETVAL = NULL; %};
#endif

    const Google::ProtocolBuffers::Dynamic::EnumDef *enum_type() const;
    const Google::ProtocolBuffers::Dynamic::MessageDef *message_type() const;

#if GOOGLE_PROTOBUF_VERSION >= 3012000
    bool has_presence() const;
#else
    bool has_presence() const %code{%
       RETVAL =
           !THIS->is_repeated() && (
               THIS->cpp_type() == google::protobuf::FieldDescriptor::CppType::CPPTYPE_MESSAGE ||
               THIS->containing_oneof() ||
               THIS->file()->syntax() == google::protobuf::FileDescriptor::SYNTAX_PROTO2
           );
    %};
#endif

    const Google::ProtocolBuffers::Dynamic::FileDef *file() const;

    const Google::ProtocolBuffers::Dynamic::FieldOptionsDef *options() const %code%{
        RETVAL = gpd::intr::options_make_wrapper<gpd::intr::FieldOptionsWrapper>(aTHX_ THIS);
    %};
};

%name{Google::ProtocolBuffers::Dynamic::FieldOptionsDef} class gpd::intr::FieldOptionsWrapper : public %name{Google::ProtocolBuffers::Dynamic::DescriptorOptionsDef} gpd::intr::DescriptorOptionsWrapper {
    bool deprecated();
};

%name{Google::ProtocolBuffers::Dynamic::OneofDef} class google::protobuf::OneofDescriptor {
    std::string name() const;
    std::string full_name() const;

    int field_count();

    const Google::ProtocolBuffers::Dynamic::FieldDef *find_field_by_number(int number) const %code{%
        RETVAL = gpd::intr::oneof_find_field_by_number(THIS, number);
    %};

    const Google::ProtocolBuffers::Dynamic::FieldDef *find_field_by_name(std::string name) const %code{%
        RETVAL = gpd::intr::oneof_find_field_by_name(THIS, name);
    %};

    SV* fields() const %code{%
        GPD_INTROSPECTION_RETURN_ARRAY(field, FieldDef);
    %};

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

#if GOOGLE_PROTOBUF_VERSION >= 3012000
    bool is_synthetic() const;
#else
    bool is_synthetic() const %code{% PERL_UNUSED_VAR(THIS); RETVAL = false; %};
#endif

    const Google::ProtocolBuffers::Dynamic::FileDef *file() const;

    const Google::ProtocolBuffers::Dynamic::OneofOptionsDef *options() const %code%{
        RETVAL = gpd::intr::options_make_wrapper<gpd::intr::OneofOptionsWrapper>(aTHX_ THIS);
    %};
};

%name{Google::ProtocolBuffers::Dynamic::OneofOptionsDef} class gpd::intr::OneofOptionsWrapper : public %name{Google::ProtocolBuffers::Dynamic::DescriptorOptionsDef} gpd::intr::DescriptorOptionsWrapper {
};

%name{Google::ProtocolBuffers::Dynamic::EnumDef} class google::protobuf::EnumDescriptor {
    std::string name();
    std::string full_name() const;

    int default_value() const %code%{
        RETVAL = gpd::intr::enum_default_value(THIS);
    %};
    int value_count() const;

    SV *find_number_by_name(std::string name) %code{%
       const google::protobuf::EnumValueDescriptor *value_descriptor = THIS->FindValueByName(name);

       RETVAL = value_descriptor ? newSViv(value_descriptor->number()) : &PL_sv_undef;
    %};

    SV *find_name_by_number(int number) %code{%
       const google::protobuf::EnumValueDescriptor *value_descriptor = THIS->FindValueByNumber(number);

       if (value_descriptor) {
           const std::string &name = value_descriptor->name();

           RETVAL = newSVpvn(name.data(), name.size());
       } else {
           RETVAL = &PL_sv_undef;
       }
    %};

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

        for (int i = 0, max = THIS->value_count(); i < max; ++i) {
            const google::protobuf::EnumValueDescriptor *value_descriptor = THIS->value(i);
            const std::string &name = value_descriptor->name();
            SV **value = hv_fetch(values, name.data(), name.size(), 1);

            sv_setiv(*value, value_descriptor->number());
        }

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

    const Google::ProtocolBuffers::Dynamic::FileDef *file() const;

    const Google::ProtocolBuffers::Dynamic::EnumOptionsDef *options() const %code%{
        RETVAL = gpd::intr::options_make_wrapper<gpd::intr::EnumOptionsWrapper>(aTHX_ THIS);
    %};
};

%name{Google::ProtocolBuffers::Dynamic::EnumOptionsDef} class gpd::intr::EnumOptionsWrapper : public %name{Google::ProtocolBuffers::Dynamic::DescriptorOptionsDef} gpd::intr::DescriptorOptionsWrapper {
    bool deprecated();
};

%name{Google::ProtocolBuffers::Dynamic::ServiceDef} class google::protobuf::ServiceDescriptor {
    std::string name();
    std::string full_name();

    SV* methods() const %code{%
        GPD_INTROSPECTION_RETURN_ARRAY(method, MethodDef);
    %};

    const Google::ProtocolBuffers::Dynamic::FileDef *file() const;

    const Google::ProtocolBuffers::Dynamic::ServiceOptionsDef *options() const %code%{
        RETVAL = gpd::intr::options_make_wrapper<gpd::intr::ServiceOptionsWrapper>(aTHX_ THIS);
    %};
};

%name{Google::ProtocolBuffers::Dynamic::ServiceOptionsDef} class gpd::intr::ServiceOptionsWrapper : public %name{Google::ProtocolBuffers::Dynamic::DescriptorOptionsDef} gpd::intr::DescriptorOptionsWrapper {
    bool deprecated();
};

%name{Google::ProtocolBuffers::Dynamic::MethodDef} class google::protobuf::MethodDescriptor {
    std::string name();
    std::string full_name();

    %name{containing_service} const Google::ProtocolBuffers::Dynamic::ServiceDef *service();
    const Google::ProtocolBuffers::Dynamic::MessageDef *input_type();
    const Google::ProtocolBuffers::Dynamic::MessageDef *output_type();

    bool client_streaming();
    bool server_streaming();

    const Google::ProtocolBuffers::Dynamic::FileDef *file() const;

    const Google::ProtocolBuffers::Dynamic::MethodOptionsDef *options() const %code%{
        RETVAL = gpd::intr::options_make_wrapper<gpd::intr::MethodOptionsWrapper>(aTHX_ THIS);
    %};
};

%name{Google::ProtocolBuffers::Dynamic::MethodOptionsDef} class gpd::intr::MethodOptionsWrapper : public %name{Google::ProtocolBuffers::Dynamic::DescriptorOptionsDef} gpd::intr::DescriptorOptionsWrapper {
    bool deprecated();
};

%name{Google::ProtocolBuffers::Dynamic::FileDef} class google::protobuf::FileDescriptor {
    std::string name();

    std::string package();

    SV* dependencies() const %code{%
        GPD_INTROSPECTION_RETURN_ARRAY(dependency, FileDef);
    %};

    SV* public_dependencies() const %code{%
        GPD_INTROSPECTION_RETURN_ARRAY(public_dependency, FileDef);
    %};

    SV* messages() const %code{%
        GPD_INTROSPECTION_RETURN_ARRAY(message_type, MessageDef);
    %};

    SV* enums() const %code{%
        GPD_INTROSPECTION_RETURN_ARRAY(enum_type, EnumDef);
    %};

    SV* services() const %code{%
        GPD_INTROSPECTION_RETURN_ARRAY(service, ServiceDef);
    %};

    SV* extensions() const %code{%
        GPD_INTROSPECTION_RETURN_ARRAY(extension, FieldDef);
    %};

    const Google::ProtocolBuffers::Dynamic::FileOptionsDef *options() const %code%{
        google::protobuf::DynamicMessageFactory *factory;
        google::protobuf::Message *options_dyn;

        RETVAL = NULL;
        if (gpd::intr::options_make_wrapper(THIS->pool(), THIS->options(), &factory, &options_dyn)) {
            RETVAL = new gpd::intr::FileOptionsWrapper(aTHX_ factory, options_dyn, &THIS->options());
        }
    %};
};

%name{Google::ProtocolBuffers::Dynamic::FileOptionsDef} class gpd::intr::FileOptionsWrapper : public %name{Google::ProtocolBuffers::Dynamic::DescriptorOptionsDef} gpd::intr::DescriptorOptionsWrapper {
    bool deprecated();
};

%package{Google::ProtocolBuffers::Dynamic::Mapper};

%{

#undef GPD_INTROSPECTION_RETURN_ARRAY

const Google::ProtocolBuffers::Dynamic::MessageDef*
message_descriptor(SV *)
  INIT:
    google::protobuf::Descriptor *descriptor = (google::protobuf::Descriptor *) CvXSUBANY(cv).any_ptr;
  CODE:
    RETVAL = descriptor;
  OUTPUT: RETVAL

const Google::ProtocolBuffers::Dynamic::EnumDef*
enum_descriptor(SV *)
  INIT:
    google::protobuf::EnumDescriptor *descriptor = (google::protobuf::EnumDescriptor *) CvXSUBANY(cv).any_ptr;
  CODE:
    RETVAL = descriptor;
  OUTPUT: RETVAL

UV
constant()
  CODE:
    RETVAL = CvXSUBANY(cv).any_uv;
  OUTPUT: RETVAL

const Google::ProtocolBuffers::Dynamic::ServiceDef*
service_descriptor(SV *)
  INIT:
    google::protobuf::ServiceDescriptor *descriptor = (google::protobuf::ServiceDescriptor *) CvXSUBANY(cv).any_ptr;
  CODE:
    RETVAL = descriptor;
  OUTPUT: RETVAL

#define DESCRIPTOR_TYPE(name) \
    gpd::intr::define_constant(aTHX_ "DESCRIPTOR_" #name, "descriptor", google::protobuf::FieldDescriptor::TYPE_##name);
#define VALUE_TYPE(name) \
    gpd::intr::define_constant(aTHX_ "VALUE_" #name, "values", gpd::intr::VALUE_##name);
#define LABEL_TYPE(name) \
    gpd::intr::define_constant(aTHX_ "LABEL_" #name, "labels", google::protobuf::FieldDescriptor::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);

%}