/*
Copyright (C) 2001-2008, The Perl Foundation.
$Id: /mirror/trunk/src/pmc/orderedhash.pmc 34157 2008-12-20T02:10:31.865311Z cotto  $

=head1 NAME

src/pmc/orderedhash.pmc - Ordered Hash

=head1 DESCRIPTION

C<OrderedHash> extends C<Hash> to provide the interfaces of C<array> and
C<hash>. To achieve ordering, hash there are a few restrictions:
C<delete_keyed> never removes items; they are just nulled.

Please note that if values are set via integer idx, these indices have to be in
strict order. Using C<push_xx> simplifies this task.  This creates a key
"\1idx" for C<idx> and is therefore not fully transparent.

There are two iterator interfaces:

=over 4

=item * retrieve values (in creation order)

Please note that after a C<delete_keyed> operation, iterating over
values doesn't work any more.  You'll get an error 'No such key'.

=item * retrieve keys (in creation order)

=back

See F<t/pmc/orderedhash.t>.

=head2 Methods

=over 4

=cut

*/

#include "parrot/parrot.h"

pmclass OrderedHash extends Hash need_ext provides array provides hash {

/*

=item C<void mark()>

Marks the OrderedHash as live.

=cut

*/

    VTABLE void mark() {
        Hash * const h = (Hash *)PMC_struct_val(SELF);
        INTVAL  i;

        if (!h)
            return;

        /* RT #53890 - keys can be NULL on purpose; move to src/hash.c ? */
        for (i = h->mask; i >= 0; --i) {
            HashBucket *b = h->bi[i];

            while (b) {

                if (b->key) {
                    pobject_lives(interp, (PObj *)b->key);
                    if (b->value)
                        pobject_lives(interp, (PObj *)b->value);
                }

                b = b->next;
            }
        }
    }

/*

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

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

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

=cut

*/

    VTABLE PMC *get_pmc_keyed_int(INTVAL idx) {
        Hash * const h = (Hash *)PMC_struct_val(SELF);
        const INTVAL n = h->entries;
        HashBucket *b;

        if (idx < 0)
            idx += n;

        if (idx < 0 || idx >= n)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_KEY_NOT_FOUND,
                "OrderedHash: index out of bounds!");

        b = h->bs + idx;

        if (b->key)
            return (PMC *)b->value;

        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_KEY_NOT_FOUND,
            "OrderedHash: No such key");
    }

    VTABLE PMC *get_pmc_keyed(PMC *key) {
        if ((PObj_get_FLAGS(key) & KEY_type_FLAGS) == KEY_integer_FLAG) {
            PMC * const item = SELF.get_pmc_keyed_int(key_integer(INTERP, key));
            PMC * const next = key_next(INTERP, key);

            if (!next)
                return item;

            return VTABLE_get_pmc_keyed(INTERP, item, next);
        }

        return SUPER(key);
    }

/*

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

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

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

=cut

*/

    VTABLE STRING *get_string_keyed_int(INTVAL idx) {
        Hash * const h = (Hash *)PMC_struct_val(SELF);
        const INTVAL n = h->entries;

        HashBucket *b;

        if (idx < 0)
            idx += n;

        if (idx < 0 || idx >= n)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_KEY_NOT_FOUND,
                "OrderedHash: index out of bounds!");

        b = h->bs + idx;

        if (b->key)
            return VTABLE_get_string(INTERP, (PMC *)b->value);

        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_KEY_NOT_FOUND,
            "OrderedHash: No such key");
    }

    VTABLE STRING *get_string_keyed(PMC *key) {
        if ((PObj_get_FLAGS(key) & KEY_type_FLAGS) == KEY_integer_FLAG) {
            PMC * const item = SELF.get_pmc_keyed_int(key_integer(INTERP, key));
            PMC * const next = key_next(INTERP, key);

            if (!next)
                return VTABLE_get_string(INTERP, item);

            return VTABLE_get_string_keyed(INTERP, item, next);
        }

        return SUPER(key);
    }
