/*
Copyright (C) 2001-2010, Parrot Foundation.
$Id: hash.pmc 48484 2010-08-14 00:48:56Z chromatic $

=head1 NAME

src/pmc/hash.pmc - Hash PMC

=head1 DESCRIPTION

Hash PMC wraps Parrot's _hash to provide a high-level API:

=over 4

=item *

Convert between various types to use as hash keys.

=item *

Convert between various types to use as hash values.

=item *

Handle compound Keys for nested Hash/Array lookups.

=item *

Provide C<HashIterator> to iterate over C<Hash>.

=back

By default Hash uses string keys and PMC values. Methods C<set_key_type> and
C<set_value_type> may be used to switch key and values type. For C<PMC> keys
hash value is calculated using VTABLE C<get_hashvalue> function.

These are the vtable functions for the Hash PMC.

=head2 Functions

=over 4

=cut

*/

#include "pmc/pmc_iterator.h"
#include "pmc/pmc_key.h"
#include "pmc/pmc_hashiteratorkey.h"

/* HEADERIZER HFILE: none */
/* HEADERIZER BEGIN: static */
/* HEADERIZER END: static */

pmclass Hash provides hash auto_attrs {
    ATTR Hash *hash;

/*

=item C<void init()>

Initializes the instance.

=item C<void init_int(INTVAL value_type)>

Initializes the instance with the value_type provided.

=item C<void destroy()>

Free hash structure.

=cut

*/

    VTABLE void init() {
        Parrot_Hash_attributes * const attr =
            (Parrot_Hash_attributes *) PMC_data(SELF);

        attr->hash            = parrot_new_hash(INTERP);
        PObj_custom_mark_destroy_SETALL(SELF);
    }

    VTABLE void init_int(INTVAL value_type) {
        Parrot_Hash_attributes * const attr =
            (Parrot_Hash_attributes *) PMC_data(SELF);

        attr->hash = parrot_create_hash(INTERP,
                (PARROT_DATA_TYPE)value_type,
                Hash_key_type_STRING,
                STRING_compare,
                (hash_hash_key_fn)key_hash_STRING);
        PObj_custom_mark_destroy_SETALL(SELF);
    }

    VTABLE void destroy() {
        Hash * const hash = (Hash *)SELF.get_pointer();
        if (hash)
            parrot_hash_destroy(INTERP, hash);
    }
/*

=item C<void mark()>

Marks the hash as live.

=cut

*/

    VTABLE void mark() {
        Hash * const hash = (Hash *)SELF.get_pointer();
        if (hash && hash->entries)
            parrot_mark_hash(INTERP, hash);
    }

/*

=item C<PMC *clone()>

Creates and returns a clone of the hash.

=cut

*/

    VTABLE PMC *clone() {
        PMC * const dest = Parrot_pmc_new(INTERP, SELF->vtable->base_type);

        parrot_hash_clone(INTERP, (Hash *)SELF.get_pointer(),
                   (Hash *)VTABLE_get_pointer(INTERP, dest));

        return dest;
    }

/*

=item C<void set_pointer(void *ptr)>

Use C<ptr> as this PMC's Hash*.

=cut

*/

    VTABLE void set_pointer(void *ptr) {
        Hash * const old_hash = (Hash *)SELF.get_pointer();
        Hash * const new_hash = (Hash *)ptr;

        PARROT_HASH(SELF)->hash = new_hash;

        if (old_hash)
            parrot_hash_destroy(INTERP, old_hash);
    }


/*

=item C<void set_integer(INTVAL type)>

=item C<void set_key_type(INTVAL type)>

Reset Hash to use different keys. See enum C<Hash_key_type> for possible
values.

NB: this method will destroy all old data!

=cut

*/
    VTABLE void set_integer_native(INTVAL type) {
        Hash            *new_hash;
        Hash * const     old_hash   = PARROT_HASH(SELF)->hash;
        PARROT_DATA_TYPE entry_type = old_hash
                                    ? old_hash->entry_type
                                    : enum_type_PMC;

        if (type == Hash_key_type_STRING)
            new_hash = parrot_create_hash(INTERP,
                    entry_type,
                    Hash_key_type_STRING,
                    STRING_compare,
                    (hash_hash_key_fn)key_hash_STRING);
        else if (type == Hash_key_type_int)
            /* new_int_hash set BOTH keys and values to INTVAL */
            new_hash = parrot_create_hash(INTERP,
                    entry_type,
                    Hash_key_type_int,
                    int_compare,
                    key_hash_int);
        else if (type == Hash_key_type_PMC)
            /* new_int_hash set BOTH keys and values to INTVAL */
            new_hash = parrot_create_hash(INTERP,
                    entry_type,
                    Hash_key_type_PMC,
                    (hash_comp_fn)PMC_compare,
                    (hash_hash_key_fn)key_hash_PMC);
        else
            /* We probably will not implement other types of keys. They are way
             * too dangerous to use from PIR */
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_UNIMPLEMENTED,
                "Hash: Unknown key type");


        PARROT_HASH(SELF)->hash = new_hash;

        if (old_hash)
            parrot_hash_destroy(INTERP, old_hash);
    }

    METHOD set_key_type(INTVAL type) {
        SELF.set_integer_native(type);
    }

