/*
Copyright (C) 2001-2010, Parrot Foundation.
$Id: packfileannotations.pmc 46369 2010-05-06 17:18:01Z NotFound $

=head1 NAME

src/pmc/packfileannotations.pmc - PackfileAnnotations PMC

=head1 DESCRIPTION

This class implements a PackfileAnnotations object, a segment of the .pbc data
file used for listing annotations.  It is a container for PackfileAnnotation
objects.

See packfile.pmc for the toplevel Packfile interface, see packfilesegment.pmc
for the list of common methods every packfile segment pmc must implement; see
PDD13 for the design spec.

To works properly PackfileAnnotations has to be added to PackfileDirectory with
PackfileConstantTable. Otherwise PackfileAnnotationKey can't be created.

=head2 Methods

=over 4

=cut

*/

#include "pmc/pmc_packfileannotation.h"

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

pmclass PackfileAnnotations auto_attrs extends PackfileSegment {
    /* ConstantTable used for names lookup */
    ATTR PMC *const_table;

    /* Annotations group: two RIA for a now */
    ATTR PMC *gr_byte;
    ATTR PMC *gr_entries;
    /* RPA of Annotation */
    ATTR PMC *annotations;

/*

=item C<void init()>

Initialize PackfileAnnotations.

=cut

*/
    VTABLE void init() {
        Parrot_PackfileAnnotations_attributes * attrs =
                PMC_data_typed(SELF, Parrot_PackfileAnnotations_attributes*);

        attrs->annotations = Parrot_pmc_new(INTERP, enum_class_ResizablePMCArray);
        /*
        Don't initialize C<const_table>. It will be set to NULL bu auto_attrs
        handling. And should be set to proper PMC in set_directory.
        */

        PObj_custom_mark_SET(SELF);
    }

/*

=item C<void mark()>

Marks the object as live.

=cut

*/

    VTABLE void mark() {
        Parrot_PackfileAnnotations_attributes * attrs =
                PARROT_PACKFILEANNOTATIONS(SELF);

        Parrot_gc_mark_PMC_alive(INTERP, attrs->const_table);
        Parrot_gc_mark_PMC_alive(INTERP, attrs->annotations);

        SUPER();
    }


/*

=item C<INTVAL elements()>

Get the number of elements in the array.

=cut

*/
    VTABLE INTVAL elements() {
        return VTABLE_elements(INTERP,
                PARROT_PACKFILEANNOTATIONS(SELF)->annotations);
    }


/*

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

Fetch an annotation PMC from the array.

=cut

*/
    VTABLE PMC *get_pmc_keyed_int(INTVAL index)  {
        return VTABLE_get_pmc_keyed_int(INTERP,
                PARROT_PACKFILEANNOTATIONS(SELF)->annotations, index);
    }


/*

=item C<void set_pmc_keyed_int(INTVAL index, PMC *annotation)>

Add an annotation to the array at the given offset.  An exception will be
thrown unless all of the following criteria are met:

=over 4

=item - The type of the PMC passed is PackfileAnnotation

=item - The entry at the previous index is defined

=item - The offset of the previous entry is less than this entry

=item - The offset of the next entry, if it exists, is greater than this entry

=item - The key ID references a valid annotation key

=back

=cut

*/
    VTABLE void set_pmc_keyed_int(INTVAL index, PMC *annotation)  {
        Parrot_PackfileAnnotations_attributes * attrs =
                PARROT_PACKFILEANNOTATIONS(SELF);
        Parrot_PackfileAnnotation_attributes  * entity_attrs =
                PARROT_PACKFILEANNOTATION(annotation);
        INTVAL dummy;

        /* TODO: add checks desribed above */
        VTABLE_set_pmc_keyed_int(INTERP, attrs->annotations, index, annotation);

        /* Add required constants */
        Parrot_mmd_multi_dispatch_from_c_args(INTERP,
            "get_or_create_constant", "PS->I", attrs->const_table,
            entity_attrs->name, &dummy);

        switch (entity_attrs->value_type) {
            case PF_ANNOTATION_KEY_TYPE_STR:
                Parrot_mmd_multi_dispatch_from_c_args(INTERP,
                    "get_or_create_constant", "PS->I", attrs->const_table,
                    entity_attrs->str_value, &dummy);
                break;

            case PF_ANNOTATION_KEY_TYPE_NUM:
                Parrot_mmd_multi_dispatch_from_c_args(INTERP,
                    "get_or_create_constant", "PN->I", attrs->const_table,
                    entity_attrs->num_value, &dummy);
                break;

            default:
                /* Do nothing. If value_type if INT it will be stored directly */
                break;
        }
    }

/*

=item C<void set_directory()>

Handle setting of ownership.

Find PackfileConstantTable in PackfileDirectory and pass it to
PackfileAnnotationKeys.

=cut

*/

    METHOD set_directory(PMC *directory) {
        Parrot_PackfileAnnotations_attributes *attrs =
                PARROT_PACKFILEANNOTATIONS(SELF);
        STRING *name;
        PMC    *segment;

        PMC *iter = VTABLE_get_iter(INTERP, directory);
        while (VTABLE_get_bool(INTERP, iter)) {
            name = VTABLE_shift_string(INTERP, iter);
            segment = VTABLE_get_pmc_keyed_str(INTERP, directory, name);
            if (VTABLE_isa(INTERP, segment,
                    Parrot_str_new_constant(INTERP, "PackfileConstantTable"))) {
                attrs->const_table = segment;
                break;
            }
        }
    }

/*

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

Initialize PackfileAnnotations from PackFile_Annotations*.

=cut

*/
    VTABLE void set_pointer(void *pointer) {
        PackFile_Annotations * a = (PackFile_Annotations*)pointer;
        Parrot_PackfileAnnotations_attributes *attrs =
                PARROT_PACKFILEANNOTATIONS(SELF);
        opcode_t  i;
        PMC      *annotation;
        Parrot_PackfileAnnotation_attributes *annotation_attrs;

        /* Copy annotations groups */
        if (a->num_groups > 0) {
            attrs->gr_byte  = Parrot_pmc_new_init_int(INTERP,
                    enum_class_ResizableIntegerArray, a->num_groups);
            attrs->gr_entries = Parrot_pmc_new_init_int(INTERP,
                    enum_class_ResizableIntegerArray, a->num_groups);
            for (i = 0; i < a->num_groups; ++i) {
                VTABLE_set_integer_keyed_int(INTERP, attrs->gr_byte, i,
                        a->groups[i].bytecode_offset);
                VTABLE_set_integer_keyed_int(INTERP, attrs->gr_entries, i,
                        a->groups[i].entries_offset);
            }
        }

        /* Copy annotations to own array */
        VTABLE_set_integer_native(INTERP, attrs->annotations, a->num_entries);
        for (i = 0; i < a->num_entries; ++i) {
            PackFile_Annotations_Entry *entry = a->entries + i;
            PackFile_Annotations_Key   *key   = a->keys + entry->key;
            annotation = Parrot_pmc_new(INTERP, enum_class_PackfileAnnotation);

            /* Poke directly to annotation attributes. */
            annotation_attrs         = PARROT_PACKFILEANNOTATION(annotation);
            annotation_attrs->offset = entry->bytecode_offset;
            if (! attrs->const_table)
                Parrot_ex_throw_from_c_args(INTERP, NULL,
                        EXCEPTION_MALFORMED_PACKFILE, "No constant table");

            annotation_attrs->name   = VTABLE_get_string_keyed_int(INTERP,
                    attrs->const_table, key->name);
            switch (key->type) {
                case PF_ANNOTATION_KEY_TYPE_INT:
                    VTABLE_set_integer_native(INTERP, annotation, entry->value);
                    break;
                case PF_ANNOTATION_KEY_TYPE_STR:
                    VTABLE_set_string_native(INTERP, annotation,
                        VTABLE_get_string_keyed_int(INTERP, attrs->const_table, entry->value));
                    break;
                case PF_ANNOTATION_KEY_TYPE_NUM:
                    VTABLE_set_number_native(INTERP, annotation,
                        VTABLE_get_number_keyed_int(INTERP, attrs->const_table, entry->value));
                    break;
                default:
                    Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_MALFORMED_PACKFILE,
                            "Unknown value type %d in Packfile Annotation", key->type);
            }

            VTABLE_set_pmc_keyed_int(INTERP, attrs->annotations, i, annotation);
        }
    }

