#ifndef _GPD_XS_MAPPER_INCLUDED
#define _GPD_XS_MAPPER_INCLUDED

#include "ref.h"

#include <upb/pb/encoder.h>
#include <upb/pb/decoder.h>
#include <upb/json/printer.h>
#include <upb/json/parser.h>

#include "unordered_map.h"

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

#include "thx_member.h"
#include "transform.h"
#include "vectorsink.h"
#include "fieldmap.h"
#include "pb/decoder.h"
#include "mapper_context.h"

#include <list>
#include <vector>

namespace gpd {

class Dynamic;
class MappingOptions;
class MapperField;
class WarnContext;

class Mapper : public Refcounted {
public:
    enum FieldTarget {
        TARGET_MAP_KEY          = 1,
        TARGET_MAP_VALUE        = 2,
        TARGET_ARRAY_ITEM       = 3,
        TARGET_HASH_ITEM        = 4,
        TARGET_FIELDTABLE_ITEM  = 5,
    };

    enum ValueAction {
        ACTION_INVALID          = 0,

        // scalar values
        ACTION_PUT_FLOAT        = 1,
        ACTION_PUT_FLOAT_ND     = 2,
        ACTION_PUT_DOUBLE       = 3,
        ACTION_PUT_DOUBLE_ND    = 4,
        ACTION_PUT_BOOL         = 5,
        ACTION_PUT_BOOL_ND      = 6,
        ACTION_PUT_STRING       = 7,
        ACTION_PUT_STRING_ND    = 8,
        ACTION_PUT_BYTES        = 9,
        ACTION_PUT_BYTES_ND     = 10,
        ACTION_PUT_ENUM         = 11,
        ACTION_PUT_ENUM_ND      = 12,
        ACTION_PUT_INT32        = 13,
        ACTION_PUT_INT32_ND     = 14,
        ACTION_PUT_UINT32       = 15,
        ACTION_PUT_UINT32_ND    = 16,
        ACTION_PUT_INT64        = 17,
        ACTION_PUT_INT64_ND     = 18,
        ACTION_PUT_UINT64       = 19,
        ACTION_PUT_UINT64_ND    = 20,

        // non-scalar values
        ACTION_PUT_MESSAGE      = 21,
        ACTION_PUT_REPEATED     = 22,
        ACTION_PUT_MAP          = 23,
        ACTION_PUT_FIELDTABLE   = 24,
    };

    struct Field {
        const upb::FieldDef *field_def;
        struct {
            upb_selector_t seq_start;
            upb_selector_t seq_end;
            union {
                struct {
                    upb_selector_t msg_start;
                    upb_selector_t msg_end;
                };
                struct {
                    upb_selector_t str_start;
                    upb_selector_t str_cont;
                    upb_selector_t str_end;
                };
                upb_selector_t primitive;
            };
        } selector;
        SV *name;
        U32 name_hash;
        bool has_default;
        bool is_map;
        FieldTarget field_target;
        ValueAction field_action, value_action;
        const Mapper *mapper; // for Message/Group fields
        gpd::transform::DecoderTransform *decoder_transform;
        gpd::transform::EncoderTransform *encoder_transform;
        UMS_NS::unordered_set<int32_t> enum_values;
        int field_index, oneof_index;
        union {
            struct {
                size_t default_str_len;
                const char *default_str;
            };
            bool default_bool;
            IV default_iv;
            UV default_uv;
            NV default_nv;
            int64_t default_i64;
            uint64_t default_u64;
        };

        std::string full_name() const;
        upb::FieldDef::Type map_value_type() const;
        const UMS_NS::unordered_set<int32_t> &map_enum_values() const;

        bool is_map_key() const { return field_target == TARGET_MAP_KEY; }
        bool is_map_value() const { return field_target == TARGET_MAP_VALUE; }
    };

    struct MapKey {
        SV *key_sv;
        const char *key_buffer;
        size_t key_len;

        MapKey(SV * _key_sv) : key_sv(_key_sv), key_buffer(NULL) { }

        void set_buffer(const char *_key_buffer, size_t _key_len) {
            key_buffer = _key_buffer;
            key_len = _key_len;
        }
    };