/*

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

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

=item C<INTVAL get_integer_keyed_int(INTVAL key)>

Returns the integer value associated with C<key>.

=cut

*/

    VTABLE INTVAL get_integer_keyed_int(INTVAL idx) {
        Hash * const h = (Hash *)PMC_struct_val(SELF);
        const INTVAL n = h->entries;
        HashBucket *b;

        if (idx < 0)
            idx += n;

        if (idx < 0 || idx >= n)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                "OrderedHash: index out of bounds!");

        b = h->bs + idx;

        if (b->key)
            return VTABLE_get_integer(INTERP, (PMC *)b->value);

        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_KEY_NOT_FOUND,
            "OrderedHash: No such key");
    }

    VTABLE INTVAL get_integer_keyed(PMC *key) {

        if ((PObj_get_FLAGS(key) & KEY_type_FLAGS) == KEY_integer_FLAG) {
            PMC * const item = SELF.get_pmc_keyed_int(key_integer(INTERP, key));
            PMC * const next = key_next(INTERP, key);

            if (!next)
                return VTABLE_get_integer(INTERP, item);

            return VTABLE_get_integer_keyed(INTERP, item, next);
        }

        return SUPER(key);
    }

/*

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

=item C<FLOATVAL get_number_keyed_int(INTVAL key)>

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

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

=cut

*/

    VTABLE FLOATVAL get_number_keyed_int(INTVAL idx) {
        Hash * const h = (Hash *)PMC_struct_val(SELF);
        const INTVAL n = h->entries;
        HashBucket *b;

        if (idx < 0)
            idx += n;

        if (idx < 0 || idx >= n)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                "OrderedHash: index out of bounds!");

        b = h->bs + idx;

        if (b->key)
            return VTABLE_get_number(INTERP, (PMC *)b->value);

        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_KEY_NOT_FOUND,
            "OrderedHash: No such key");
    }

    VTABLE FLOATVAL get_number_keyed(PMC *key) {
        if ((PObj_get_FLAGS(key) & KEY_type_FLAGS) == KEY_integer_FLAG) {
            PMC * const item = SELF.get_pmc_keyed_int(key_integer(INTERP, key));
            PMC * const next = key_next(INTERP, key);

            if (!next)
                return VTABLE_get_number(INTERP, item);

            return VTABLE_get_number_keyed(INTERP, item, next);
        }

        return SUPER(key);
    }

/*

=item C<void set_pmc_keyed_int(INTVAL idx, PMC *val)>

=item C<void set_integer_keyed_int(INTVAL key, INTVAL value)>

=item C<void set_number_keyed_int(INTVAL key, FLOATVAL value)>

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

Sets the PMC value of the element at index C<key> to C<val>.
The created key = "\1idx".

=cut

*/

    VTABLE void set_pmc_keyed_int(INTVAL idx, PMC *val) {
        Hash * const     h = (Hash *)PMC_struct_val(SELF);
        const INTVAL     n = h->entries;
        STRING * const fmt = CONST_STRING(INTERP, "\1%d");

        if (idx < -n)
            idx = -idx - n - 1;
        else if (idx < 0)
            idx += n;

        if (idx >= n) {
            /* TODO warn or fill if there are holes */
            STRING * const key = Parrot_sprintf_s(INTERP, fmt, idx);
            SELF.set_pmc_keyed_str(key, val);
        }
        else {
            HashBucket * const b = h->bs + idx;

            if (!b->key)
                b->key = Parrot_sprintf_s(INTERP, fmt, idx);

            b->value     = val;
        }
    }

    VTABLE void set_integer_keyed_int(INTVAL idx, INTVAL value) {
        PMC * const v = pmc_new(INTERP, Parrot_get_ctx_HLL_type(INTERP,
                    enum_class_Integer));
        VTABLE_set_integer_native(INTERP, v, value);
        SELF.set_pmc_keyed_int(idx, v);
    }

    VTABLE void set_number_keyed_int(INTVAL idx, FLOATVAL value) {
        PMC * const v = pmc_new(INTERP, Parrot_get_ctx_HLL_type(INTERP,
                    enum_class_Float));
        VTABLE_set_number_native(INTERP, v, value);
        SELF.set_pmc_keyed_int(idx, v);
    }

    VTABLE void set_string_keyed_int(INTVAL idx, STRING *value) {
        PMC * const v = pmc_new(INTERP, Parrot_get_ctx_HLL_type(INTERP,
                    enum_class_String));
        VTABLE_set_string_native(INTERP, v, value);
        SELF.set_pmc_keyed_int(idx, v);
    }