/*

=item C<void *get_pointer()>

Create PackFile_Annotations* from self.

=cut

*/
    VTABLE void *get_pointer() {
        Parrot_PackfileAnnotations_attributes *attrs =
                PARROT_PACKFILEANNOTATIONS(SELF);
        PackFile_Annotations *res = mem_gc_allocate_zeroed_typed(INTERP,
                PackFile_Annotations);
        INTVAL                i, num;
        PMC                  *keys;             /* Temporary representation of Keys */
        PMC                  *names;            /* Constants for Key's names */
        PMC                  *types;            /* Types of Keys */
        INTVAL                key_id;
        INTVAL                last_key_id;
        INTVAL                name_id;

        res->base.type = PF_ANNOTATIONS_SEG;

        /* Create annotations groups */
        num = PMC_IS_NULL(attrs->gr_byte) ?
            (INTVAL)0 : VTABLE_elements(INTERP, attrs->gr_byte);
        res->num_groups = num;
        if (num > 0) {
            res->groups = mem_gc_allocate_n_zeroed_typed(interp,
                    num, PackFile_Annotations_Group);
            for (i = 0; i < num; ++i) {
                res->groups[i].bytecode_offset =
                        VTABLE_get_integer_keyed_int(INTERP, attrs->gr_byte, i);
                res->groups[i].entries_offset =
                        VTABLE_get_integer_keyed_int(INTERP, attrs->gr_entries, i);
            }
        }

        /*
            Create keys. Hash will be created in next structure:
            keys => {
                name => [
                    $int_key_id,
                    $str_key_id,
                    $num_key_id,
                ]
            }

            Each key has name from ConstantTable. We store them in names Array.
            names => [
                $constant_id_1,
                $contsant_id_2,
                ...
            ]
        */
        keys        = Parrot_pmc_new(INTERP, enum_class_Hash);
        names       = Parrot_pmc_new(INTERP, enum_class_ResizableIntegerArray);
        types       = Parrot_pmc_new(INTERP, enum_class_ResizableIntegerArray);
        last_key_id = -1;

        /* Iterate over stored annotations and create Key if required. */
        num = VTABLE_elements(INTERP, attrs->annotations);
        res->num_entries = num;
        res->entries = mem_gc_allocate_n_typed(INTERP, num, PackFile_Annotations_Entry);
        for (i = 0; i < num; ++i) {
            PMC  * const entity = VTABLE_get_pmc_keyed_int(INTERP, attrs->annotations, i);
            const Parrot_PackfileAnnotation_attributes * const entity_attrs =
                PARROT_PACKFILEANNOTATION(entity);

            /* Handle creating of Key */
            PMC * key_array = VTABLE_get_pmc_keyed_str(INTERP, keys, entity_attrs->name);
            if (PMC_IS_NULL(key_array)) {
                /* Never see this name before. Create new FIA and add to keys. */
                key_array = Parrot_pmc_new_init_int(INTERP, enum_class_FixedIntegerArray, 3);
                VTABLE_set_integer_keyed_int(INTERP, key_array, 0, -1);
                VTABLE_set_integer_keyed_int(INTERP, key_array, 1, -1);
                VTABLE_set_integer_keyed_int(INTERP, key_array, 2, -1);

                VTABLE_set_pmc_keyed_str(INTERP, keys, entity_attrs->name, key_array);
            }

            /* PackfileAnnotation.value_type exactly the same as key.type */
            key_id = VTABLE_get_integer_keyed_int(INTERP, key_array, entity_attrs->value_type);

            /* If key_id is -1 it means we have to create new key. */
            if (key_id == -1) {
                key_id = ++last_key_id;
                VTABLE_set_integer_keyed_int(INTERP, key_array, entity_attrs->value_type, key_id);

                /* Store type */
                VTABLE_set_integer_keyed_int(INTERP, types, key_id, entity_attrs->value_type);


                /* Store constant for name. */
                Parrot_mmd_multi_dispatch_from_c_args(INTERP, "get_or_create_constant",
                            "PS->I", attrs->const_table, entity_attrs->name, &name_id);

                VTABLE_set_integer_keyed_int(INTERP, names, key_id, name_id);
            }

            /* At this point we have create (if nesassary) key and name constant. */
            /* Start storing real entity */

            res->entries[i].bytecode_offset = entity_attrs->offset;
            res->entries[i].key             = key_id;

            /* Handle value */
            switch (entity_attrs->value_type) {
                case PF_ANNOTATION_KEY_TYPE_INT:
                    res->entries[i].value = entity_attrs->int_value;
                    break;
                case PF_ANNOTATION_KEY_TYPE_STR:
                    Parrot_mmd_multi_dispatch_from_c_args(INTERP, "get_or_create_constant",
                            "PS->I", attrs->const_table, entity_attrs->str_value,
                            &res->entries[i].value);
                    break;
                case PF_ANNOTATION_KEY_TYPE_NUM:
                    Parrot_mmd_multi_dispatch_from_c_args(INTERP, "get_or_create_constant",
                            "PN->I", attrs->const_table, entity_attrs->num_value,
                            &res->entries[i].value);
                    break;
                default:
                    Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_MALFORMED_PACKFILE,
                            "Unknown value type in PackfileAnnotation");
            }
        }

        /* Time to pack keys */
        num = VTABLE_elements(INTERP, types);
        res->num_keys = num;
        res->keys = mem_gc_allocate_n_zeroed_typed(INTERP, num,
                PackFile_Annotations_Key);
        for (i = 0; i < num; ++i) {
            res->keys[i].name = VTABLE_get_integer_keyed_int(INTERP, names, i);
            res->keys[i].type = VTABLE_get_integer_keyed_int(INTERP, types, i);
        }

        return res;
    }

/*

=item C<METHOD type()>

Get segment type.

=cut

*/

    METHOD type() {
        RETURN(INTVAL PF_ANNOTATIONS_SEG);
    }

}

/*

=back

=cut

*/

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