/*

=item C<METHOD get_key_type()>

Return type of keys in Hash.

=cut

*/
    METHOD get_key_type() {
        const INTVAL ret = ((Hash *)SELF.get_pointer())->key_type;
        RETURN(INTVAL ret);
    }

/*

=item C<METHOD set_value_type(INTVAL type)>

Reset Hash to use different value-type for stored items. If there is no
previous _hash was set defaults to STRING* keys.

NB: this method will destroy all old data!

=cut
*/
    METHOD set_value_type(INTVAL type) {
        Hash *old_hash = (Hash *)SELF.get_pointer();
        Hash *new_hash;

        /*
        If someone called Hash.set_pointer with NULL pointer...
        It will create STRING* keys hash. Because we can't use STRING_compare
        directly - it declared static in F<src/hash.c>
        */
        if (old_hash && old_hash->entry_type == type)
            return;

        switch (type) {
          case enum_type_INTVAL:
          case enum_type_STRING:
          case enum_type_PMC:
            new_hash = parrot_create_hash(INTERP,
                        (PARROT_DATA_TYPE)type,
                        old_hash ? old_hash->key_type : Hash_key_type_STRING,
                        old_hash ? old_hash->compare : STRING_compare,
                        old_hash ? old_hash->hash_val : (hash_hash_key_fn)key_hash_STRING);
            break;
          default:
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_UNIMPLEMENTED,
                        "Hash: unsupported entry_type %d", type);
        }

        PARROT_HASH(SELF)->hash = new_hash;

        if (old_hash)
            parrot_hash_destroy(INTERP, old_hash);
    }

    METHOD get_value_type() {
        INTVAL ret = ((Hash *)SELF.get_pointer())->entry_type;
        RETURN(INTVAL ret);
    }

/*

=item C<void *get_pointer()>

Get a pointer to this PMC's Hash*.

=cut

*/
    VTABLE void *get_pointer() {
        return PARROT_HASH(SELF)->hash;
    }

/*

=item C<INTVAL get_integer()>

=item C<FLOATVAL get_number()>

Returns the size of the hash.

=cut

*/

    VTABLE INTVAL get_integer() {
        return parrot_hash_size(INTERP, (Hash *)SELF.get_pointer());
    }

    VTABLE FLOATVAL get_number() {
        return SELF.get_integer();
    }

/*

=item C<STRING *get_string()>

Returns a string representation of the hash, showing its class name and
memory address.

=item  C<STRING *get_repr()>

Return a representation of the hash contents.

=cut

*/

    VTABLE STRING *get_string() {
        return Parrot_sprintf_c(INTERP, "Hash[0x%x]", SELF);
    }

    VTABLE STRING *get_repr() {
        /* TT #1231:  Use freeze in get_repr() (for hashes) */
        PMC * const  iter = VTABLE_get_iter(INTERP, SELF);
        STRING      *res  = CONST_STRING(INTERP, "{");
        const INTVAL n    = VTABLE_elements(INTERP, SELF);
        INTVAL       j;

        for (j = 0; j < n; ++j) {
            STRING * const key      = VTABLE_shift_string(INTERP, iter);
            char *   const key_str  = Parrot_str_to_cstring(INTERP, key);
            const size_t   str_len  = strlen(key_str);
            size_t         i;
            int            all_digit = 1;
            PMC           *val;

            for (i = 0; i < str_len; ++i) {
                if (!isdigit((unsigned char)key_str[i])) {
                    all_digit = 0;
                    break;
                }
            }

            Parrot_str_free_cstring(key_str);

            if (all_digit) {
                res = Parrot_str_concat(INTERP, res, key);
            }
            else {
                res = Parrot_str_concat(INTERP, res, CONST_STRING(INTERP, "'"));
                res = Parrot_str_concat(INTERP, res, key);
                res = Parrot_str_concat(INTERP, res, CONST_STRING(INTERP, "'"));
            }

            res = Parrot_str_concat(INTERP, res, CONST_STRING(INTERP, ": "));
            val = SELF.get_pmc_keyed_str(key);
            res = Parrot_str_concat(INTERP, res, VTABLE_get_string(INTERP, val));

            if (j < n - 1)
                res = Parrot_str_concat(INTERP, res, CONST_STRING(INTERP, ", "));
        }

        res = Parrot_str_concat(INTERP, res, CONST_STRING(INTERP, "}"));

        return res;
    }

