#include "moar.h"

/* This representation's function pointer table. */
static const MVMREPROps SCRef_this_repr;

/* Creates a new type object of this representation, and associates it with
 * the given HOW. */
static MVMObject * type_object_for(MVMThreadContext *tc, MVMObject *HOW) {
    MVMSTable *st  = MVM_gc_allocate_stable(tc, &SCRef_this_repr, HOW);

    MVMROOT(tc, st, {
        MVMObject *obj = MVM_gc_allocate_type_object(tc, st);
        MVM_ASSIGN_REF(tc, &(st->header), st->WHAT, obj);
        st->size = sizeof(MVMSerializationContext);
    });

    return st->WHAT;
}

/* Initializes a new instance. */
static void initialize(MVMThreadContext *tc, MVMSTable *st, MVMObject *root, void *data) {
    MVMObject *root_codes, *rep_indexes, *rep_scs, *owned_objects, *rm;

    MVMInstance       *instance     = tc->instance;
    MVMObject         *BOOTIntArray = instance->boot_types.BOOTIntArray;
    MVMSerializationContextBody *sc = ((MVMSerializationContext *)root)->body;

    if (!sc) {
        /* XXX figure out what actually causes this */
        MVM_exception_throw_adhoc(tc, "Cannot initialize an SCRef with a null serialization context");
    }

    MVM_gc_root_temp_push(tc, (MVMCollectable **)&root);

    rep_indexes = REPR(BOOTIntArray)->allocate(tc, STABLE(BOOTIntArray));
    MVM_ASSIGN_REF(tc, &(root->header), sc->rep_indexes, rep_indexes);

    rm = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTReentrantMutex);
    MVM_ASSIGN_REF(tc, &(root->header), sc->mutex, rm);

    root_codes = REPR(instance->boot_types.BOOTArray)->allocate(tc, STABLE(instance->boot_types.BOOTArray));
    MVM_ASSIGN_REF(tc, &(root->header), sc->root_codes, root_codes);

    rep_scs = REPR(instance->boot_types.BOOTArray)->allocate(tc, STABLE(instance->boot_types.BOOTArray));
    MVM_ASSIGN_REF(tc, &(root->header), sc->rep_scs, rep_scs);

    owned_objects = REPR(instance->boot_types.BOOTArray)->allocate(tc, STABLE(instance->boot_types.BOOTArray));
    MVM_ASSIGN_REF(tc, &(root->header), sc->owned_objects, owned_objects);

    MVM_gc_root_temp_pop(tc);
}

/* Copies the body of one object to another. */
static void copy_to(MVMThreadContext *tc, MVMSTable *st, void *src, MVMObject *dest_root, void *dest) {
    MVM_exception_throw_adhoc(tc, "Cannot copy object with representation SCRef");
}