    struct DecoderHandlers {
        DECL_THX_MEMBER;
        SV *target_ref;
        std::vector<SV *> items;
        std::vector<MapKey> map_keys;
        std::vector<const Mapper *> mappers;
        std::vector<std::vector<bool> > seen_fields;
        std::vector<std::vector<int32_t> > seen_oneof;
        gpd::transform::DecoderTransformQueue pending_transforms;
        gpd::transform::DecoderTransform *decoder_transform;
        bool decoder_transform_fieldtable;
        std::string error;
        SV *string;
        bool track_seen_fields;
        std::vector<gpd::transform::DecoderFieldtable::Entry> fieldtable_entries;

        DecoderHandlers(pTHX_ const Mapper *mapper);
        ~DecoderHandlers();

        void prepare(HV *target);
        void finish();
        SV *get_and_mortalize_target();
        static void static_clear(DecoderHandlers *cxt);

        static bool on_end_message(DecoderHandlers *cxt, upb::Status *status);
        static DecoderHandlers *on_start_string(DecoderHandlers *cxt, const int *field_index, size_t size_hint);
        static size_t on_append_string(DecoderHandlers *cxt, const int *field_index, const char *buf, size_t len);
        static bool on_end_string(DecoderHandlers *cxt, const int *field_index);
        static void on_string(DecoderHandlers *cxt, const int *field_index, const char *buf, size_t len, bool is_utf8);
        static DecoderHandlers *on_start_sequence(DecoderHandlers *cxt, const int *field_index);
        static size_t on_string_key(DecoderHandlers *cxt, const int *field_index, const char *buf, size_t len);
        static bool on_end_sequence(DecoderHandlers *cxt, const int *field_index);
        static DecoderHandlers *on_start_map(DecoderHandlers *cxt, const int *field_index);
        static bool on_end_map(DecoderHandlers *cxt, const int *field_index);
        static DecoderHandlers *on_start_sub_message(DecoderHandlers *cxt, const int *field_index);
        static bool on_end_sub_message(DecoderHandlers *cxt, const int *field_index);
        static bool on_end_map_entry(DecoderHandlers *cxt, const int *field_index);
        static bool on_end_string_map_entry(DecoderHandlers *cxt, const int *field_index);

        template<class T>
        static bool on_nv(DecoderHandlers *cxt, const int *field_index, T val);

        template<class T>
        static bool on_iv(DecoderHandlers *cxt, const int *field_index, T val);

        template<class T>
        static bool on_uv(DecoderHandlers *cxt, const int *field_index, T val);

        static bool on_enum(DecoderHandlers *cxt, const int *field_index, int32_t val);
        static bool on_bigiv(DecoderHandlers *cxt, const int *field_index, int64_t val);
        static bool on_biguv(DecoderHandlers *cxt, const int *field_index, uint64_t val);

        static bool on_perl_bool(DecoderHandlers *cxt, const int *field_index, bool val);
        static bool on_numeric_bool(DecoderHandlers *cxt, const int *field_index, bool val);
        static bool on_json_bool(DecoderHandlers *cxt, const int *field_index, bool val);

        void push_mapper(const Mapper *mapper);
        void pop_mapper();

        bool apply_defaults_and_check();
        SV *get_target(const int *field_index);
        SV *get_hash_item_target(const int *field_index);
        void mark_seen(const int *field_index) {
            if (track_seen_fields)
                seen_fields.back()[*field_index] = true;
        }

        void maybe_add_transform(SV *target, const gpd::transform::DecoderTransform *message_transform, const gpd::transform::DecoderTransform *field_transform) {
            if (message_transform || field_transform)
                pending_transforms.add_transform(target, message_transform, field_transform);
        }
        void add_transform_fieldtable(SV *target, const gpd::transform::DecoderTransform *message_transform, const gpd::transform::DecoderTransform *field_transform);
        void finish_add_transform_fieldtable();
        void apply_transforms() {
            pending_transforms.apply_transforms();
        }
    };

    struct EncoderState {
        upb::Status *status;
        upb::Sink *sink;
        MapperContext *mapper_context;

        EncoderState(upb::Status *_status, MapperContext *_mapper_context);

        EncoderState(EncoderState &original, upb::Sink *_sink) :
                status(original.status),
                sink(_sink),
                mapper_context(original.mapper_context) {
        }

        void setup(upb::Sink *sink);
    };

public:
    Mapper(pTHX_ Dynamic *registry, const upb::MessageDef *message_def, const gpd::pb::Descriptor *gpd_descriptor, HV *stash, const MappingOptions &options);
    ~Mapper();

    const char *full_name() const;
    const char *package_name() const;

    void resolve_mappers();
    void create_encoder_decoder();
    void set_decoder_options(HV *options);
    void set_encoder_options(HV *options);

