#ifndef _GPD_XS_MAPPER_INCLUDED
#define _GPD_XS_MAPPER_INCLUDED

#undef New

#include "ref.h"

#include <upb/pb/encoder.h>
#include <upb/pb/decoder.h>
#include <upb/json/printer.h>
#include <upb/json/parser.h>
#include <upb/bindings/stdc++/string.h>

#include "unordered_map.h"

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

#include "thx_member.h"

#include <list>
#include <vector>

namespace gpd {

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

class Mapper : public Refcounted {
public:
    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;
        bool is_key;
        bool is_value;
        const Mapper *mapper; // for Message/Group fields
        STD_TR1::unordered_set<int32_t> enum_values;
        int 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 STD_TR1::unordered_set<int32_t> &map_enum_values() const;
    };

    struct DecoderHandlers {
        DECL_THX_MEMBER;
        std::vector<SV *> items;
        std::vector<const Mapper *> mappers;
        std::vector<std::vector<bool> > seen_fields;
        std::vector<std::vector<int32_t> > seen_oneof;
        std::string error;
        SV *string;

        DecoderHandlers(pTHX_ const Mapper *mapper);

        void prepare(HV *target);
        SV *get_target();
        void clear();

        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_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 DecoderHandlers *on_start_sequence(DecoderHandlers *cxt, const int *field_index);
        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);

        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_bool(DecoderHandlers *cxt, const int *field_index, bool val);

        bool apply_defaults_and_check();
        SV *get_target(const int *field_index);
        void mark_seen(const int *field_index);
    };

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

    const char *full_name() const;

    void resolve_mappers();
    void create_encoder_decoder();

    SV *encode(SV *ref);
    SV *decode(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;

private:
    bool encode(upb::Sink *sink, upb::Status *status, SV *ref) const;
    bool encode(upb::Sink *sink, upb::Status *status, const Field &fd, SV *ref) const;
    bool encode_nodefaults(upb::Sink *sink, upb::Status *status, const Field &fd, SV *ref) const;
    bool encode_key(upb::Sink *sink, upb::Status *status, const Field &fd, const char *key, I32 keylen) const;
    bool encode_hash_kv(upb::Sink *sink, upb::Status *status, const char *key, STRLEN keylen, SV *value) const;
    bool encode_from_perl_array(upb::Sink *sink, upb::Status *status, const Field &fd, SV *ref) const;
    bool encode_from_perl_hash(upb::Sink *sink, upb::Status *status, const Field &fd, SV *ref) const;
    bool encode_from_message_array(upb::Sink *sink, upb::Status *status, const Mapper::Field &fd, AV *source) const;

    template<class G, class S>
    bool encode_from_array(upb::Sink *sink, upb::Status *status, const Mapper::Field &fd, AV *source) const;

    template<class G, class S>
    bool encode_from_array(upb::Sink *sink, const Mapper::Field &fd, AV *source) const {
        return encode_from_array<G, S>(sink, NULL, fd, source);
    }

    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;

    DECL_THX_MEMBER;
    Dynamic *registry;
    const upb::MessageDef *message_def;
    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;
    STD_TR1::unordered_map<std::string, Field *> field_map;
    upb::Status status;
    DecoderHandlers decoder_callbacks;
    upb::Sink encoder_sink, decoder_sink;
    std::string output_buffer;
    upb::StringSink string_sink;
    bool check_required_fields, decode_explicit_defaults, encode_defaults, check_enum_values;
    WarnContext *warn_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 EnumMapper : public Refcounted {
public:
    EnumMapper(pTHX_ Dynamic *registry, const upb::EnumDef *enum_def);
    ~EnumMapper();

    SV *enum_descriptor() const;

private:
    DECL_THX_MEMBER;
    Dynamic *registry;
    const upb::EnumDef *enum_def;
};

class WarnContext {
public:
    enum Kind {
        Array   = 1,
        Hash    = 2,
        Message = 3,
    };

    struct Item {
        Kind kind;
        union {
            int index;
            const Mapper::Field *field;
            struct {
                const char *key;
                STRLEN keylen;
            };
        };

        Item(Kind _kind) : kind(_kind) { }
    };

    static void setup(pTHX);
    static WarnContext *get(pTHX);

    void warn_with_context(pTHX_ SV *warning) const;

    Item &push_level(Kind kind) {
        levels.push_back(Item(kind));

        return levels.back();
    }

    void pop_level() { levels.pop_back(); }
    void clear() { levels.clear(); }
    void localize_warning_handler(pTHX);

private:
    typedef std::list<Item> Levels;

    WarnContext(pTHX);

    Levels levels;
    SV *chained_handler;
    SV *warn_handler;
};

};

#endif