/*
Copyright (C) 2001-2006, The Perl Foundation.
$Id: unmanagedstruct.pmc 16275 2006-12-28 01:20:44Z particle $

=head1 NAME

src/pmc/unmanagedstruct.pmc - C struct with unmanaged memory

=head1 DESCRIPTION

PMC class to hold C C<struct>s that parrot's not responsible for
disposing of.

Buffer can be accessed using keyed assignments to PMC. Out of
bounds access will very likely segfault.

=head2 Functions

=over 4

=cut

*/

#include "parrot/parrot.h"
#include "parrot/compiler.h"
#include <assert.h>


static INTVAL key_2_idx(Interp *interp, PMC *pmc, PMC *key);
static size_t calc_offsets(Interp *interp, PMC*,
    PMC *init, size_t toff);

/*

=item C<static char *
char_offset_int(Interp *interp, PMC *pmc, INTVAL ix, int *type)>

Returns the pointer for the element at index C<ix>, and sets the element
type in C<*type>.

=cut

*/

static char *
char_offset_int(Interp *interp, PMC *pmc, INTVAL ix, int *type)
{
    size_t offs, n;
    ix *= 3;

    if (!PMC_pmc_val(pmc))
        real_exception(interp, NULL, E_ValueError,
                "Missing struct initializer");
    n = (size_t)VTABLE_elements(interp, PMC_pmc_val(pmc));
    if ((size_t)ix >= n)
        real_exception(interp, NULL, E_ValueError,
                "Non existent elements in struct "
                "ix = %d n=%d", (int)ix, (int)n);
    /* use structure init */
    *type = (int) VTABLE_get_integer_keyed_int(interp,
        PMC_pmc_val(pmc), ix);
    offs = (size_t) VTABLE_get_integer_keyed_int(interp,
        PMC_pmc_val(pmc), ix + 2);
    return ((char *)PMC_data(pmc)) + offs;
}

/*

=item C<static INTVAL
key_2_idx(Interp *interp, PMC *pmc, PMC *key)>

Returns the index for the element associated with key C<*key>. Raises an
exception if the key doesn't exist.

=cut

*/

static INTVAL
key_2_idx(Interp *interp, PMC *pmc, PMC *key)
{
    int ix;
    ix = 0;

    if (!PMC_pmc_val(pmc))
        real_exception(interp, NULL, E_ValueError,
                "Missing struct initializer");
    if (PObj_get_FLAGS(key) & KEY_string_FLAG) {
        PMC * const types = PMC_pmc_val(pmc);
        if (types->vtable->base_type == enum_class_OrderedHash) {
            Hash * const hash = (Hash*)PMC_struct_val(types);
            HashBucket * const b = parrot_hash_get_bucket(interp, hash,
                key_string(interp, key));
            if (!b)
                real_exception(interp, NULL, E_KeyError,
                        "key doesn't exist");
            ix = b - hash->bs;
        }
        else
            real_exception(interp, NULL, E_TypeError,
                    "unhandled type aggregate");
        ix /= 3;
    }
    else
        ix = key_integer(interp, key);
    return ix;
}

/*

=item C<static char *
char_offset_key(Interp *interp, PMC *pmc, PMC *key, int *type)>

Returns the pointer for the element associated with key C<*key>, and
sets the element type in C<*type>.

=cut

*/