    SV *encode(SV *ref);
    SV *decode_upb(const char *buffer, STRLEN bufsize);
    SV *decode_bbpb(const char *buffer, STRLEN bufsize);
    SV *encode_json(SV *ref);
    SV *decode_json(const char *buffer, STRLEN bufsize);
    bool check(SV *ref);

    const char *last_error_message() const;

    int field_count() const;
    const Field *get_field(int index) const;

    MapperField *find_extension(const std::string &name) const;

    SV *message_descriptor() const;
    SV *make_object(SV *data) const;
    bool get_decode_blessed() const;
    bool get_track_seen_fields() const;

    void set_bool(SV *target, bool value) const;

private:
    static bool run_bbpb_decoder(Mapper *root_mapper, const char *buffer, STRLEN bufsize);

    bool encode_message(EncoderState &state, SV *ref) const {
        if (encoder_transform) {
            if (encoder_transform_fieldtable) {
                return encode_transformed_fieldtable_message(state, ref, encoder_transform);
            } else {
                return encode_transformed_message(state, ref, encoder_transform);
            }
        } else if (unknown_field_transform) {
            return encode_simple_message_iterate_hash(state, ref);
        } else {
            return encode_simple_message_iterate_fields(state, ref);
        }
    }

    bool encode_simple_message_iterate_fields(EncoderState &state, SV *ref) const;
    bool encode_simple_message_iterate_hash(EncoderState &state, SV *ref) const;
    bool encode_transformed_message(EncoderState &state, SV *ref, gpd::transform::EncoderTransform *encoder_transform) const;
    bool encode_transformed_fieldtable_message(EncoderState &state, SV *ref, gpd::transform::EncoderTransform *encoder_transform) const;
    bool encode_field(EncoderState &state, const Field &fd, SV *ref) const;
    bool encode_key(EncoderState &state, const Field &fd, const char *key, I32 keylen) const;
    bool encode_hash_kv(EncoderState &state, const char *key, STRLEN keylen, SV *value) const;
    bool encode_from_perl_array(EncoderState &state, const Field &fd, SV *ref) const;
    bool encode_from_perl_hash(EncoderState &state, const Field &fd, SV *ref) const;
    bool encode_from_message_array(EncoderState &state, const Mapper::Field &fd, AV *source) const;

    template<class G, class S>
    bool encode_from_array(EncoderState &state, const Mapper::Field &fd, AV *source) const;

    bool check(upb::Status *status, SV *ref) const;
    bool check(upb::Status *status, const Field &fd, SV *ref) const;
    bool check_from_perl_array(upb::Status *status, const Field &fd, SV *ref) const;
    bool check_from_message_array(upb::Status *status, const Mapper::Field &fd, AV *source) const;
    bool check_from_enum_array(upb::Status *status, const Mapper::Field &fd, AV *source) const;

    void apply_default(const Field &field, SV *target) const;
    void apply_map_value_default(SV *target) const;

    void set_json_bool(SV *target, bool value) const;

    struct FieldData {
        enum RepeatedType {
            SCALAR_FIELD       = 0,
            REPEATED_FIELD     = 1,
            MAP_FIELD          = 2,
        };

        enum Action {
            STORE_FLOAT                 = 1,
            STORE_DOUBLE                = 2,
            STORE_STRING                = 3,
            STORE_MESSAGE               = 4,
            STORE_INT32                 = 5,
            STORE_INT64                 = 6,
            STORE_UINT32                = 7,
            STORE_UINT64                = 8,
            STORE_ZIGZAG                = 9,
            STORE_PERL_BOOL             = 10,
            STORE_ENUM                  = 11,
            STORE_NUMERIC_BOOL          = 12,
            STORE_MAP_MESSAGE           = 13,
            STORE_BIG_INT64             = 14,
            STORE_BIG_UINT64            = 15,
            STORE_BIG_ZIGZAG            = 16,
            STORE_STRING_MAP_MESSAGE    = 17,
            STORE_STRING_KEY            = 18,
            STORE_BYTES                 = 19,
            STORE_JSON_BOOL             = 20,
        };

        int index;
        RepeatedType repeated_type;
        Action action;
    };
    typedef gpd::pb::DecoderFieldData<FieldData>::Entry FieldDataEntry;