/*

=item C<INTVAL get_integer_keyed_str(STRING *key)>

=item C<INTVAL get_integer_keyed_int(INTVAL key)>

=cut

*/

    VTABLE INTVAL get_integer_keyed_str(STRING *key) {
        const Hash * const hash = (Hash*)SELF.get_pointer();
        HashBucket * const b = parrot_hash_get_bucket(INTERP, hash,
                hash_key_from_string(INTERP, hash, key));

        if (!b)
            return 0;

        return hash_value_to_int(INTERP, hash, b->value);
    }

    VTABLE INTVAL get_integer_keyed_int(INTVAL key) {
        const Hash * const hash = (Hash*)SELF.get_pointer();
        HashBucket * const b = parrot_hash_get_bucket(INTERP, hash,
                hash_key_from_int(INTERP, hash, key));

        if (!b)
            return 0;

        return hash_value_to_int(INTERP, hash, b->value);
    }
/*

=item C<INTVAL get_integer_keyed(PMC *key)>

Returns the integer value for the element at C<*key>.

=cut

*/

    /* Handling Keys */
    VTABLE INTVAL get_integer_keyed(PMC *key) {
        const Hash * const hash = (Hash *)SELF.get_pointer();
        HashBucket * const b = parrot_hash_get_bucket(INTERP, hash,
                hash_key_from_pmc(INTERP, hash, key));
        PMC        *valpmc;
        PMC        *nextkey;

        if (!b)
            return 0;

        nextkey = key_next(INTERP, key);
        valpmc  = hash_value_to_pmc(INTERP, hash, b->value);

        /* Stop recursion. This is last step */
        if (!nextkey)
            return VTABLE_get_integer(INTERP, valpmc);

        /* Recusively call to enclosed aggregate */
        return VTABLE_get_integer_keyed(INTERP, valpmc, nextkey);
    }

/*

=item C<void set_integer_keyed(PMC *key, INTVAL value)>

=cut

*/

    VTABLE void set_integer_keyed(PMC *key, INTVAL value) {
        Hash   * const hash    = (Hash *)SELF.get_pointer();
        void   * const keystr  = hash_key_from_pmc(INTERP, hash, key);
        PMC    * const nextkey = key_next(INTERP, key);
        PMC    *box;
        HashBucket *b;

        if (PObj_constant_TEST(SELF)
        && !PObj_constant_TEST((PObj *)key))
            Parrot_ex_throw_from_c_args(INTERP, NULL,
                EXCEPTION_INVALID_OPERATION,
                "Used non-constant PMC key in constant hash.");

        if (!nextkey) {
            parrot_hash_put(INTERP, hash, keystr,
                    hash_value_from_int(INTERP, hash, value));
            return;
        }

        b = parrot_hash_get_bucket(INTERP, hash, keystr);
        if (b)
            box = hash_value_to_pmc(INTERP, hash, b->value);
        else {
            /* autovivify an Hash */
            box = Parrot_pmc_new(INTERP, SELF.type());
            parrot_hash_put(INTERP, hash, keystr,
                    hash_value_from_pmc(INTERP, hash, box));
        }

        VTABLE_set_integer_keyed(INTERP, box, nextkey, value);
    }

    VTABLE void set_integer_keyed_int(INTVAL key, INTVAL value) {
        Hash * const hash = (Hash *)SELF.get_pointer();
        parrot_hash_put(INTERP, hash, hash_key_from_int(INTERP, hash, key),
                hash_value_from_int(INTERP, hash, value));
    }

