#ifndef _GPD_XS_FIELDMAP_INCLUDED
#define _GPD_XS_FIELDMAP_INCLUDED

#include "unordered_map.h"

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

#include <vector>

namespace gpd {

class PerlString {
public:
    const char *buffer;
    STRLEN len;
    U32 hash;

    bool operator==(const PerlString &other) const {
        return buffer == other.buffer ||
            (len == other.len && memcmp(buffer, other.buffer, len) == 0);
    }

    void fill(pTHX_ const char *buffer_, STRLEN len_) {
        buffer = buffer_;
        len = len_;
        PERL_HASH(hash, buffer, len);
    }

    // sv needs to be a shared SV
    void fill(pTHX_ SV *sv) {
        buffer = SvPV(sv, len);
        hash = SvSHARED_HASH(sv);
    }

    void fill(pTHX_ HE *he) {
        if (HeKLEN(he) == HEf_SVKEY) {
            buffer = SvPV(HeKEY_sv(he), len);
        } else {
            buffer = HeKEY(he);
            // if the key is marked as UTF-8 and contains non-ASCII characters,
            // it will not be there anyway in the lookup
            len = abs(HeKLEN(he));
        }
        hash = HeHASH(he);
        if (UNLIKELY(!hash)) {
            PERL_HASH(hash, buffer, len);
        }
    }
};

}

UMS_NS_START

template<>
struct hash<gpd::PerlString> {
    size_t operator()(const gpd::PerlString &s) const {
        return s.hash;
    }
};

UMS_NS_END

namespace gpd {

template<class T> class FieldMap;

class FieldMapImpl {
    template<class T> friend class FieldMap;

    typedef UMS_NS::unordered_map<PerlString, void *> NameMap;
    typedef UMS_NS::unordered_map<unsigned int, void *> NumberMap;
    typedef std::vector<void *> NumberVector;

    FieldMapImpl() {
        top_packed_field = 0;
    }

    void *find_by_name(pTHX_ SV *name) const;
    void *find_by_name(pTHX_ HE *he) const;
    void *find_by_name(pTHX_ const char *name, STRLEN namelen) const;

    void *find_by_name(const PerlString &key) const {
        NameMap::const_iterator it = by_name.find(key);

        return it == by_name.end() ? NULL : it->second;
    }

    void *find_by_number(unsigned int number) const {
        if (number <= top_packed_field) {
            return packed_by_number[number];
        } else {
            typename NumberMap::const_iterator it = by_number.find(number);

            return it != by_number.end() ? it->second : NULL;
        }
    }

    void add(pTHX_ SV *name, unsigned int number, void *field);

    void optimize_lookup();

    unsigned int top_packed_field;
    NameMap by_name;
    NumberMap by_number;
    NumberVector packed_by_number;
};

template<class T>
class FieldMap {
public:
    void add(pTHX_ SV *name, unsigned int number, T *field) {
        impl.add(aTHX_ name, number, field);
    }

    void optimize_lookup() {
        impl.optimize_lookup();
    }

    // name needs to be a shared SV
    T *find_by_name(pTHX_ SV *name) const {
        return static_cast<T *>(impl.find_by_name(aTHX_ name));
    }

    T *find_by_name(pTHX_ HE *he) const {
        return static_cast<T *>(impl.find_by_name(aTHX_ he));
    }

    T *find_by_name(pTHX_ const char *name, STRLEN namelen) const {
        return static_cast<T *>(impl.find_by_name(aTHX_ name, namelen));
    }

    T *find_by_number(unsigned int number) const {
        return static_cast<T *>(impl.find_by_number(number));
    }

private:
    FieldMapImpl impl;
};

}

#endif