#ifndef _GPD_XS_DYNAMIC_INCLUDED
#define _GPD_XS_DYNAMIC_INCLUDED

#include "upb/bridge.h"

#include "pb/decoder.h"

#include "descriptorloader.h"
#include "ref.h"

#include "unordered_map.h"

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

namespace gpd {

class Mapper;
class MethodMapper;
class ServiceDef;

struct MappingOptions {
    enum BoolStyle {
        Perl = 1,
        Numeric = 2,
        JSON = 3,
    };

    enum AccessorStyle {
        GetAndSet = 1,
        PlainAndSet = 2,
        SingleAccessor = 3,
        Plain = 4,
    };

    enum ClientService {
        Disable = 0,
        Noop = 1,
        GrpcXS = 2,
    };

    enum Decoder {
        Upb = 0,
        Bbpb = 1,
    };

    bool use_bigints;
    bool check_required_fields;
    bool explicit_defaults;
    bool encode_defaults;
    bool encode_defaults_proto3;
    bool check_enum_values;
    bool generic_extension_methods;
    bool implicit_maps;
    bool decode_blessed;
    bool fail_ref_coercion;
    bool no_redefine_perl_names;
    bool ignore_undef_fields;
    BoolStyle boolean_style;
    AccessorStyle accessor_style;
    ClientService client_services;
    Decoder default_decoder;
    SV *stack_trace;

    MappingOptions(pTHX_ SV *options_ref);
};

class Dynamic : public Refcounted {
public:
    Dynamic(const std::string &root_directory);
    ~Dynamic();

    void load_file(pTHX_ const std::string &file);
    void load_string(pTHX_ const std::string &file, SV *string);
    void load_serialized_string(pTHX_ SV *sv);

    void map_message(pTHX_ const std::string &message, const std::string &perl_package, const MappingOptions &options);
    void map_package(pTHX_ const std::string &pb_package, const std::string &perl_package_prefix, const MappingOptions &options);
    void map_package_prefix(pTHX_ const std::string &pb_prefix, const std::string &perl_package_prefix, const MappingOptions &options);
    void map_message_prefix(pTHX_ const std::string &message, const std::string &perl_package_prefix, const MappingOptions &options);
    void map_enum(pTHX_ const std::string &enum_name, const std::string &perl_package, const MappingOptions &options);
    void map_service(pTHX_ const std::string &service_name, const std::string &perl_package, const MappingOptions &options);
    void map_wkts(pTHX_ const MappingOptions &options);
    void resolve_references();

    const Mapper *find_mapper(const upb::MessageDef *message_def) const;

    static bool is_proto3() {
        return GOOGLE_PROTOBUF_VERSION >= 3000000; // always true
    }

    static bool has_proto3_optional() {
        return GOOGLE_PROTOBUF_VERSION >= 3012000;
    }

private:
    void add_file_recursively(pTHX_ const google::protobuf::FileDescriptor *file);
    void map_package_or_prefix(pTHX_ const std::string &pb_package, bool is_prefix, const std::string &perl_package_prefix, const MappingOptions &options);
    void map_message_recursive(pTHX_ const google::protobuf::Descriptor *descriptor, const std::string &perl_package, const MappingOptions &options);
    void map_message_prefix_recursive(pTHX_ const google::protobuf::Descriptor *descriptor, const std::string &perl_package_prefix, const MappingOptions &options, UMS_NS::unordered_set<std::string> &recursed_names);
    void map_message(pTHX_ const google::protobuf::Descriptor *descriptor, const std::string &perl_package, const MappingOptions &options);
    void bind_message(pTHX_ const std::string &perl_package, Mapper *mapper, const google::protobuf::Descriptor *descriptor, HV *stash, const MappingOptions &options);
    void map_enum(pTHX_ const google::protobuf::EnumDescriptor *descriptor, const std::string &perl_package, const MappingOptions &options);
    void bind_enum(pTHX_ const std::string &perl_package, const google::protobuf::EnumDescriptor *descriptor, const MappingOptions &options);
    void map_service(pTHX_ const google::protobuf::ServiceDescriptor *descriptor, const std::string &perl_package, const MappingOptions &options);
    void bind_service(pTHX_ const std::string &perl_package, const google::protobuf::ServiceDescriptor *descriptor, const MappingOptions &options);
    void map_service_noop(pTHX_ const google::protobuf::ServiceDescriptor *descriptor, const std::string &perl_package, const MappingOptions &options, ServiceDef *service_def);
    void map_service_grpc_xs(pTHX_ const google::protobuf::ServiceDescriptor *descriptor, const std::string &perl_package, const MappingOptions &options, ServiceDef *service_def);
    void check_package(pTHX_ const std::string &perl_package, const std::string &pb_name);
    void mark_package(pTHX_ const std::string &perl_package, SV *stack_trace);
    std::string pbname_to_package(pTHX_ const std::string &pb_name, const std::string &perl_package_prefix);

    DescriptorLoader descriptor_loader;
    upb::googlepb::DefBuilder def_builder;
    UMS_NS::unordered_map<std::string, const Mapper *> descriptor_map;
    UMS_NS::unordered_set<std::string> mapped_enums;
    UMS_NS::unordered_set<std::string> mapped_services;
    UMS_NS::unordered_set<const google::protobuf::FileDescriptor *> files;
    std::vector<Mapper *> pending;
    std::vector<MethodMapper *> pending_methods;
    gpd::pb::DescriptorSet descriptor_set;
};

};

#endif