/*

=item C<void set_integer_keyed_str(STRING *key, INTVAL value)>

=cut

*/

    VTABLE void set_integer_keyed_str(STRING *key, INTVAL value) {
        Hash * const hash = (Hash *)SELF.get_pointer();

        if (PObj_constant_TEST(SELF)
        && !PObj_constant_TEST((PObj *)key))
            Parrot_ex_throw_from_c_args(INTERP, NULL,
                EXCEPTION_INVALID_OPERATION,
                "Used non-constant key in constant hash.");

        parrot_hash_put(INTERP, hash, hash_key_from_string(INTERP, hash, key),
                hash_value_from_int(INTERP, hash, value));
    }


/*

=item C<FLOATVAL get_number_keyed_str(STRING *key)>

=item C<FLOATVAL get_number_keyed_int(INTVAL key)>

=cut

*/

    VTABLE FLOATVAL get_number_keyed_str(STRING *key) {
        const Hash * const hash = (Hash *)SELF.get_pointer();
        HashBucket * const b = parrot_hash_get_bucket(INTERP, hash,
                hash_key_from_string(INTERP, hash, key));

        if (!b)
            return 0.0;

        return hash_value_to_number(INTERP, hash, b->value);
    }

    VTABLE FLOATVAL get_number_keyed_int(INTVAL key) {
        const Hash * const hash = (Hash *)SELF.get_pointer();
        HashBucket * const b = parrot_hash_get_bucket(INTERP, hash,
                hash_key_from_int(INTERP, hash, key));

        if (!b)
            return 0.0;

        return hash_value_to_number(INTERP, hash, b->value);
    }
/*

=item C<FLOATVAL get_number_keyed(PMC *key)>

Returns the floating-point value for the element at C<*key>.

=cut

*/

    /* I can't migrate this function right now. Some problem with JITting */
    VTABLE FLOATVAL get_number_keyed(PMC *key) {
        PMC               *nextkey;
        PMC               *valpmc;
        const Hash * const hash   = (Hash *)SELF.get_pointer();
        void       * const keystr = hash_key_from_pmc(INTERP, hash, key);
        HashBucket * const b      = parrot_hash_get_bucket(INTERP, hash, keystr);

        if (!b)
            return 0.0;

        nextkey = key_next(INTERP, key);
        valpmc  = hash_value_to_pmc(INTERP, hash, b->value);

        if (!nextkey)
            return VTABLE_get_number(INTERP, valpmc);

        return VTABLE_get_number_keyed(INTERP, valpmc, nextkey);
    }


/*

=item C<STRING *get_string_keyed_str(STRING *key)>

=item C<STRING *get_string_keyed_int(INTVAL key)>

=cut

*/

    VTABLE STRING *get_string_keyed_str(STRING *key) {
        const Hash * const hash = (Hash*)SELF.get_pointer();
        HashBucket * const b =
            parrot_hash_get_bucket(INTERP, hash, hash_key_from_string(INTERP, hash, key));

        if (!b)
            return Parrot_str_new_noinit(INTERP, enum_stringrep_one, 0);

        return hash_value_to_string(INTERP, hash, b->value);
    }

    VTABLE STRING *get_string_keyed_int(INTVAL key) {
        const Hash * const hash = (Hash*)SELF.get_pointer();
        HashBucket * const b =
            parrot_hash_get_bucket(INTERP, hash, hash_key_from_int(INTERP, hash, key));

        if (!b)
            return Parrot_str_new_noinit(INTERP, enum_stringrep_one, 0);

        return hash_value_to_string(INTERP, hash, b->value);
    }

/*

=item C<STRING *get_string_keyed(PMC *key)>

Returns the string value for the element at C<*key>.

=cut

*/

    VTABLE STRING *get_string_keyed(PMC *key) {
        const Hash * const hash = (Hash *)SELF.get_pointer();
        HashBucket * const b = parrot_hash_get_bucket(INTERP, hash,
                hash_key_from_pmc(INTERP, hash, key));
        PMC        *valpmc;
        PMC        *nextkey;

        if (!b)
            return Parrot_str_new_noinit(INTERP, enum_stringrep_one, 0);

        nextkey = key_next(INTERP, key);
        valpmc  = hash_value_to_pmc(INTERP, hash, b->value);

        /* Stop recursion. This is last step */
        if (!nextkey)
            return VTABLE_get_string(INTERP, valpmc);

        /* Recusively call to enclosed aggregate */
        return VTABLE_get_string_keyed(INTERP, valpmc, nextkey);
    }