static char *
char_offset_key(Interp *interp, PMC *pmc, PMC *key, int *type)
{
    size_t offs, count, size, max;
    PMC *next, *init, *outer_init;
    int ix;
    char *p;

#ifdef STRUCT_DEBUG
    trace_key_dump(interp, key);
#endif
    ix = key_2_idx(interp, pmc, key);
    next = key_next(interp, key);
    p = char_offset_int(interp, pmc, ix, type);
    ix *= 3;
    if (!next)
        return p;
    count = 1;
    if (PObj_get_FLAGS(next) & KEY_integer_FLAG)
        count = key_integer(interp, next);
    else
        count = 1;
    outer_init = init = PMC_pmc_val(pmc);
    max = (size_t) VTABLE_get_integer_keyed_int(interp, init, ix + 1);
#ifdef STRUCT_DEBUG
    PIO_eprintf(interp, " count = %d ix = %d max = %d\n",
            (int)count, (int)ix, (int)max);
#endif

    if (*type == enum_type_struct_ptr || *type == enum_type_struct) {
        /* the struct PMC is hanging off the initializer element
         * as property "_struct"
         */
        PMC * const ptr = VTABLE_get_pmc_keyed_int(interp, init, ix);
        init = VTABLE_getprop(interp, ptr,
            string_from_cstring(interp, "_struct", 0));
        assert(init && (init->vtable->base_type == enum_class_UnManagedStruct
            ||  init->vtable->base_type == enum_class_ManagedStruct));

        if (max > 1) {  /* array of structs */

            if (key_next(interp, next))
                next = key_next(interp, next);
            offs = PMC_int_val(init);
#ifdef STRUCT_DEBUG
            PIO_eprintf(interp, "offs = %d\n", (int)offs);
#endif
            p += offs * count;
        }
        if (init->vtable->base_type == enum_class_UnManagedStruct) {
            /*
             * now point PMC_data of this struct to the real data
             */
            if (*type == enum_type_struct_ptr) {
                /* that is either a pointer */
                assert((PTR2INTVAL(p) & (PARROT_PTR_ALIGNMENT - 1)) == 0);
                PMC_data(init) = *(void**)p;
            }
            else {
                /* or just an offset for nested structs */
                PMC_data(init) = p;
            }
        }
        else if (init->vtable->base_type == enum_class_ManagedStruct &&
            *type == enum_type_struct_ptr) {
            /* a nested struct pointer belonging to us
             * p is the location of the struct pointer in the
             * outer struct, the inner is at PMC_data(init)
             */
            assert((PTR2INTVAL(p) & (PARROT_PTR_ALIGNMENT - 1)) == 0);
            *(void**)p = PMC_data(init);
        }
        return char_offset_key(interp, init, next, type);
    }
    if (count >= max)
        real_exception(interp, NULL, E_LookupError,
                "Non existent array element in struct: "
                "count = %d max=%d", (int)count, (int)max);
    size = data_types[*type - enum_first_type].size;
    return p + count * size;
}

/*

=item C<static INTVAL
ret_int(const char *p, int type)>

Returns the element of type C<type> starting at C<*p> as an C<INTVAL>.

If, for example, C<char> or c<short> type size doesn't match, this will fail
we need some more configure support for type sizes.

=cut

*/

static INTVAL
ret_int(const char *p, int type)
    __attribute__nonnull__(1);

static INTVAL
ret_int(const char *p, int type)
{
    switch (type) {
        case enum_type_INTVAL:
            return *(const INTVAL*) p;
#if INT_SIZE == 4
        case enum_type_int32:
        case enum_type_uint32:
#endif
#if INT_SIZE == 8
        case enum_type_int64:
        case enum_type_uint64:
#endif
        case enum_type_int:
            return *(const int*) p;
#if (LONG_SIZE == 4) && !(INT_SIZE == 4) /* Unlikely combination. */
        case enum_type_int32:
        case enum_type_uint32:
#endif
#if (LONG_SIZE == 8) && !(INT_SIZE == 8)
        case enum_type_int64:
        case enum_type_uint64:
#endif
        case enum_type_long:
        case enum_type_ulong:
            return *(const long*) p;
#if SHORT_SIZE == 2
        case enum_type_int16:
        case enum_type_uint16:
#endif
        /* If SHORT_SIZE != 2 getting int16s requires extra tricks. */
        case enum_type_short:
            return *(const short*) p;
        case enum_type_uint8:
        case enum_type_int8:
        case enum_type_char:
        case enum_type_uchar:
            return *p;
        default:
            internal_exception(1, "returning unhandled int type in struct");
    }
    return -1;
}