/*

=item C<void push_float(FLOATVAL value)>

=item C<void push_integer(INTVAL value)>

=item C<void push_pmc(PMC *value)>

=item C<void push_string(STRING *value)>

=cut

*/

    void push_pmc(PMC *value) {
        const INTVAL n = SELF.elements();
        SELF.set_pmc_keyed_int(n, value);
    }

    VTABLE void push_float(FLOATVAL value) {
        const INTVAL n = SELF.elements();
        SELF.set_number_keyed_int(n, value);
    }

    VTABLE void push_integer(INTVAL value) {
        const INTVAL n = SELF.elements();
        SELF.set_integer_keyed_int(n, value);
    }

    VTABLE void push_string(STRING *value) {
        const INTVAL n = SELF.elements();
        SELF.set_string_keyed_int(n, value);
    }

/*

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

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

=item C<INTVAL exists_keyed_int(INTVAL key)>

=cut

*/

    VTABLE INTVAL exists_keyed_int(INTVAL idx) {
        Hash * const h = (Hash *)PMC_struct_val(SELF);
        const INTVAL n = h->entries;
        HashBucket  *b;

        if (idx < 0)
            idx += n;

        if (idx < 0 || idx >= n)
            return 0;

        b = h->bs + idx;

        if (b->key)
            return 1;

        return 0;
    }

    VTABLE INTVAL exists_keyed(PMC *key) {
        if (PObj_get_FLAGS(key) & KEY_integer_FLAG) {
            PMC        *item, *next;
            HashBucket *b;
            Hash       * const h   = (Hash *)PMC_struct_val(SELF);
            INTVAL             idx = key_integer(INTERP, key);
            const INTVAL       n   = h->entries;

            if (idx < 0)
                idx += n;

            if (idx < 0 || idx >= n)
                return 0;

            b = h->bs + idx;

            if (!b->key)
                return 0;

            item = (PMC *)b->value;
            next = key_next(INTERP, key);

            if (!next)
                return 1;

            return VTABLE_exists_keyed(INTERP, item, next);
        }

        return SUPER(key);
    }

    VTABLE INTVAL exists_keyed_str(STRING *key) {
        const Hash * const       h = (Hash *)PMC_struct_val(SELF);
        const HashBucket * const b = parrot_hash_get_bucket(INTERP, h, key);

        return (b && b->key);
    }

/*

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

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

=item C<INTVAL defined_keyed_int(INTVAL key)>

=cut

*/

    VTABLE INTVAL defined_keyed(PMC *key) {
        if (PObj_get_FLAGS(key) & KEY_integer_FLAG) {
            Hash * const h   = (Hash *)PMC_struct_val(SELF);
            INTVAL       idx = key_integer(INTERP, key);
            const INTVAL n   = h->entries;

            HashBucket *b;
            PMC        *item, *next;

            if (idx < 0)
                idx += n;

            /* XXX non-existent is undefined - is this correct */
            if (idx < 0 || idx >= n)
                return 0;

            b = h->bs + idx;

            if (!b->key)
                return 0;

            item = (PMC *)b->value;
            next = key_next(INTERP, key);

            if (!next)
                return VTABLE_defined(INTERP, item);

            return VTABLE_defined_keyed(INTERP, item, next);
        }

        return SUPER(key);
    }

    VTABLE INTVAL defined_keyed_int(INTVAL idx) {
        Hash * const h = (Hash *)PMC_struct_val(SELF);
        const INTVAL n = h->entries;
        HashBucket  *b;

        if (idx < 0)
            idx += n;

        if (idx < 0 || idx >= n)
            return 0;

        b = h->bs + idx;

        if (b->key)
            return VTABLE_defined(INTERP, (PMC *)b->value);

        return 0;
    }