/*

=item C<void set_string_keyed(PMC *key, STRING *value)>

=cut

*/

    VTABLE void set_string_keyed(PMC *key, STRING *value) {
        Hash   * const hash    = (Hash *)SELF.get_pointer();
        void   * const keystr  = hash_key_from_pmc(INTERP, hash, key);
        PMC    * const nextkey = key_next(INTERP, key);
        PMC    *box;
        HashBucket *b;

        if (PObj_constant_TEST(SELF)){
            if (!PObj_constant_TEST((PObj *)key))
                Parrot_ex_throw_from_c_args(INTERP, NULL,
                    EXCEPTION_INVALID_OPERATION,
                    "Used non-constant PMC key in constant hash.");
            if (!PObj_constant_TEST((PObj *)value))
                Parrot_ex_throw_from_c_args(INTERP, NULL,
                    EXCEPTION_INVALID_OPERATION,
                    "Used non-constant STRING value in constant hash.");
        }

        if (!nextkey) {
            parrot_hash_put(INTERP, hash, keystr,
                    hash_value_from_string(INTERP, hash, value));
            return;
        }

        b = parrot_hash_get_bucket(INTERP, hash, keystr);
        if (b)
            box = hash_value_to_pmc(INTERP, hash, b->value);
        else {
            /* autovivify an Hash */
            box = Parrot_pmc_new(INTERP, SELF.type());
            parrot_hash_put(INTERP, hash, keystr,
                    hash_value_from_pmc(INTERP, hash, box));
        }

        VTABLE_set_string_keyed(INTERP, box, nextkey, value);
    }

/*

=item C<void set_string_keyed_str(STRING *key, STRING *value)>

=cut

*/

    VTABLE void set_string_keyed_str(STRING *key, STRING *value) {
        Hash * const hash = (Hash *)SELF.get_pointer();

        if (PObj_constant_TEST(SELF)){
            if (!PObj_constant_TEST((PObj *)key))
                Parrot_ex_throw_from_c_args(INTERP, NULL,
                    EXCEPTION_INVALID_OPERATION,
                    "Used non-constant STRING key in constant hash.");
            if (!PObj_constant_TEST((PObj *)value))
                Parrot_ex_throw_from_c_args(INTERP, NULL,
                    EXCEPTION_INVALID_OPERATION,
                    "Used non-constant STRING value in constant hash.");
        }

        parrot_hash_put(INTERP, hash,
                hash_key_from_string(INTERP, hash, key),
                hash_value_from_string(INTERP, hash, value));
    }

    VTABLE void set_string_keyed_int(INTVAL key, STRING *value) {
        Hash * const hash = (Hash *)SELF.get_pointer();

        if ((PObj_constant_TEST(SELF))
        && (!PObj_constant_TEST((PObj *)value)))
            Parrot_ex_throw_from_c_args(INTERP, NULL,
                EXCEPTION_INVALID_OPERATION,
                "Used non-constant STRING value in constant hash.");

        parrot_hash_put(INTERP, hash,
                hash_key_from_int(INTERP, hash, key),
                hash_value_from_string(INTERP, hash, value));
    }

/*

=item C<PMC *get_pmc_keyed(PMC *key)>

=item C<PMC *get_pmc_keyed_str(STRING *key)>

=item C<PMC *get_pmc_keyed_int(INTVAL key)>

Returns the PMC value for the element at C<*key>.

=cut

*/

    VTABLE PMC *get_pmc_keyed_str(STRING *key) {
        const Hash *hash;
        HashBucket *b = NULL;

        GET_ATTR_hash(INTERP, SELF, hash);

        /* special case the most likely key type, for speed */
        key = hash->key_type == Hash_key_type_STRING
            ? key
            : (STRING *)hash_key_from_string(INTERP, hash, key);

        b = parrot_hash_get_bucket(INTERP, hash, key);

        if (!b)
            return PMCNULL;

        /* special case the most likely value type, for speed */
        if (hash->entry_type == enum_type_PMC)
            return (PMC *)b->value;
        else
            return hash_value_to_pmc(INTERP, hash, b->value);
    }

    VTABLE PMC *get_pmc_keyed_int(INTVAL key) {
        const Hash * const hash = (Hash *)SELF.get_pointer();
        HashBucket * const b    = parrot_hash_get_bucket(INTERP, hash,
                hash_key_from_int(INTERP, hash, key));

        if (!b)
            return PMCNULL;

        return hash_value_to_pmc(INTERP, hash, b->value);
    }

    /* Compound Key */
    VTABLE PMC *get_pmc_keyed(PMC *key) {
        const Hash * const hash = (Hash *)SELF.get_pointer();
        HashBucket * const b = parrot_hash_get_bucket(INTERP, hash,
                hash_key_from_pmc(INTERP, hash, key));
        PMC        *valpmc;
        PMC        *nextkey;

        if (!b)
            return PMCNULL;

        nextkey = key_next(INTERP, key);
        valpmc  = hash_value_to_pmc(INTERP, hash, b->value);

        /* Stop recursion. This is last step */
        if (!nextkey)
            return valpmc;

        /* Recusively call to enclosed aggregate */
        return VTABLE_get_pmc_keyed(INTERP, valpmc, nextkey);
    }

