/*
Copyright (C) 2001-2009, The Perl Foundation.
$Id: /mirror/trunk/src/pmc/resizablepmcarray.pmc 35367 2009-01-10T10:50:26.942379Z cotto  $

=head1 NAME

src/pmc/resizablepmcarray.pmc - resizable array for PMCs only

=head1 DESCRIPTION

This class, ResizablePMCArray, implements an resizable array which stores PMCs.
It puts things into Integer, Float, or String PMCs as appropriate.

=head2 Functions

=over 4

=cut

*/

#include "parrot/parrot.h"


pmclass ResizablePMCArray extends FixedPMCArray need_ext provides array {

/*

=item C<void set_integer_native(INTVAL size)>

Resizes the array to C<size> elements.

=cut

*/

    VTABLE void set_integer_native(INTVAL size) {
        if (size < 0)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                    "ResizablePMCArray: Can't resize!");

        if (!PMC_data(SELF)) {
            /* empty - used fixed routine */
            if (size < 8) {
                SUPER(8);
                PMC_int_val(SELF)  = size;
                PMC_int_val2(SELF) = 8;
            }
            else {
                SUPER(size);
                PMC_int_val2(SELF) = size;
            }
        }
        else if (size <= PMC_int_val2(SELF)) {
            PMC_int_val(SELF) = size;
            /* we could shrink here if necessary */
            return;
        }
        else {
            INTVAL i, cur, needed;
            i = cur = PMC_int_val2(SELF);
            if (cur < 8192)
                cur = size < 2 * cur ? 2 * cur : size;
            else {
                needed = size - cur;
                cur   += needed + 4096;
                cur   &= ~0xfff;
            }

            PMC_data(SELF) = mem_sys_realloc(PMC_data(SELF),
                    cur * sizeof (PMC *));

            for (; i < cur; i++)
                ((PMC **)PMC_data(SELF))[i] = PMCNULL;

            PMC_int_val2(SELF) = cur;
            PMC_int_val(SELF)  = size;
        }
    }

/*

=item C<FLOATVAL shift_float()>

=item C<INTVAL shift_integer()>

=item C<PMC *shift_pmc()>

=item C<STRING *shift_string()>

Removes and returns an item from the start of the array.

=cut

TODO: This always moves the array memory, which is not very performant
TODO: Check whether there is already an element that can be shifted

*/

    VTABLE FLOATVAL shift_float() {
        INTVAL    size = PMC_int_val(SELF);
        PMC      *data;
        PMC     **item;
        FLOATVAL  value;

        if (0 == size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                    "ResizablePMCArray: Can't shift from an empty array!");

        item              = (PMC  **)PMC_data(SELF);
        data              = item[0];
        value             = VTABLE_get_number(INTERP, data);
        PMC_int_val(SELF) = --size;

        mem_sys_memmove(item, item + 1, size * sizeof (PMC *));

        item[size]        = PMCNULL;

        return value;
    }

    VTABLE INTVAL shift_integer() {
        INTVAL    size = PMC_int_val(SELF);
        PMC      *data;
        PMC     **item;
        INTVAL    value;

        if (0 == size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                    "ResizablePMCArray: Can't shift from an empty array!");

        item              = (PMC **)PMC_data(SELF);
        data              = item[0];
        value             = VTABLE_get_integer(INTERP, data);
        PMC_int_val(SELF) = --size;

        mem_sys_memmove(item, item + 1, size * sizeof (PMC*));
        item[size]        = PMCNULL;

        return value;
    }

    VTABLE PMC *shift_pmc() {
        INTVAL  size = PMC_int_val(SELF);
        PMC    *data;
        PMC   **item;

        if (0 == size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                    "ResizablePMCArray: Can't shift from an empty array!");

        item              = (PMC **)PMC_data(SELF);
        data              = item[0];
        PMC_int_val(SELF) = --size;

        mem_sys_memmove(item, item + 1, size * sizeof (PMC *));
        item[size]        = PMCNULL;

        return data;
    }

    VTABLE STRING *shift_string() {
        INTVAL   size = PMC_int_val(SELF);
        PMC     *data;
        PMC    **item;
        STRING  *value;

        if (0 == size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                    "ResizablePMCArray: Can't shift from an empty array!");

        item              = (PMC **)PMC_data(SELF);
        data              = item[0];
        value             = VTABLE_get_string(INTERP, data);
        PMC_int_val(SELF) = --size;

        mem_sys_memmove(item, item + 1, size * sizeof (PMC *));

        item[size]        = PMCNULL;

        return value;
    }