/*

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

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

=item C<void delete_keyed_int(INTVAL key)>

Deletes the key C<*key> from the hash.

=cut

*/

    VTABLE void delete_keyed(PMC *key) {
        PMC * const next = key_next(INTERP, key);

        if (PObj_get_FLAGS(key) & KEY_integer_FLAG) {
            if (next) {
                PMC * const item = SELF.get_pmc_keyed_int(key_integer(INTERP, key));
                VTABLE_delete_keyed(INTERP, item, next);
                return;
            }

            SELF.delete_keyed_int(key_integer(INTERP, key));
        }
        else {
            if (next) {
                PMC * const item = SELF.get_pmc_keyed_str(VTABLE_get_string(INTERP, key));
                VTABLE_delete_keyed(INTERP, item, next);
                return;
            }
            SELF.delete_keyed_str(VTABLE_get_string(INTERP, key));
        }
    }

    VTABLE void delete_keyed_int(INTVAL idx) {
        Hash * const h = (Hash *)PMC_struct_val(SELF);
        const INTVAL n = h->entries;
        HashBucket  *b;

        if (idx < 0)
            idx += n;

        if (idx < 0 || idx >= n)
            return;

        b = h->bs + idx;

        if (!b)
            return;

        b->key       = NULL;
        b->value     = NULL;
    }

    VTABLE void delete_keyed_str(STRING *key) {
        const Hash * const h = (Hash *)PMC_struct_val(SELF);
        HashBucket * const b = parrot_hash_get_bucket(INTERP, h, key);

        if (!b)
            return;

        b->key       = NULL;
        b->value     = NULL;
    }

/*

=item C<PMC *clone()>

Create a clone of the OrderedHash. Non-existent keys are compacted.  Accessing
the clone via integers has different indices, if items were deleted.

=cut

*/

    VTABLE PMC *clone() {
        PMC  * const dest   = pmc_new(INTERP, SELF->vtable->base_type);
        Hash * const hash   = (Hash *)PMC_struct_val(SELF);
        Hash * const h_dest = (Hash *)PMC_struct_val(dest);
        UINTVAL     i;

        for (i = 0; i <= N_BUCKETS(hash->mask-1); i++) {
            HashBucket * const b      = hash->bs + i;
            void       * const key    = b->key;

            if (key)
                parrot_hash_put(INTERP, h_dest, key,
                    (void *)VTABLE_clone(INTERP, (PMC *)b->value));
        }

        return dest;
    }
/*

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

Used during archiving to visit the elements in the hash.

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

Used to archive the hash.

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

Used to unarchive the hash.

Freeze/thaw are inherited from hash.  Only thaw.visit is special, as we have to
preserve key/value order.

=cut

*/

    VTABLE void visit(visit_info *info) {
        info->container  = SELF;

        switch (info->what) {
            case VISIT_THAW_NORMAL:
            case VISIT_THAW_CONSTANTS:
                SUPER(info);
                break;

            case VISIT_FREEZE_NORMAL:
            case VISIT_FREEZE_AT_DESTRUCT:
                {
                    Hash     * const hash = (Hash *)PMC_struct_val(SELF);
                    IMAGE_IO * const io   = info->image_io;
                    UINTVAL i;

                    for (i = 0; i <= hash->entries; i++) {
                        HashBucket * const b   = hash->bs + i;

                        if (b) {
                            STRING * const key = (STRING *)b->key;
                            if (key) {
                                VTABLE_push_string(interp, io, key);
                                (info->visit_pmc_now)(interp, (PMC *)b->value, info);
                            }
                        }
                    }
                }
                break;
            default:
                Parrot_ex_throw_from_c_args(interp, NULL,
                    EXCEPTION_INVALID_OPERATION,
                    "unhandled visit action (%d)", info->what);
        }
    }
}

/*

=back

=head1 SEE ALSO

F<docs/pdds/pdd08_keys.pod>.

=head1 HISTORY

Initial rev by leo 2003-08-21.

=cut

*/

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