/*

=item C<static FLOATVAL
ret_float(const char *p, int type)>

Returns the element of type C<type> starting at C<*p> as a C<FLOATVAL>.

=cut

*/

static FLOATVAL
ret_float(const char *p, int type)
    __attribute__nonnull__(1);

static FLOATVAL
ret_float(const char *p, int type)
{
    switch (type) {
        case enum_type_FLOATVAL:
            return (FLOATVAL) *(const FLOATVAL*) p;
        case enum_type_float:
            return (FLOATVAL) *(const float*) p;
        case enum_type_double:
            return (FLOATVAL) *(const double*) p;
        default:
            internal_exception(1, "returning unhandled float type in struct");
    }
    return -1.0;
}

/*

=item C<static STRING*
ret_string(Interp *interp, char *p, int type)>

Returns the element of type C<type> starting at C<*p> as a Parrot string.

=cut

*/

static STRING*
ret_string(Interp *interp, char *p, int type)
{
    char *cstr;
    size_t len;

    switch (type) {
        case enum_type_cstr:
            cstr = *(char **) p;
            len = strlen(cstr);
            return string_make(interp, cstr, len, "iso-8859-1",
                PObj_external_FLAG);
        default:
            real_exception(interp, NULL, E_TypeError,
                    "returning unhandled string type in struct");
    }
    return NULL;
}

/*

=item C<static PMC*
ret_pmc(Interp *interp, PMC* pmc, char *p, int type, INTVAL idx)>

Returns the element of type C<type> starting at C<*p> as a PMC.

=cut

*/

static PMC*
ret_pmc(Interp *interp, PMC* pmc, char *p, int type, INTVAL idx)
{
    PMC *ret = NULL, *init, *ptr;

    switch (type) {
        case enum_type_func_ptr:
            /* this is a raw function pointer - not a PMC */
            ret = *(PMC**) p;
            /* now check if initializer has a signature attached */
            init = PMC_pmc_val(pmc);
            ptr = VTABLE_get_pmc_keyed_int(interp, init, idx*3);
            if (ptr->pmc_ext && PMC_metadata(ptr)) {
                PMC *sig = VTABLE_getprop(interp, ptr,
                        string_from_cstring(interp, "_signature", 0));
                if (VTABLE_defined(interp, sig)) {
                    STRING *sig_str = VTABLE_get_string(interp, sig);
                    ret = pmc_new(interp, enum_class_NCI);
                    VTABLE_set_pointer_keyed_str(interp, ret, sig_str,
                            *(PMC**)p);
                }
            }
            return ret;
        case enum_type_struct_ptr:

            /* check the metadata for an initializer */
            init = PMC_pmc_val(pmc);
            ptr  = VTABLE_get_pmc_keyed_int(interp, init, idx * 3);

            /* grab the struct from the metadata */
            if (ptr->pmc_ext && PMC_metadata(ptr))
            {
                ret = VTABLE_getprop(interp, ptr,
                        const_string(interp, "_struct"));
            }
            else
            {
                real_exception(interp, NULL, E_TypeError,
                        "no initializer available for nested struct");
            }

            /* assign the pointer */
            PMC_data(ret) = *(void**)p;

            return ret;
        default:
            real_exception(interp, NULL, E_TypeError,
                    "returning unhandled pmc type (%d) in struct",
                    type);
    }
    return NULL;
}

/*

=item C<static void
set_int(char *p, int type, INTVAL value)>

=cut

*/