    DECL_THX_MEMBER;
    Dynamic *registry;
    const upb::MessageDef *message_def;
    const gpd::pb::Descriptor *gpd_descriptor;
    int oneof_count; // cached here for performance
    HV *stash;
    upb::reffed_ptr<const upb::Handlers> pb_encoder_handlers, json_encoder_handlers;
    upb::reffed_ptr<upb::Handlers> decoder_handlers;
    upb::reffed_ptr<const upb::pb::DecoderMethod> pb_decoder_method;
    upb::reffed_ptr<const upb::json::ParserMethod> json_decoder_method;
    std::vector<Field> fields;
    std::vector<MapperField *> extension_mapper_fields;
    gpd::FieldMap<Field> field_map;
    upb::Status status;
    EncoderState encoder_state;
    DecoderHandlers decoder_callbacks;
    gpd::pb::DecoderFieldData<FieldData> decoder_field_data;
    gpd::transform::EncoderTransform *encoder_transform;
    bool encoder_transform_fieldtable;
    gpd::transform::UnknownFieldTransform *unknown_field_transform;
    upb::Sink decoder_sink;
    gpd::VectorSink vector_sink;
    bool check_required_fields, decode_explicit_defaults, encode_defaults, check_enum_values, decode_blessed, fail_ref_coercion, ignore_undef_fields;
    int boolean_style;
    int map_key_index, map_value_index;
    GV *json_false, *json_true;
    WarnContext *warn_context;
    MapperContext mapper_context;
};

class MapperField : public Refcounted {
public:
    MapperField(pTHX_ const Mapper *mapper, const Mapper::Field *field);
    ~MapperField();

    const char *name();
    bool is_repeated();
    bool is_extension();
    bool is_map();

    // presence
    bool has_field(HV *self);
    void clear_field(HV *self);

    // optional/oneof/required
    SV *get_scalar(HV *self, SV *target);
    void set_scalar(HV *self, SV *value);

    // repeated
    SV *get_item(HV *self, int index, SV *target);
    void set_item(HV *self, int index, SV *value);
    void add_item(HV *self, SV *value);
    int list_size(HV *self);
    SV *get_list(HV *self);
    void set_list(HV *self, SV *ref);

    // map
    SV *get_item(HV *self, SV *key, SV *target);
    void set_item(HV *self, SV *key, SV *value);
    SV *get_map(HV *self);
    void set_map(HV *self, SV *ref);

    static MapperField *find_extension(pTHX_ CV *cv, SV *extension);
    static MapperField *find_scalar_extension(pTHX_ CV *cv, SV *extension);
    static MapperField *find_repeated_extension(pTHX_ CV *cv, SV *extension);

private:
    DECL_THX_MEMBER;
    SV *get_read_field(HV *self);
    SV *get_write_field(HV *self);
    SV *get_read_array_ref(HV *self);
    AV *get_read_array(HV *self);
    AV *get_write_array(HV *self);
    SV *get_read_hash_ref(HV *self);
    HV *get_read_hash(HV *self);
    HV *get_write_hash(HV *self);
    void copy_default(SV *target);
    void copy_value(SV *target, SV *value);
    void clear_oneof(HV *self);

    const Mapper::Field *field;
    const Mapper *mapper;
};

class MethodMapper : public Refcounted {
public:
    MethodMapper(pTHX_ Dynamic *registry, const std::string &method, const upb::MessageDef *input_def, const upb::MessageDef *output_def, bool client_streaming, bool server_streaming);
    ~MethodMapper();

    SV *method_name_key() const { return method_name_key_sv; }
    SV *serialize_key() const { return serialize_key_sv; }
    SV *deserialize_key() const { return deserialize_key_sv; }

    SV *method_name() const { return method_name_sv; }
    SV *serialize() const { return serialize_sv; }
    SV *deserialize() const { return deserialize_sv; }
    SV *grpc_call() const { return (SV *) grpc_call_sv; }

    void resolve_input_output();

private:
    DECL_THX_MEMBER;
    Dynamic *registry;
    const upb::MessageDef *input_def, *output_def;
    SV *method_name_key_sv, *serialize_key_sv, *deserialize_key_sv;
    SV *method_name_sv, *serialize_sv, *deserialize_sv;
    CV *grpc_call_sv;
};

class WarnContext {
public:
    static void setup(pTHX);
    static WarnContext *get(pTHX);

    void warn_with_context(pTHX_ SV *warning) const;

    void set_context(MapperContext *cxt) { context = cxt; }
    void localize_warning_handler(pTHX);

private:
    WarnContext(pTHX);

    MapperContext *context;
    SV *chained_handler;
    SV *warn_handler;
};

};

#endif