/* Called by the VM to mark any GCable items. */
static void SCRef_gc_mark(MVMThreadContext *tc, MVMSTable *st, void *data, MVMGCWorklist *worklist) {
    MVMSerializationContextBody *sc = ((MVMSerializationContextBody **)data)[0];
    MVMuint64 i;

    MVM_gc_worklist_add(tc, worklist, &sc->handle);
    MVM_gc_worklist_add(tc, worklist, &sc->description);
    MVM_gc_worklist_add(tc, worklist, &sc->root_codes);
    MVM_gc_worklist_add(tc, worklist, &sc->rep_indexes);
    MVM_gc_worklist_add(tc, worklist, &sc->rep_scs);
    MVM_gc_worklist_add(tc, worklist, &sc->owned_objects);
    /* Add it using more optimized code when we have to add a large number of things in
     * a loop. */
    MVM_gc_worklist_presize_for(tc, worklist, sc->num_objects + sc->num_stables);
    if (worklist->include_gen2) {
        for (i = 0; i < sc->num_objects; i++)
            MVM_gc_worklist_add_include_gen2_nocheck(tc, worklist, &sc->root_objects[i]);
        for (i = 0; i < sc->num_stables; i++)
            MVM_gc_worklist_add_include_gen2_nocheck(tc, worklist, &sc->root_stables[i]);
    }
    else {
        for (i = 0; i < sc->num_objects; i++)
            MVM_gc_worklist_add_no_include_gen2_nocheck(tc, worklist, &sc->root_objects[i]);
        for (i = 0; i < sc->num_stables; i++)
            MVM_gc_worklist_add_no_include_gen2_nocheck(tc, worklist, &sc->root_stables[i]);
    }

    MVM_gc_worklist_add(tc, worklist, &sc->sc);
    MVM_gc_worklist_add(tc, worklist, &sc->mutex);

    /* Mark serialization reader, if we have one. */
    if (sc->sr) {
        MVM_gc_worklist_presize_for(tc, worklist,  sc->sr->root.num_dependencies + sc->sr->root.num_contexts);
        if (worklist->include_gen2) {
            for (i = 0; i < sc->sr->root.num_dependencies; i++)
                MVM_gc_worklist_add_include_gen2_nocheck(tc, worklist, &(sc->sr->root.dependent_scs[i]));
            for (i = 0; i < sc->sr->root.num_contexts; i++)
                MVM_gc_worklist_add_include_gen2_nocheck(tc, worklist, &(sc->sr->contexts[i]));
        }
        else {
            for (i = 0; i < sc->sr->root.num_dependencies; i++)
                MVM_gc_worklist_add_no_include_gen2_nocheck(tc, worklist, &(sc->sr->root.dependent_scs[i]));
            for (i = 0; i < sc->sr->root.num_contexts; i++)
                MVM_gc_worklist_add_no_include_gen2_nocheck(tc, worklist, &(sc->sr->contexts[i]));
        }
        MVM_gc_worklist_add(tc, worklist, &(sc->sr->root.sc));
        MVM_gc_worklist_add(tc, worklist, &(sc->sr->root.string_heap));
        MVM_gc_worklist_add(tc, worklist, &(sc->sr->root.string_comp_unit));
        MVM_gc_worklist_add(tc, worklist, &(sc->sr->codes_list));
        MVM_gc_worklist_add(tc, worklist, &(sc->sr->current_object));

    }
}

/* Called by the VM in order to free memory associated with this object. */
static void gc_free(MVMThreadContext *tc, MVMObject *obj) {
    MVMSerializationContext *sc = (MVMSerializationContext *)obj;

    if (sc->body == NULL)
        return;

    /* Remove from weakref lookup hash (which doesn't count as a root). */
    uv_mutex_lock(&tc->instance->mutex_sc_registry);
    /* sc->body->handle is only written to by code that has already validated
     * that handle is a concrete string. */
    MVM_str_hash_delete_nocheck(tc, &tc->instance->sc_weakhash, sc->body->handle);
    tc->instance->all_scs[sc->body->sc_idx] = NULL;
    uv_mutex_unlock(&tc->instance->mutex_sc_registry);

    /* Free manually managed object and STable root list memory. */
    MVM_free(sc->body->root_objects);
    MVM_free(sc->body->root_stables);
    MVM_free(sc->body->param_intern_lookup);
    MVM_free(sc->body->param_intern_st_lookup);

    /* If we have a serialization reader, clean that up too. */
    if (sc->body->sr) {
        if (sc->body->sr->data_needs_free)
            MVM_free(sc->body->sr->data);
        MVM_free(sc->body->sr->root.dependent_scs);
        MVM_free(sc->body->sr->contexts);
        MVM_free(sc->body->sr->wl_objects.indexes);
        MVM_free(sc->body->sr->wl_stables.indexes);
        MVM_free(sc->body->sr);
    }

    /* Free body. */
    MVM_free(sc->body);
}

static const MVMStorageSpec storage_spec = {
    MVM_STORAGE_SPEC_REFERENCE, /* inlineable */
    0,                          /* bits */
    0,                          /* align */
    MVM_STORAGE_SPEC_BP_NONE,   /* boxed_primitive */
    0,                          /* can_box */
    0,                          /* is_unsigned */
};