static void
set_int(char *p, int type, INTVAL value)
{
    switch (type) {
        case enum_type_uint8:
        case enum_type_int8:
        case enum_type_char:
        case enum_type_uchar:
            *(char*) p = (char)value & 0xff;
            break;
        case enum_type_INTVAL:
            *(INTVAL*) p = value;
            break;
        case enum_type_int:
            *(int*) p = value;
            break;
        case enum_type_int16:
        case enum_type_uint16:
        case enum_type_short:
            *(short*)p = (short)value;
            break;
        default:
            internal_exception(1, "setting unhandled int type in struct");
            break;
    }
}

/*

=item C<static void
set_float(char *p, int type, FLOATVAL value)>

Sets the value of the element of type C<type> starting at C<*p> to
C<value>.

=cut

*/

static void
set_float(char *p, int type, FLOATVAL value)
{
    switch (type) {
        case enum_type_FLOATVAL:
            *(FLOATVAL*) p = (FLOATVAL) value;
            break;
        case enum_type_float:
            *(float*) p = (float) value;
            break;
        case enum_type_double:
            *(double*) p = (double) value;
            break;
        default:
            internal_exception(1, "setting unhandled float type in struct");
            break;
    }
}

/*

=item C<static void
set_string(char *p, int type, STRING *value)>

Sets the value of the element of type C<type> starting at C<*p> to
C<*value>.

=cut

*/

static void
set_string(char *p, int type, STRING* value)
{
    char *cstr;
    switch (type) {
        case enum_type_cstr:
            /* XXX assumes 0-terminate C-string here
             *     we can't use string_to_cstring easily
             */
            cstr = value->strstart;
            *(char **) p = cstr;
            break;
        default:
            internal_exception(1, "setting unhandled string type in struct (%d)", type);
            break;
    }
}

/*

=item C<static int
calc_align(Interp *interp, PMC *pmc, PMC *type_pmc,
        int type, int offs)>

Alignment of contained structures is the alignment of the
biggest item in that C<struct>.

i386: C<long long> or C<double> is aligned on 4.

This is recursive as structure definitions.

=cut

*/

static int
calc_align(Interp *interp, PMC* pmc, PMC* type_pmc,
        int type, int offs)
{
    int align = data_types[type - enum_first_type].size;
    PMC *nested, *nested_init;

    nested = nested_init = NULL;
    if (type == enum_type_struct || type == enum_type_struct_ptr) {
        /* a nested structs alignment is the biggest item in it
         * so go through that struct and check
         */
        nested = VTABLE_getprop(interp, type_pmc,
            string_from_cstring(interp, "_struct", 0));
        nested_init = PMC_pmc_val(nested);
    }
    if (type == enum_type_struct) {
        size_t i, n = (size_t)VTABLE_elements(interp, nested_init);
        int a_max = 0;
        int new_offs;

        if (n % 3)
            real_exception(interp, NULL, E_TypeError,
                    "Illegal initializer for struct");

        for (i = 0; i < n; i += 3) {
                PMC* nested_type_pmc = VTABLE_get_pmc_keyed_int(interp,
                        nested_init, i);
                int nested_type = (int) VTABLE_get_integer(interp,
                        nested_type_pmc);
                new_offs = calc_align(interp, nested,
                        nested_type_pmc, nested_type, offs);
                if (new_offs > a_max)
                    a_max = new_offs;
        }
        align = a_max;
    }

    if (align && offs % align) {
        const int diff = align - (offs % align);
        offs += diff;
    }

    if (type == enum_type_struct || type == enum_type_struct_ptr) {
        calc_offsets(interp, nested, nested_init, 0);
    }
    return offs;
}

/*

=item C<static size_t
calc_offsets(Interp *interp, PMC *pmc, PMC *value, size_t toff)>

Calculates the offsets for the C<struct>. See C<init_pmc()> for a
description of C<*value>.

=cut

*/