/*

=item C<void set_number_keyed(PMC *key, FLOATVAL value)>

=cut

*/

    VTABLE void set_number_keyed(PMC *key, FLOATVAL value) {
        Hash   * const hash    = (Hash *)SELF.get_pointer();
        void   * const keystr  = hash_key_from_pmc(INTERP, hash, key);
        PMC    * const nextkey = key_next(INTERP, key);
        PMC    *box            = PMCNULL;
        HashBucket *b;

        if (PObj_constant_TEST(SELF)
        && !PObj_constant_TEST((PObj *)key))
            Parrot_ex_throw_from_c_args(INTERP, NULL,
                EXCEPTION_INVALID_OPERATION,
                "Used non-constant PMC key in constant hash.");

        if (!nextkey) {
            PMC * const val = get_number_pmc(INTERP, value);
            parrot_hash_put(INTERP, hash, keystr, hash_value_from_pmc(INTERP, hash, val));
            return;
        }

        b = parrot_hash_get_bucket(INTERP, (Hash *)SELF.get_pointer(), keystr);
        if (b)
            box = hash_value_to_pmc(INTERP, hash, b->value);

        /* autovivify an Hash */
        if (PMC_IS_NULL(box))
            box = Parrot_pmc_new(INTERP, SELF.type());

        VTABLE_set_number_keyed(INTERP, box, nextkey, value);
    }

/*

=item C<void set_number_keyed_str(STRING *key, FLOATVAL value)>

Sets C<value> as the value for C<*key>.

=cut

*/

    VTABLE void set_number_keyed_str(STRING *key, FLOATVAL value) {
        PMC * const val  = get_number_pmc(INTERP, value);

        if (PObj_constant_TEST(SELF)
        && !PObj_constant_TEST((PObj *)key))
            Parrot_ex_throw_from_c_args(INTERP, NULL,
                EXCEPTION_INVALID_OPERATION,
                "Used non-constant STRING key in constant hash.");

        parrot_hash_put(INTERP, (Hash *)SELF.get_pointer(), key, val);
    }

/*

=item C<void set_pmc_keyed(PMC *dest_key, PMC *value)>

=cut

*/

    VTABLE void set_pmc_keyed(PMC *key, PMC *value) {
        Hash   * const hash    = (Hash *)SELF.get_pointer();
        void   * const keystr  = hash_key_from_pmc(INTERP, hash, key);
        PMC    * const nextkey = key_next(INTERP, key);
        PMC    *box;
        HashBucket *b;

        if (PObj_constant_TEST(SELF)) {
            if (!PObj_constant_TEST((PObj *)key))
                Parrot_ex_throw_from_c_args(INTERP, NULL,
                    EXCEPTION_INVALID_OPERATION,
                    "Used non-constant PMC key in constant hash.");

            if (!PObj_constant_TEST((PObj *)value))
                Parrot_ex_throw_from_c_args(INTERP, NULL,
                    EXCEPTION_INVALID_OPERATION,
                    "Used non-constant PMC value in constant hash.");
        }

        if (!nextkey) {
            parrot_hash_put(INTERP, hash, keystr, value);
            return;
        }

        b = parrot_hash_get_bucket(INTERP, hash, keystr);
        if (b)
            box = hash_value_to_pmc(INTERP, hash, b->value);
        else {
            /* autovivify an Hash */
            box = Parrot_pmc_new(INTERP, SELF.type());
            parrot_hash_put(INTERP, hash, keystr,
                    hash_value_from_pmc(INTERP, hash, box));
        }


        VTABLE_set_pmc_keyed(INTERP, box, nextkey, value);
    }