/*

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

Returns the PMC value of the element at index C<key>.

=cut

*/

    VTABLE PMC *get_pmc_keyed_int(INTVAL key) {
        PMC **data;

        if (key < 0)
            key += PMC_int_val(SELF);

        if (key < 0)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                "ResizablePMCArray: index out of bounds!");

        if (key >= PMC_int_val(SELF))
            return PMCNULL;

        data = PMC_data_typed(SELF, PMC **);

        if (PMC_IS_NULL(data[key]))
            return PMCNULL;

        return data[key];
    }

/*

=item C<void set_pmc_keyed_int(INTVAL key, PMC *src)>

Sets the PMC value of the element at index C<key> to C<*src>.

=item C<void set_pmc_keyed(PMC *key, PMC *src)>

If key is a slice, do a splice as set that item.

=cut

*/

    VTABLE void set_pmc_keyed_int(INTVAL key, PMC *src) {
        PMC **data;

        if (key < 0)
            key += PMC_int_val(SELF);

        if (key < 0)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                "ResizablePMCArray: index out of bounds!");

        if (key >= PMC_int_val(SELF))
            SELF.set_integer_native(key+1);

        data      = (PMC **)PMC_data(SELF);
        GC_WRITE_BARRIER(INTERP, SELF, data[key], src);
        data[key] = src;
    }

    VTABLE void set_pmc_keyed(PMC *key, PMC *src) {
        SUPER(key, src);
    }

    VTABLE void delete_keyed(PMC *key) {
        INTVAL  i;
        INTVAL  idx  = key_integer(INTERP, key);
        INTVAL  n    = PMC_int_val(SELF);
        PMC   **data = PMC_data_typed(SELF, PMC **);

        for (i = idx; i < n - 1; ++i)
            data[i] = data[i + 1];

        PMC_int_val(SELF)--;
    }

/*

=item C<INTVAL exists_keyed_int(INTVAL key)>

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

Returns TRUE is the element at C<key> exists; otherwise returns false.

=cut

*/
    VTABLE INTVAL exists_keyed_int(INTVAL key) {
        PMC **data;

        if (key < 0)
            key += PMC_int_val(SELF);

        if (key < 0 || key >= PMC_int_val(SELF))
            return 0;

        data = (PMC **)PMC_data(SELF);
        return !PMC_IS_NULL(data[key]);
    }

    VTABLE INTVAL exists_keyed(PMC *key) {
        INTVAL ix = key_integer(INTERP, key);
        return SELF.exists_keyed_int(ix);
    }

/*

=item C<INTVAL defined_keyed_int(INTVAL key)>

Returns TRUE is the element at C<key> is defined; otherwise returns false.

=cut

*/

    VTABLE INTVAL defined_keyed_int(INTVAL key) {
        PMC *val;

        if (key < 0)
            key += PMC_int_val(SELF);

        if (key < 0 || key >= PMC_int_val(SELF))
            return 0;

        val = SELF.get_pmc_keyed_int(key);

        if (PMC_IS_NULL(val))
            return 0;

        return VTABLE_defined(INTERP, val);
    }

/*

=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)>

Extends the array by adding an element of value C<*value> to the end of
the array.

=cut

*/

    VTABLE void push_float(FLOATVAL value) {
        INTVAL size = PMC_int_val(SELF);
        PMC   *val  = pmc_new(INTERP, enum_class_Float);

        VTABLE_set_number_native(INTERP, val, value);

        /* let set_pmc_keyed_int() worry about memory allocation */
        SELF.set_pmc_keyed_int(size, val);

        return;
    }

    VTABLE void push_integer(INTVAL value) {
        INTVAL size = PMC_int_val(SELF);
        PMC   *val  = pmc_new(INTERP, enum_class_Integer);

        VTABLE_set_integer_native(INTERP, val, value);

        /* let set_pmc_keyed_int() worry about memory allocation */
        SELF.set_pmc_keyed_int(size, val);

        return;
    }

    void push_pmc(PMC *value) {
        INTVAL size = PMC_int_val(SELF);

        /* let set_integer_native() worry about memory allocation */
        SELF.set_integer_native(size + 1);
        ((PMC **)PMC_data(SELF))[size] = value;

        return;
    }

    VTABLE void push_string(STRING *value) {
        INTVAL size = PMC_int_val(SELF);
        PMC   *val  = pmc_new(INTERP, enum_class_String);

        VTABLE_assign_string_native(INTERP, val, value);

        /* let set_pmc_keyed_int() worry about memory allocation */
        SELF.set_pmc_keyed_int(size, val);

        return;
    }