/* Gets the storage specification for this representation. */
static const MVMStorageSpec * get_storage_spec(MVMThreadContext *tc, MVMSTable *st) {
    return &storage_spec;
}

/* Compose the representation. */
static void compose(MVMThreadContext *tc, MVMSTable *st, MVMObject *info) {
    /* Nothing to do for this REPR. */
}

static MVMuint64 unmanaged_size(MVMThreadContext *tc, MVMSTable *st, void *data) {
    MVMSerializationContextBody     *body      = ((MVMSerializationContextBody **)data)[0];
    MVMuint64 size = 0;

    size += sizeof(MVMObject *) * body->num_objects;
    size += sizeof(MVMSTable *) * body->num_stables;

    /* XXX probably have to measure the MVMSerializationReader, too */

    return size;
}

static void describe_refs(MVMThreadContext *tc, MVMHeapSnapshotState *ss, MVMSTable *st, void *data) {
    MVMSerializationContextBody *body = ((MVMSerializationContextBody **)data)[0];
    MVMuint64 index;

    if (body->sr)
        return;

    for (index = 0; index < body->num_objects; index++)
        MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
            (MVMCollectable *)body->root_objects[index], "Object root set");
    for (index = 0; index < body->num_stables; index++)
        MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
            (MVMCollectable *)body->root_stables[index], "STable root set");

    MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
        (MVMCollectable *)body->root_codes, "Root code refs");
    MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
        (MVMCollectable *)body->rep_indexes, "Repossession indices");
    MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
        (MVMCollectable *)body->rep_scs, "Repossession SCs");
    MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
        (MVMCollectable *)body->owned_objects, "Owned Objects");

    MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
        (MVMCollectable *)body->handle, "Handle");
    MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
        (MVMCollectable *)body->description, "Description");
    MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
        (MVMCollectable *)body->sc, "SC");
    MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
        (MVMCollectable *)body->mutex, "Mutex");

    /* Mark serialization reader, if we have one. */
    if (body->sr) {
        MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
            (MVMCollectable *)body->sr->root.sc, "Reader Root SC");
        for (index = 0; index < body->sr->root.num_dependencies; index++)
            MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
                (MVMCollectable *)body->sr->root.dependent_scs[index], "SC Dependency (Reader)");
        MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
            (MVMCollectable *)body->sr->root.string_heap, "String heap (Reader)");
        MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
            (MVMCollectable *)body->sr->root.string_comp_unit, "String compilation unit (Reader)");
        MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
            (MVMCollectable *)body->sr->codes_list, "Code objects list (Reader)");
        MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss,
            (MVMCollectable *)body->sr->current_object, "Current object (Reader)");
    }
}

/* Initializes the representation. */
const MVMREPROps * MVMSCRef_initialize(MVMThreadContext *tc) {
    return &SCRef_this_repr;
}

static const MVMREPROps SCRef_this_repr = {
    type_object_for,
    MVM_gc_allocate_object,
    initialize,
    copy_to,
    MVM_REPR_DEFAULT_ATTR_FUNCS,
    MVM_REPR_DEFAULT_BOX_FUNCS,
    MVM_REPR_DEFAULT_POS_FUNCS,
    MVM_REPR_DEFAULT_ASS_FUNCS,
    MVM_REPR_DEFAULT_ELEMS,
    get_storage_spec,
    NULL, /* change_type */
    NULL, /* serialize */
    NULL, /* deserialize */
    NULL, /* serialize_repr_data */
    NULL, /* deserialize_repr_data */
    NULL, /* deserialize_stable_size */
    SCRef_gc_mark,
    gc_free,
    NULL, /* gc_cleanup */
    NULL, /* gc_mark_repr_data */
    NULL, /* gc_free_repr_data */
    compose,
    NULL, /* spesh */
    "SCRef", /* name */
    MVM_REPR_ID_SCRef,
    unmanaged_size,
    describe_refs,
};