/*

=item C<void set_pmc_keyed_str(STRING *key, PMC *value)>

Sets C<*value> as the value for C<*key>.

=cut

*/

    VTABLE void set_pmc_keyed_str(STRING *key, PMC *value) {
        Hash * const hash = (Hash *)SELF.get_pointer();

        if (PObj_constant_TEST(SELF)) {
            if (!PObj_constant_TEST((PObj *)key))
                Parrot_ex_throw_from_c_args(INTERP, NULL,
                    EXCEPTION_INVALID_OPERATION,
                    "Used non-constant STRING key in constant hash.");

            if (!PObj_constant_TEST((PObj *)value))
                Parrot_ex_throw_from_c_args(INTERP, NULL,
                    EXCEPTION_INVALID_OPERATION,
                    "Used non-constant STRING value in constant hash.");
        }

        parrot_hash_put(INTERP, hash, hash_key_from_string(INTERP, hash, key),
                hash_value_from_pmc(INTERP, hash, value));
    }

/*

=item C<INTVAL exists_keyed_str(STRING *key)>

=cut

*/

    VTABLE INTVAL exists_keyed_str(STRING *key) {
        Hash * const hash = (Hash *)SELF.get_pointer();
        HashBucket * const b = parrot_hash_get_bucket(INTERP, hash,
                hash_key_from_string(INTERP, hash, key));
        return b != NULL;
    }

/*

=item C<INTVAL exists_keyed(PMC *key)>

Returns whether a key C<*key> exists in the hash.

=cut

*/

    VTABLE INTVAL exists_keyed(PMC *key) {
        Hash       * const h  = (Hash *)SELF.get_pointer();
        void       * const sx = hash_key_from_pmc(INTERP, h, key);
        HashBucket * const b  = parrot_hash_get_bucket(INTERP, h, sx);

        /* no such key */
        if (!b)
            return 0;

        key = key_next(INTERP, key);

        /* lookup stops here */
        if (!key)
            return 1;

        return VTABLE_exists_keyed(INTERP, hash_value_to_pmc(INTERP, h, b->value), key);
    }

/*

=item C<INTVAL defined_keyed_str(STRING *key)>

=cut

*/

    VTABLE INTVAL defined_keyed_str(STRING *key) {
        const Hash * const hash = (Hash *)SELF.get_pointer();
        HashBucket * const b = parrot_hash_get_bucket(INTERP, hash,
                hash_key_from_string(INTERP, hash, key));

        /* no such key */
        if (!b)
            return 0;

        return VTABLE_defined(INTERP, hash_value_to_pmc(INTERP, hash, b->value));
    }

/*

=item C<INTVAL defined_keyed(PMC *key)>

Returns whether the value for C<*key> is defined.

=cut

*/

    VTABLE INTVAL defined_keyed(PMC *key) {
        Hash       * const h  = (Hash *)SELF.get_pointer();
        void       * const sx = hash_key_from_pmc(INTERP, h, key);
        HashBucket * const b  = parrot_hash_get_bucket(INTERP, h, sx);

        /* no such key */
        if (!b)
            return 0;

        key = key_next(INTERP, key);

        if (!key)
            return VTABLE_defined(INTERP, hash_value_to_pmc(INTERP, h, b->value));
        else
            return VTABLE_defined_keyed(INTERP,
                    hash_value_to_pmc(INTERP, h, b->value), key);
    }

/*

=item C<void delete_keyed_str(STRING *key)>

=cut

*/

    VTABLE void delete_keyed_str(STRING *key) {
        Hash * const hash = (Hash *)SELF.get_pointer();
        parrot_hash_delete(INTERP, hash, hash_key_from_string(INTERP, hash, key));
    }

/*

=item C<void delete_keyed(PMC *key)>

Deletes the element associated with C<*key>.

=cut

*/

    VTABLE void delete_keyed(PMC *key) {
        Hash       * const h  = (Hash *)SELF.get_pointer();
        void       * const sx = hash_key_from_pmc(INTERP, h, key);
        HashBucket * const b  = parrot_hash_get_bucket(INTERP, h, sx);

        /* no such key */
        if (!b)
            return;

        key = key_next(INTERP, key);

        if (!key)
            parrot_hash_delete(INTERP, h, sx);
        else
            VTABLE_delete_keyed(INTERP, (PMC *)b->value, key);
    }

/*

=item C<INTVAL get_bool()>

Returns true if the hash size is not zero.

=cut

*/

    VTABLE INTVAL get_bool() {
        return parrot_hash_size(INTERP, (Hash *)SELF.get_pointer()) != 0;
    }

/*

=item C<INTVAL elements()>

Returns the number of elements in the hash.

=cut

*/

    VTABLE INTVAL elements() {
        return parrot_hash_size(INTERP, (Hash *)SELF.get_pointer());
    }

