#include "introspection.h"

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

using namespace gpd;
using namespace gpd::intr;
using namespace google::protobuf;
using namespace std;

namespace {
#if __cplusplus >= 201103L
    using CppType = FieldDescriptor::CppType;
    using Type = FieldDescriptor::Type;
#else
    typedef FieldDescriptor CppType;
    typedef FieldDescriptor Type;
#endif
}

ValueType gpd::intr::field_value_type(const FieldDescriptor *field_def) {
    switch (field_def->cpp_type()) {
    case CppType::CPPTYPE_INT32:
        return VALUE_INT32;
    case CppType::CPPTYPE_INT64:
        return VALUE_INT64;
    case CppType::CPPTYPE_UINT32:
        return VALUE_UINT32;
    case CppType::CPPTYPE_UINT64:
        return VALUE_UINT64;
    case CppType::CPPTYPE_DOUBLE:
        return VALUE_DOUBLE;
    case CppType::CPPTYPE_FLOAT:
        return VALUE_FLOAT;
    case CppType::CPPTYPE_BOOL:
        return VALUE_BOOL;
    case CppType::CPPTYPE_ENUM:
        return VALUE_ENUM;
    case CppType::CPPTYPE_STRING:
        return field_def->type() == Type::TYPE_STRING ?
            VALUE_STRING : VALUE_BYTES;;
    case CppType::CPPTYPE_MESSAGE:
        return VALUE_MESSAGE;
    }
    return VALUE_INVALID; // should not happen
}

bool gpd::intr::field_is_primitive(const FieldDescriptor *field_def) {
    switch (field_def->cpp_type()) {
    case CppType::CPPTYPE_STRING:
    case CppType::CPPTYPE_MESSAGE:
        return false;
    default:
        return true;
    }
}

SV *gpd::intr::field_default_value(pTHX_ const FieldDescriptor *field_def) {
    switch (field_def->cpp_type()) {
    case CppType::CPPTYPE_FLOAT:
        return newSVnv(field_def->default_value_float());
    case CppType::CPPTYPE_DOUBLE:
        return newSVnv(field_def->default_value_double());
    case CppType::CPPTYPE_BOOL:
        return field_def->default_value_bool() ? &PL_sv_yes : &PL_sv_no;
    case CppType::CPPTYPE_STRING: {
        const string &value = field_def->default_value_string();
        SV *result = newSVpv(value.data(), value.length());

        if (field_def->type() == Type::TYPE_STRING)
            SvUTF8_on(result);

        return result;
    }
    case CppType::CPPTYPE_MESSAGE:
        return &PL_sv_undef;
    case CppType::CPPTYPE_ENUM: {
        const EnumValueDescriptor *enumvalue_def = field_def->default_value_enum();

        return enumvalue_def != NULL ? newSViv(enumvalue_def->number()) : 0;
    }
    case CppType::CPPTYPE_INT32:
        return newSViv(field_def->default_value_int32());
    case CppType::CPPTYPE_INT64:
        return newSViv(field_def->default_value_int64());
    case CppType::CPPTYPE_UINT32:
        return newSVuv(field_def->default_value_uint32());
    case CppType::CPPTYPE_UINT64:
        return newSVuv(field_def->default_value_uint64());
    }
    return NULL; // should not happen
}

int gpd::intr::enum_default_value(const EnumDescriptor *enum_def) {
    if (enum_def->value_count() > 0) {
        return enum_def->value(0)->number();
    }

    return 0;
}

const FieldDescriptor *gpd::intr::oneof_find_field_by_number(const OneofDescriptor *oneof_def, int number) {
    const FieldDescriptor *field_def = oneof_def->containing_type()->FindFieldByNumber(number);

    return field_def->containing_oneof() == oneof_def ? field_def : NULL;
}

const FieldDescriptor *gpd::intr::oneof_find_field_by_name(const OneofDescriptor *oneof_def, const string &name) {
    const FieldDescriptor *field_def = oneof_def->containing_type()->FindFieldByName(name);

    return field_def->containing_oneof() == oneof_def ? field_def : NULL;
}

// this function jumps through various hoops:
// - takes an options message (FieldOptions, MessageOptions, ...)
// - looks up a descriptor by name in the pool the message came from
// - builds a dynamic message from the descriptor
// - serializes the original message, and parses it via the dynamic message
//
// What seems to be happening is that loading the descriptors dynamically
// creates a separate set of descriptors for the messages in descriptor.proto,
// and the extensions get associated with this new instance, while the methods
// in the various descriptor objects return a message that uses the compiled-in
// descriptor.
//
// This means that the extension fields can't be looked up directly in the
// object returned by the descriptor API, hence the serialize/deserialize
// round trip.
bool gpd::intr::options_make_wrapper(const DescriptorPool *descriptor_pool, const Message &options_def, DynamicMessageFactory **factory, Message **options) {
    // calling options_def.GetDescriptor() will return a different descriptor
    // pointer (from the generated pool, I think) than looking the descriptor
    // up in the merged pool, and the extensions are associated with the latter
    const string &options_name = options_def.GetDescriptor()->full_name();
    const Descriptor *options_descriptor = descriptor_pool->FindMessageTypeByName(options_name);

    // if no descriptor is found in the pool, google/protobuf/descrptor.proto
    // has not been loaded, so there aren't any custom options
    if (!options_descriptor) {
        // make a copy of the message, just to make code more uniform
        *options = options_def.New();
        (*options)->CopyFrom(options_def);
        *factory = NULL;

        return true;
    }

    // creating a new DynamicFactory every time is wastful, but this code
    // is not performance critical
    DynamicMessageFactory *factory_dyn = new DynamicMessageFactory(descriptor_pool);
    Message *options_dyn = factory_dyn->GetPrototype(options_descriptor)->New();

    string serialized;
    if (!options_def.SerializeToString(&serialized)) {
        return false;
    }
    if (!options_dyn->ParseFromString(serialized)) {
        return false;
    }

    *factory = factory_dyn;
    *options = options_dyn;

    return true;
}