/*

Removes and returns the last element in the array.

=item C<INTVAL pop_float()>

=item C<INTVAL pop_integer()>

=item C<PMC *pop_pmc()>

=item C<STRING *pop_string()>

=cut

*/

    VTABLE FLOATVAL pop_float() {
        INTVAL   size = PMC_int_val(SELF);
        PMC     *data;

        if (0 == size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                    "ResizablePMCArray: Can't pop from an empty array!");

        data              = ((PMC **)PMC_data(SELF))[--size];
        PMC_int_val(SELF) = size;

        return VTABLE_get_number(INTERP, data);
    }

    VTABLE INTVAL pop_integer() {
        INTVAL  size = PMC_int_val(SELF);
        PMC    *data;

        if (0 == size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                    "ResizablePMCArray: Can't pop from an empty array!");

        data              = ((PMC **)PMC_data(SELF))[--size];
        PMC_int_val(SELF) = size;

        return VTABLE_get_integer(INTERP, data);
    }

    VTABLE PMC *pop_pmc() {
        INTVAL size = PMC_int_val(SELF);
        PMC   *data;

        if (0 == size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                    "ResizablePMCArray: Can't pop from an empty array!");

        data              = ((PMC **)PMC_data(SELF))[--size];
        PMC_int_val(SELF) = size;

        return data;
    }

    VTABLE STRING *pop_string() {
        INTVAL  size = PMC_int_val(SELF);
        PMC    *data;

        if (0 == size)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                    "ResizablePMCArray: Can't pop from an empty array!");

        data              = ((PMC **)PMC_data(SELF))[--size];
        PMC_int_val(SELF) = size;

        return VTABLE_get_string(INTERP, data);
    }

/*

=item C<void unshift_float(FLOATVAL value)>

=item C<void unshift_integer(INTVAL value)>

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

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

Extends the array by adding an element of value C<*value> to the begin of
the array.

=cut

*/

    VTABLE void unshift_float(FLOATVAL value) {
        INTVAL  size = PMC_int_val(SELF);
        PMC    *val  = pmc_new(INTERP, enum_class_Float);
        PMC   **data;
        INTVAL  i;

        VTABLE_set_number_native(INTERP, val, value);

        /* let set_integer_native() worry about memory allocation */
        SELF.set_integer_native(size + 1);

        /* make room */
        data = (PMC **)PMC_data(SELF);

        for (i = size; i; --i)
            data[i] = data[i - 1];

        data[0] = val;

        return;
    }

    VTABLE void unshift_integer(INTVAL value) {
        INTVAL  size = PMC_int_val(SELF);
        PMC    *val  = pmc_new(INTERP, enum_class_Integer);
        PMC    **data;
        INTVAL   i;

        VTABLE_set_integer_native(INTERP, val, value);

        /* let set_integer_native() worry about memory allocation */
        SELF.set_integer_native(size + 1);

        /* make room */
        data = (PMC **)PMC_data(SELF);

        for (i = size; i; --i)
            data[i] = data[i - 1];

        data[0] = val;

        return;
    }

    void unshift_pmc(PMC *value) {
        INTVAL  size = PMC_int_val(SELF);
        PMC   **data;
        INTVAL  i;

        /* let set_integer_native() worry about memory allocation */
        SELF.set_integer_native(size + 1);

        /* make room */
        data = (PMC **)PMC_data(SELF);

        for (i = size; i; --i)
            data[i] = data[i - 1];

        data[0] = value;

        return;
    }

    VTABLE void unshift_string(STRING *value) {
        INTVAL  size = PMC_int_val(SELF);
        PMC    *val  = pmc_new(INTERP, enum_class_String);
        PMC   **data;
        INTVAL  i;

        VTABLE_set_string_native(INTERP, val, value);

        /* let set_integer_native() worry about memory allocation */
        SELF.set_integer_native(size + 1);

        /* make room */
        data = (PMC **)PMC_data(SELF);

        for (i = size; i; --i)
            data[i] = data[i - 1];

        data[0] = val;

        return;
    }

/*

=item C<PMC *clone()>

Creates and returns a copy of the array.

=cut

*/

    VTABLE PMC *clone() {
        PMC *copy = SUPER();

        /* copy trimmed extra space */
        PMC_int_val2(copy) = PMC_int_val(SELF);

        return copy;
    }

/*

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

The C<==> operation. Compares two array to hold equal elements.

=cut

*/

    VTABLE INTVAL is_equal(PMC *value) {
        INTVAL j, n;

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

        n = SELF.elements();

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

        for (j = 0; j < n; ++j) {
            PMC *item1 = SELF.get_pmc_keyed_int(j);
            PMC *item2 = VTABLE_get_pmc_keyed_int(INTERP, value, j);
            INTVAL result;

            if (item1 == item2)
                continue;

            Parrot_mmd_multi_dispatch_from_c_args(INTERP, "is_equal",
                "PP->I", item1, item2, &result);
            if (!result)
                return 0;
        }

        return 1;
    }