static size_t
calc_offsets(Interp *interp, PMC* pmc, PMC *value, size_t toff)
{
    size_t i, n = (size_t)VTABLE_elements(interp, value);
    int size;

    if (n % 3)
        real_exception(interp, NULL, E_TypeError,
                "Illegal initializer for struct");

    for (i = 0; i < n; i += 3) {
        PMC* type_pmc = VTABLE_get_pmc_keyed_int(interp,
            value, i);
        int type = (int) VTABLE_get_integer(interp, type_pmc);
        int count= (int) VTABLE_get_integer_keyed_int(interp,
            value, i+1);
        int offs = (int) VTABLE_get_integer_keyed_int(interp,
            value, i+2);

        if (type < enum_first_type || type >= enum_last_type)
            real_exception(interp, NULL, E_TypeError,
                    "Illegal type in initializer for struct");
        if (count <= 0) {
            count = 1;
            VTABLE_set_integer_keyed_int(interp, value, i+1, count);
        }
        if (offs <= 0) {
            offs = toff = calc_align(interp, pmc, type_pmc, type, toff);
            VTABLE_set_integer_keyed_int(interp, value, i+2, offs);
        }
        else
            toff = offs;
            if (type == enum_type_struct) {
                PMC *nested = VTABLE_getprop(interp, type_pmc,
                        string_from_cstring(interp, "_struct", 0));
                size = PMC_int_val(nested);
            }
            else
                size = data_types[type - enum_first_type].size;
        toff += count * size;
            /* set / allocate size */
        if (i == n - 3)
            VTABLE_set_integer_native(interp, pmc, toff);
    }
    return toff;
}

