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

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "perl_unpollute.h"

#include "mapper.h"

%{

void
handle_warning(SV *text)
  INIT:
    gpd::WarnContext *cxt = (gpd::WarnContext *) CvXSUBANY(cv).any_ptr;
  CODE:
    cxt->warn_with_context(aTHX_ text);

void
set_decoder_options(SV *, HV *options)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
  CODE:
    mapper->set_decoder_options(options);

void
set_encoder_options(SV *, HV *options)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
  CODE:
    mapper->set_encoder_options(options);

SV*
new(SV *, SV *ref = NULL)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
  CODE:
    RETVAL = mapper->make_object(ref);
  OUTPUT: RETVAL

SV*
new_and_check(SV *, SV *ref = NULL)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
  CODE:
    if (!mapper->check(ref))
        croak("Check failed: %s", mapper->last_error_message());

    RETVAL = mapper->make_object(ref);
  OUTPUT: RETVAL

SV*
decode_upb(SV *, SV *scalar)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
    STRLEN bufsize;
    const char *buffer = SvPV(scalar, bufsize);
  CODE:
    RETVAL = mapper->decode_upb(buffer, bufsize);

    if (!RETVAL) {
        croak("Deserialization failed: %s", mapper->last_error_message());
    }
  OUTPUT: RETVAL

SV*
decode_bbpb(SV *, SV *scalar)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
    STRLEN bufsize;
    const char *buffer = SvPV(scalar, bufsize);
  CODE:
    RETVAL = mapper->decode_bbpb(buffer, bufsize);

    if (!RETVAL) {
        croak("Deserialization failed: %s", mapper->last_error_message());
    }
  OUTPUT: RETVAL

SV*
static_decode(SV *scalar)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
    STRLEN bufsize;
    const char *buffer = SvPV(scalar, bufsize);
  CODE:
    RETVAL = mapper->decode_upb(buffer, bufsize);

    if (!RETVAL) {
        croak("Deserialization failed: %s", mapper->last_error_message());
    }
  OUTPUT: RETVAL

SV*
decode_json(SV *, SV *scalar)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
    STRLEN bufsize;
    const char *buffer = SvPV(scalar, bufsize);
  CODE:
    RETVAL = mapper->decode_json(buffer, bufsize);

    if (!RETVAL) {
        croak("Deserialization failed: %s", mapper->last_error_message());
    }
  OUTPUT: RETVAL

SV*
encode(SV *klass_or_object, SV *ref = NULL)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
  CODE:
    if (ref == NULL) {
        if (sv_isobject(klass_or_object))
            ref = klass_or_object;
        else
            croak("Usage: $object->encode or $class->encode($hash)");
    }

    RETVAL = mapper->encode(ref);

    if (!RETVAL) {
        croak("Serialization failed: %s", mapper->last_error_message());
    }
  OUTPUT: RETVAL

SV*
static_encode(SV *ref)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
  CODE:
    RETVAL = mapper->encode(ref);

    if (!RETVAL) {
        croak("Serialization failed: %s", mapper->last_error_message());
    }
  OUTPUT: RETVAL

SV*
encode_json(SV *klass_or_object, SV *ref = NULL)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
  CODE:
    if (ref == NULL) {
        if (sv_isobject(klass_or_object))
            ref = klass_or_object;
        else
            croak("Usage: $object->encode or $class->encode($hash)");
    }

    RETVAL = mapper->encode_json(ref);

    if (!RETVAL) {
        croak("Serialization failed: %s", mapper->last_error_message());
    }
  OUTPUT: RETVAL

void
check(SV *klass_or_object, SV *ref = NULL)
  INIT:
    gpd::Mapper *mapper = (gpd::Mapper *) CvXSUBANY(cv).any_ptr;
  CODE:
    if (ref == NULL) {
        if (sv_isobject(klass_or_object))
            ref = klass_or_object;
        else
            croak("Usage: $object->check or $class->check($hash)");
    }

    if (mapper->check(ref))
        croak("Check failed: %s", mapper->last_error_message());

SV *
has_field(HV *self)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  CODE:
    bool has_it = field->has_field(self);

    RETVAL = has_it ? &PL_sv_yes : &PL_sv_no;
  OUTPUT: RETVAL

SV *
has_extension_field(HV *self, SV *extension)
  INIT:
    gpd::MapperField *field = gpd::MapperField::find_extension(aTHX_ cv, extension);
  CODE:
    bool has_it = field->has_field(self);

    RETVAL = has_it ? &PL_sv_yes : &PL_sv_no;
  OUTPUT: RETVAL

void
clear_field(HV *self)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  CODE:
    field->clear_field(self);

void
clear_extension_field(HV *self, SV *extension)
  INIT:
    gpd::MapperField *field = gpd::MapperField::find_extension(aTHX_ cv, extension);
  CODE:
    field->clear_field(self);

void
get_scalar(HV *self)
  INIT:
    dXSTARG;
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  PPCODE:
    PUSHs(field->get_scalar(self, TARG));

void
get_extension_scalar(HV *self, SV *extension)
  INIT:
    dXSTARG;
    gpd::MapperField *field = gpd::MapperField::find_scalar_extension(aTHX_ cv, extension);
  PPCODE:
    PUSHs(field->get_scalar(self, TARG));

void
set_scalar(HV *self, SV *value)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  CODE:
    field->set_scalar(self, value);

void
set_extension_scalar(HV *self, SV *extension, SV *value)
  INIT:
    gpd::MapperField *field = gpd::MapperField::find_scalar_extension(aTHX_ cv, extension);
  CODE:
    field->set_scalar(self, value);

void
get_or_set_scalar(HV *self, SV *value = NULL)
  INIT:
    dXSTARG;
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  PPCODE:
    if (!value)
        PUSHs(field->get_scalar(self, TARG));
    else
        field->set_scalar(self, value);

void
get_or_set_extension_scalar(HV *self, SV *extension, SV *value = NULL)
  INIT:
    dXSTARG;
    gpd::MapperField *field = gpd::MapperField::find_scalar_extension(aTHX_ cv, extension);
  PPCODE:
    if (!value)
        PUSHs(field->get_scalar(self, TARG));
    else
        field->set_scalar(self, value);

void
get_list_item(HV *self, IV index)
  INIT:
    dXSTARG;
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  PPCODE:
    PUSHs(field->get_item(self, index, TARG));

void
get_extension_item(HV *self, SV *extension, IV index)
  INIT:
    dXSTARG;
    gpd::MapperField *field = gpd::MapperField::find_repeated_extension(aTHX_ cv, extension);
  PPCODE:
    PUSHs(field->get_item(self, index, TARG));

void
set_list_item(HV *self, IV index, SV *value)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  CODE:
    field->set_item(self, index, value);

void
set_extension_item(HV *self, SV *extension, IV index, SV *value)
  INIT:
    gpd::MapperField *field = gpd::MapperField::find_repeated_extension(aTHX_ cv, extension);
  CODE:
    field->set_item(self, index, value);

void
get_or_set_list_item(HV *self, IV index, SV *value = NULL)
  INIT:
    dXSTARG;
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  PPCODE:
    if (!value)
        PUSHs(field->get_item(self, index, TARG));
    else
        field->set_item(self, index, value);

void
get_or_set_extension_item(HV *self, SV *extension, IV index, SV *value = NULL)
  INIT:
    dXSTARG;
    gpd::MapperField *field = gpd::MapperField::find_scalar_extension(aTHX_ cv, extension);
  PPCODE:
    if (!value)
        PUSHs(field->get_item(self, index, TARG));
    else
        field->set_item(self, index, value);

void
add_item(HV *self, SV *value)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  CODE:
    field->add_item(self, value);

void
add_extension_item(HV *self, SV *extension, SV *value)
  INIT:
    gpd::MapperField *field = gpd::MapperField::find_repeated_extension(aTHX_ cv, extension);
  CODE:
    field->add_item(self, value);

IV
list_size(HV *self)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  CODE:
    RETVAL = field->list_size(self);
  OUTPUT: RETVAL

IV
extension_list_size(HV *self, SV *extension)
  INIT:
    gpd::MapperField *field = gpd::MapperField::find_repeated_extension(aTHX_ cv, extension);
  CODE:
    RETVAL = field->list_size(self);
  OUTPUT: RETVAL

void
get_list(HV *self)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  PPCODE:
    PUSHs(field->get_list(self));

void
get_extension_list(HV *self, SV *extension)
  INIT:
    gpd::MapperField *field = gpd::MapperField::find_repeated_extension(aTHX_ cv, extension);
  PPCODE:
    PUSHs(field->get_list(self));

void
set_list(HV *self, SV *ref)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  CODE:
    field->set_list(self, ref);

void
set_extension_list(HV *self, SV *extension, SV *ref)
  INIT:
    gpd::MapperField *field = gpd::MapperField::find_repeated_extension(aTHX_ cv, extension);
  CODE:
    field->set_list(self, ref);

void
get_or_set_list(HV *self, SV *ref = NULL)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  PPCODE:
    if (!ref)
        PUSHs(field->get_list(self));
    else
        field->set_list(self, ref);

void
get_or_set_extension_list(HV *self, SV *extension, SV *ref = NULL)
  INIT:
    gpd::MapperField *field = gpd::MapperField::find_scalar_extension(aTHX_ cv, extension);
  PPCODE:
    if (!ref)
        PUSHs(field->get_list(self));
    else
        field->set_list(self, ref);

void
get_map_item(HV *self, SV *key)
  INIT:
    dXSTARG;
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  PPCODE:
    PUSHs(field->get_item(self, key, TARG));

void
set_map_item(HV *self, SV *key, SV *value)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  CODE:
    field->set_item(self, key, value);

void
get_or_set_map_item(HV *self, SV *key, SV *value = NULL)
  INIT:
    dXSTARG;
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  PPCODE:
    if (!value)
        PUSHs(field->get_item(self, key, TARG));
    else
        field->set_item(self, key, value);

void
get_map(HV *self)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  PPCODE:
    PUSHs(field->get_map(self));

void
set_map(HV *self, SV *ref)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  CODE:
    field->set_map(self, ref);

void
get_or_set_map(HV *self, SV *ref = NULL)
  INIT:
    gpd::MapperField *field = (gpd::MapperField *) CvXSUBANY(cv).any_ptr;
  PPCODE:
    if (!ref)
        PUSHs(field->get_map(self));
    else
        field->set_map(self, ref);

BOOT:
    gpd::WarnContext::setup(aTHX);

    sv_setiv(get_sv("Google::ProtocolBuffers::Dynamic::Fieldtable::debug_decoder_transform", GV_ADDMULTI), PTR2IV(gpd::transform::fieldtable_debug_decoder_transform));
    sv_setiv(get_sv("Google::ProtocolBuffers::Dynamic::Fieldtable::profile_decoder_transform", GV_ADDMULTI), PTR2IV(gpd::transform::fieldtable_profile_decoder_transform));
    sv_setiv(get_sv("Google::ProtocolBuffers::Dynamic::Fieldtable::debug_encoder_transform", GV_ADDMULTI), PTR2IV(gpd::transform::fieldtable_debug_encoder_transform));
    sv_setiv(get_sv("Google::ProtocolBuffers::Dynamic::Fieldtable::debug_encoder_unknown_fields", GV_ADDMULTI), PTR2IV(gpd::transform::fieldtable_debug_encoder_unknown_fields));

SV *
grpc_xs_call_service_passthrough(SV *self, ...)
  INIT:
    gpd::MethodMapper *method = (gpd::MethodMapper *) CvXSUBANY(cv).any_ptr;
  CODE:
    EXTEND(SP, 13); // worst case
    PUSHMARK(SP);
    PUSHs(self);
    PUSHs(method->method_name_key());
    PUSHs(method->method_name());
    PUSHs(method->serialize_key());
    PUSHs(method->serialize());
    PUSHs(method->deserialize_key());
    PUSHs(method->deserialize());
    for (int i = 1; i < items; i += 2) {
        SV *key_sv = ST(i);
        STRLEN key_len;
        const char *key = SvPV(key_sv, key_len);

        if ((key_len == 8 && key[0] == 'a' && strncmp(key, "argument", key_len) == 0) ||
            (key_len == 8 && key[0] == 'm' && strncmp(key, "metadata", key_len) == 0) ||
            (key_len == 7 && key[0] == 'o' && strncmp(key, "options", key_len) == 0)) {
            PUSHs(ST(i));
            PUSHs(ST(i + 1));
        }
    }
    PUTBACK;

    call_sv(method->grpc_call(), G_SCALAR);

    SPAGAIN;
    RETVAL = POPs;
    PUTBACK;

    SvREFCNT_inc(RETVAL);
  OUTPUT: RETVAL

%}

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

%{

SV *
debug_encoder_unknown_fields_get()
  INIT:
    AV *value = gpd::transform::fieldtable_debug_encoder_unknown_fields_get();
  CODE:
    if (!value)
        value = newAV();
    RETVAL = newRV_noinc((SV *) value);
  OUTPUT: RETVAL

%}