/*

=item METHOD append(PMC *other)

Append the other array to this array.

=cut

*/

    METHOD append(PMC *other) {
        INTVAL i;
        INTVAL n = VTABLE_elements(INTERP, SELF);
        INTVAL m = VTABLE_elements(INTERP, other);

        if (!m)
            RETURN(void);

        /* pre-size it */
        VTABLE_set_integer_native(INTERP, SELF, n + m);

        if (other->vtable->base_type == SELF->vtable->base_type
        ||  other->vtable->base_type == enum_class_FixedPMCArray) {
            PMC **other_data = PMC_data_typed(other, PMC **);
            PMC **this_data  = PMC_data_typed(SELF, PMC **);

            /* libc is faster at copying data than a manual loop here */
            memmove(this_data + n, other_data, m  * sizeof (PMC *));
        }
        else {
            PMC **this_data = PMC_data_typed(SELF, PMC **);

            for (i = 0; i < m; ++i)
                this_data[n + i] = VTABLE_get_pmc_keyed_int(INTERP, other, i);
        }
    }
/*

=item C<STRING *get_repr()>

Returns the Parrot string representation C<ResizablePMCArray>.

=cut

*/

    VTABLE STRING *get_repr() {
        INTVAL  j;
        INTVAL  n   = VTABLE_elements(INTERP, SELF);
        STRING *res = CONST_STRING(INTERP, "[ ");

        for (j = 0; j < n; ++j) {
            PMC *val = SELF.get_pmc_keyed_int(j);
            res      = string_append(INTERP, res, VTABLE_get_repr(INTERP, val));

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

        return string_append(INTERP, res, CONST_STRING(INTERP, " ]"));
    }

/*

=item C<void splice(PMC *value, INTVAL offset, INTVAL count)>

Replaces C<count> elements starting at C<offset> with the elements in
C<value>.

Note that the C<value> PMC can be of any of the various array types.

Note that this implementation can be *VERY *inefficient as it manipulates
everything via the VTABLE api.

=cut

*/

    void splice(PMC *value, INTVAL offset, INTVAL count) {
        const INTVAL length = VTABLE_elements(INTERP, SELF);
        const INTVAL elems  = VTABLE_elements(INTERP, value);
        INTVAL       shift  = elems - count;
        INTVAL       i;

        /* start from end? */
        if (offset < 0)
            offset += length;

        if (offset < 0)
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_OUT_OF_BOUNDS,
                "illegal splice offset\n");

        /* shrink the array */
        if (shift < 0) {
            /* start at offset so we don't overwrite values we'll need */
            for (i = offset + count; i < length; i++)
                VTABLE_set_pmc_keyed_int(INTERP, SELF, i + shift,
                    VTABLE_get_pmc_keyed_int(INTERP, SELF, i));

            SELF.set_integer_native(length + shift);
        }
        /* grow the array */
        else if (shift > 0) {
            SELF.set_integer_native(length + shift);

            /* move the existing values */
            /* start at length-1 so we don't overwrite values we'll need */
            for (i = length - 1; i >= offset; i--)
                VTABLE_set_pmc_keyed_int(INTERP, SELF, i + shift,
                    VTABLE_get_pmc_keyed_int(INTERP, SELF, i));
        }

        /* copy the new values */
        for (i = 0; i < elems; i++)
            VTABLE_set_pmc_keyed_int(INTERP, SELF, i + offset,
                VTABLE_get_pmc_keyed_int(INTERP, value, i));
    }

/*

=item METHOD PMC* shift()

=item METHOD PMC* pop()

Method forms to remove and return a PMC from the beginning or
end of the array.

=cut

*/

    METHOD PMC* shift() {
        PMC *value = VTABLE_shift_pmc(INTERP, SELF);
        RETURN(PMC *value);
    }

    METHOD PMC* pop() {
        PMC *value = VTABLE_pop_pmc(INTERP, SELF);
        RETURN(PMC *value);
    }

/*

=item METHOD unshift(PMC* value)

=item METHOD push(PMC* value)

Method forms to add a PMC to the beginning or end of the array.

=cut

*/

    METHOD unshift(PMC* value) {
        VTABLE_unshift_pmc(INTERP, SELF, value);
    }

    METHOD push(PMC* value) {
        VTABLE_push_pmc(INTERP, SELF, value);
    }

}

/*

=back

=head1 SEE ALSO

F<docs/pdds/pdd17_basic_types.pod>.

=head1 HISTORY

Initial version                  - Matt Fowles 2004-06-11
Changed allocator to double size - Matt Fowles 2004-06-15

=cut

*/

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