pmclass UnManagedStruct extends default need_ext no_ro {

/*

=back

=head2 Methods

=over 4

=item C<void init()>

Initializes the C<struct> with a default value of C<NULL>.

=cut

*/

    void init() {
        PMC_pmc_val(SELF) = NULL;
    }


/*

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

Initialize the struct with some data.

C<*value> should be an array of triples of:

=over 4

=item 0

The datatype. See the C<enum> in F<include/parrot/datatypes.h>.

=item 1

The count.

=item 2

The offset.

=back

=cut

*/

    void init_pmc(PMC* value) {
        SELF.set_pmc(value);
    }

/*

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

Sets C<*value> (see C<init_pmc()> and calculates the offsets.

=cut

*/

    void set_pmc(PMC* value) {
        PMC_pmc_val(SELF) = value;
        calc_offsets(INTERP, SELF, value, 0);
        PObj_custom_mark_SET(SELF);
    }

/*

=item C<void mark()>

Marks the C<struct> as live.

=cut

*/

    void mark() {
        if (PMC_pmc_val(SELF))
            pobject_lives(INTERP, (PObj *) PMC_pmc_val(SELF));
    }

/*

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

Returns whether the two C<struct>s are equivalent.

=cut

*/

    INTVAL is_equal(PMC* value) {
        return (SELF->vtable == value->vtable &&
            PMC_data(SELF) == PMC_data(value));
    }

/*

=item C<INTVAL defined()>

Returns whether the C<struct> is defined.

=cut

*/

    INTVAL defined() {
        return PMC_data(SELF) != NULL;
    }

/*

=item C<INTVAL get_integer()>

Returns the size of the C<struct>.

=cut

*/

    INTVAL get_integer() {
        return PMC_int_val(SELF);
    }

/*

=item C<void set_integer_native(INTVAL size)>

Sets the size of the C<struct>.

=cut

*/

    void set_integer_native(INTVAL size) {
        PMC_int_val(SELF) = size;
    }

/*

=item C<INTVAL get_integer_keyed_int(INTVAL ix)>

Returns the integer value at index C<ix>.

=cut

*/

    INTVAL get_integer_keyed_int(INTVAL ix) {
        int type;
        char *p = char_offset_int(INTERP, pmc, ix, &type);
        return ret_int(p, type);
    }

/*

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

Returns the integer value associated with C<*key>.

=cut

*/

    INTVAL get_integer_keyed(PMC* key) {
        int type;
        char *p = char_offset_key(INTERP, pmc, key, &type);
        return ret_int(p, type);
    }

/*

=item C<FLOATVAL get_number_keyed_int(INTVAL key)>

Returns the floating-point value at index C<ix>.

=cut

*/

    FLOATVAL get_number_keyed_int(INTVAL key) {
        int type;
        char *p = char_offset_int(INTERP, pmc, key, &type);
        return ret_float(p, type);
    }

/*

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

Returns the floating-point value associated with C<*key>.

=cut

*/

    FLOATVAL get_number_keyed(PMC* key) {
        int type;
        char *p = char_offset_key(INTERP, pmc, key, &type);
        return ret_float(p, type);
    }

/*

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

Returns the Parrot string value at index C<ix>.

=cut

*/

    STRING* get_string_keyed_int(INTVAL key) {
        int type;
        char *p = char_offset_int(INTERP, pmc, key, &type);
        return ret_string(INTERP, p, type);
    }

/*

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

Returns the Parrot string value associated with C<*key>.

=cut

*/

    STRING* get_string_keyed(PMC* key) {
        int type;
        char *p = char_offset_key(INTERP, pmc, key, &type);
        return ret_string(INTERP, p, type);
    }

/*

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

Returns the PMC value at index C<ix>.

=cut

*/

    PMC* get_pmc_keyed_int(INTVAL key) {
        int type;
        char *p = char_offset_int(INTERP, pmc, key, &type);
        return ret_pmc(INTERP, pmc, p, type, key);
    }

/*

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

Returns the PMC value associated with C<*key>.

=cut

*/

    PMC* get_pmc_keyed(PMC* key) {
        int type;
        char *p = char_offset_key(INTERP, pmc, key, &type);
        return ret_pmc(INTERP, pmc, p, type,
            key_2_idx(INTERP, pmc, key));
    }

/*

=item C<void *get_pointer()>

Returns the pointer to the actual C C<struct>.

=cut

*/

    void* get_pointer() {
        return PMC_data(SELF);
    }

/*

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

Set the pointer to the actual C C<struct>.

=cut

*/

    void set_pointer(void* value) {
        PMC_data(SELF) = value;
    }

/*

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

Sets the value of the element at index C<ix> to C<value>.

=cut

*/

    void set_integer_keyed_int(INTVAL ix, INTVAL value) {
        int type;
        char *p = char_offset_int(INTERP, pmc, ix, &type);
        set_int(p, type, value);
    }

/*

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

Sets the value of the element associated with key C<*key> to C<value>.

May cause segfaults if value is out of bounds.

=cut

*/

    void set_integer_keyed(PMC* key, INTVAL value) {
        int type;
        char *p = char_offset_key(INTERP, pmc, key, &type);
        set_int(p, type, value);
    }

/*

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

Sets the value of the element at index C<ix> to C<value>.

=cut

*/

    void set_number_keyed_int(INTVAL key, FLOATVAL value) {
        int type;
        char *p = char_offset_int(INTERP, pmc, key, &type);
        set_float(p, type, value);
    }

/*

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

Sets the value of the element associated with key C<*key> to C<value>.

=cut

*/

    void set_number_keyed(PMC *key, FLOATVAL value) {
        int type;
        char *p = char_offset_key(INTERP, pmc, key, &type);
        set_float(p, type, value);
    }

/*

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

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

=cut

*/

    void set_string_keyed_int(INTVAL key, STRING* value) {
        int type;
        char *p = char_offset_int(INTERP, pmc, key, &type);
        set_string(p, type, value);
    }

/*

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

Sets the value of the element associated with key C<*key> to C<*value>.

=cut

*/

    void set_string_keyed(PMC *key, STRING* value) {
        int type;
        char *p = char_offset_key(INTERP, pmc, key, &type);
        set_string(p, type, value);
    }

}

/*

=back

=head1 HISTORY

Initial revision by sean 2002/08/04

=head1 SEE ALSO

F<docs/pmc/struct.pod>

=cut

*/

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