/*

=item C<PMC *get_iter()>

Return a new iterator.

=cut

*/

    VTABLE PMC *get_iter() {
        return Parrot_pmc_new_init(INTERP, enum_class_HashIterator, SELF);
    }

/*

=item C<INTVAL is_same(const PMC *other)>

Returns whether the hash is the same as C<*other>.

=cut

*/

    VTABLE INTVAL is_same(PMC *other) {
        return (INTVAL)(other->vtable == SELF->vtable &&
            VTABLE_get_pointer(INTERP, other) == SELF.get_pointer());
    }

/*

=item C<INTVAL is_equal(PMC *value)>

The C<==> operation.

Check if two hashes hold the same keys and values.

=cut

*/

    VTABLE INTVAL is_equal(PMC *value) {
        PMC * const iter = VTABLE_get_iter(INTERP, SELF);
        INTVAL      j, n;

        if (value->vtable->base_type != SELF->vtable->base_type)
            return 0;

        n = SELF.elements();

        if (VTABLE_elements(INTERP, value) != n)
            return 0;

        for (j = 0; j < n; ++j) {
            STRING * const key = VTABLE_shift_string(INTERP, iter);
            PMC           *item1, *item2;

            if (!VTABLE_exists_keyed_str(INTERP, value, key))
                return 0;

            item1 = SELF.get_pmc_keyed_str(key);
            item2 = VTABLE_get_pmc_keyed_str(INTERP, value, key);

            if (item1 == item2)
                continue;

            if (!VTABLE_is_equal(INTERP, item1, item2))
                return 0;
        }

        return 1;
    }

/*

=item C<void visit(PMC *info)>

Used during archiving to visit the elements in the hash.

=cut

*/

    VTABLE void visit(PMC *info) {
        parrot_hash_visit(INTERP, (Hash *)SELF.get_pointer(), info);
        SUPER(info);
    }

/*

=item C<void freeze(PMC *info)>

Used to archive the hash.

=cut

*/

    VTABLE void freeze(PMC *info) {
        Hash     * const hash = (Hash *)SELF.get_pointer();

        SUPER(info);
        VTABLE_push_integer(INTERP, info, VTABLE_elements(INTERP, SELF));
        VTABLE_push_integer(INTERP, info, (INTVAL)hash->key_type);
        VTABLE_push_integer(INTERP, info, hash->entry_type);
    }

/*

=item C<void thaw(PMC *info)>

Used to unarchive the hash.

=cut

*/

    VTABLE void thaw(PMC *info) {
        SUPER(info);

        {
            const INTVAL elems  = VTABLE_shift_integer(INTERP, info);
            const INTVAL k_type = VTABLE_shift_integer(INTERP, info);
            const INTVAL v_type = VTABLE_shift_integer(INTERP, info);
            Hash        *hash;

            if (k_type != Hash_key_type_STRING || v_type != enum_hash_pmc) {
                hash_comp_fn cmp_fn;
                hash_hash_key_fn key_fn;

                switch (k_type) {
                  case Hash_key_type_int:
                    key_fn = (hash_hash_key_fn)key_hash_int;
                    cmp_fn = (hash_comp_fn)int_compare;
                    break;
                  case Hash_key_type_STRING:
                    key_fn = (hash_hash_key_fn)key_hash_STRING;
                    cmp_fn = (hash_comp_fn)STRING_compare;
                    break;
                  case Hash_key_type_PMC:
                    key_fn = (hash_hash_key_fn)key_hash_PMC;
                    cmp_fn = (hash_comp_fn)PMC_compare;
                    break;
                  default:
                    Parrot_ex_throw_from_c_args(INTERP, NULL, 1,
                            "unimplemented key type");
                    break;
                }

                SELF.set_pointer(parrot_create_hash(INTERP, (PARROT_DATA_TYPE)v_type,
                                                    (Hash_key_type)k_type, cmp_fn, key_fn));
            }

            hash = (Hash *)SELF.get_pointer();

            PARROT_ASSERT((INTVAL)hash->key_type == k_type);
            PARROT_ASSERT(hash->entry_type       == v_type);

            hash->entries   = elems;
        }
    }
}

/*

=back

=head1 SEE ALSO

F<docs/pdds/pdd08_keys.pod>.

=cut

*/

/*
 * Local variables:
 *   c-file-style: "parrot"
 * End:
 * vim: expandtab shiftwidth=4:
 */