// DescriptorOptionsWrapper

DescriptorOptionsWrapper::DescriptorOptionsWrapper(pTHX_ DynamicMessageFactory *_factory, const Message *_options) :
        factory(_factory),
        options(_options) {
    SET_THX_MEMBER;
}

DescriptorOptionsWrapper::~DescriptorOptionsWrapper() {
    delete options;
    delete factory;
}

SV *DescriptorOptionsWrapper::custom_option_by_name(const string &name) {
    const Reflection *reflection = options->GetReflection();
    const FieldDescriptor *extension_field = reflection->FindKnownExtensionByName(name);

    if (extension_field == NULL) {
        return &PL_sv_undef;
    }

    return get_field(extension_field);
}

SV *DescriptorOptionsWrapper::custom_option_by_number(int number) {
    const Reflection *reflection = options->GetReflection();
    const FieldDescriptor *extension_field = reflection->FindKnownExtensionByNumber(number);

    if (extension_field == NULL) {
        return &PL_sv_undef;
    }

    return get_field(extension_field);
}

bool DescriptorOptionsWrapper::get_attribute(CV *autoload_cv, SV **retval) {
    string attribute(SvPVX(autoload_cv), SvCUR(autoload_cv));
    const Descriptor *descriptor = options->GetDescriptor();
    const FieldDescriptor *field = descriptor->FindFieldByName(attribute);

    if (field == NULL) {
        return false;
    }

    *retval = get_field(field);

    return true;
}

SV *DescriptorOptionsWrapper::get_field(const FieldDescriptor *field) {
    const Reflection *reflection = options->GetReflection();

    switch (field->cpp_type()) {
    case CppType::CPPTYPE_FLOAT:
        return newSVnv(reflection->GetFloat(*options, field));
    case CppType::CPPTYPE_DOUBLE:
        return newSVnv(reflection->GetDouble(*options, field));
    case CppType::CPPTYPE_BOOL:
        return reflection->GetBool(*options, field) ? &PL_sv_yes : &PL_sv_no;
    case CppType::CPPTYPE_STRING: {
        const string &value = reflection->GetString(*options, field);
        SV *result = newSVpv(value.data(), value.length());

        if (field->type() == Type::TYPE_STRING)
            SvUTF8_on(result);

        return result;
    }
    case CppType::CPPTYPE_MESSAGE:
        return &PL_sv_undef;
    case CppType::CPPTYPE_ENUM: {
        const EnumValueDescriptor *enumvalue_def = reflection->GetEnum(*options, field);

        return enumvalue_def != NULL ? newSViv(enumvalue_def->number()) : 0;
    }
    case CppType::CPPTYPE_INT32:
        return newSViv(reflection->GetInt32(*options, field));
    case CppType::CPPTYPE_INT64:
        return newSViv(reflection->GetInt64(*options, field));
    case CppType::CPPTYPE_UINT32:
        return newSVuv(reflection->GetUInt32(*options, field));
    case CppType::CPPTYPE_UINT64:
        return newSVuv(reflection->GetUInt64(*options, field));
    }

    return NULL; // should not happen
}

// Other options objects wrappers

bool MessageOptionsWrapper::deprecated() {
    return options->deprecated();
}

bool FieldOptionsWrapper::deprecated() {
    return options->deprecated();
}

bool EnumOptionsWrapper::deprecated() {
    return options->deprecated();
}

bool ServiceOptionsWrapper::deprecated() {
    return options->deprecated();
}

bool MethodOptionsWrapper::deprecated() {
    return options->deprecated();
}

bool FileOptionsWrapper::deprecated() {
    return options->deprecated();
}

#if PERL_VERSION < 10
    #undef  newCONSTSUB
    #define newCONSTSUB(a, b,c) Perl_newCONSTSUB(aTHX_ a, const_cast<char *>(b), c)
#endif

void gpd::intr::define_constant(pTHX_ const char *name, const char *tag, int value) {
    HV *stash = gv_stashpv("Google::ProtocolBuffers::Dynamic", GV_ADD);
    AV *export_ok = get_av("Google::ProtocolBuffers::Dynamic::EXPORT_OK", GV_ADD);
    HV *export_tags = get_hv("Google::ProtocolBuffers::Dynamic::EXPORT_TAGS", GV_ADD);
    SV **tag_arrayref = hv_fetch(export_tags, tag, strlen(tag), 1);

    newCONSTSUB(stash, name, newSViv(value));

    if (!SvOK(*tag_arrayref)) {
        sv_upgrade(*tag_arrayref, SVt_RV);
        SvROK_on(*tag_arrayref);
        SvRV_set(*tag_arrayref, (SV *) newAV());
    }
    AV *tag_array = (AV *) SvRV(*tag_arrayref);

    av_push(export_ok, newSVpv(name, 0));
    av_push(tag_array, newSVpv(name, 0));
}
