#include "moar.h"

/* This representation's function pointer table. */
static const MVMREPROps SpeshLog_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, &SpeshLog_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(MVMSpeshLog);
    }

    return st->WHAT;
}

/* Initializes the log. */
static void initialize(MVMThreadContext *tc, MVMSTable *st, MVMObject *root, void *data) {
    MVMSpeshLogBody *log = (MVMSpeshLogBody *)data;
    log->entries = MVM_malloc(sizeof(MVMSpeshLogEntry) * MVM_SPESH_LOG_DEFAULT_ENTRIES);
    log->limit = MVM_SPESH_LOG_DEFAULT_ENTRIES;
}

/* 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 SpeshLog");
}

/* Called by the VM to mark any GCable items. */
static void gc_mark(MVMThreadContext *tc, MVMSTable *st, void *data, MVMGCWorklist *worklist) {
    MVMSpeshLogBody *log = (MVMSpeshLogBody *)data;
    MVMuint32 i;
    MVM_gc_worklist_add(tc, worklist, &(log->thread));
    if (!log->entries)
        return;
    for (i = 0; i < log->used; i++) {
        switch (log->entries[i].kind) {
            case MVM_SPESH_LOG_ENTRY:
                MVM_gc_worklist_add(tc, worklist, &(log->entries[i].entry.sf));
                break;
            case MVM_SPESH_LOG_PARAMETER:
            case MVM_SPESH_LOG_PARAMETER_DECONT:
                MVM_gc_worklist_add(tc, worklist, &(log->entries[i].param.type));
                break;
            case MVM_SPESH_LOG_TYPE:
            case MVM_SPESH_LOG_RETURN:
                MVM_gc_worklist_add(tc, worklist, &(log->entries[i].type.type));
                break;
            case MVM_SPESH_LOG_INVOKE:
                MVM_gc_worklist_add(tc, worklist, &(log->entries[i].invoke.sf));
                break;
        }
    }
}

static void describe_refs (MVMThreadContext *tc, MVMHeapSnapshotState *ss, MVMSTable *st, void *data) {
    MVMSpeshLogBody  *body      = (MVMSpeshLogBody *)data;
    MVMuint64         i         = 0;

    MVMuint64 cache_1 = 0;
    MVMuint64 cache_2 = 0;
    MVMuint64 cache_3 = 0;
    MVMuint64 cache_4 = 0;
    MVMuint64 cache_5 = 0;
    MVMuint64 cache_6 = 0;

    if (!body->entries)
        return;
    for (i = 0; i < body->used; i++) {
        switch (body->entries[i].kind) {
            case MVM_SPESH_LOG_ENTRY:
                MVM_profile_heap_add_collectable_rel_const_cstr_cached(tc, ss,
                    (MVMCollectable *)body->entries[i].entry.sf, "Spesh log entry", &cache_1);
                break;
            case MVM_SPESH_LOG_PARAMETER:
                MVM_profile_heap_add_collectable_rel_const_cstr_cached(tc, ss,
                    (MVMCollectable *)body->entries[i].param.type, "Parameter entry", &cache_2);
                break;
            case MVM_SPESH_LOG_PARAMETER_DECONT:
                MVM_profile_heap_add_collectable_rel_const_cstr_cached(tc, ss,
                    (MVMCollectable *)body->entries[i].param.type, "Deconted parameter entry", &cache_3);
                break;
            case MVM_SPESH_LOG_TYPE:
                MVM_profile_heap_add_collectable_rel_const_cstr_cached(tc, ss,
                    (MVMCollectable *)body->entries[i].type.type, "Type entry", &cache_4);
                break;
            case MVM_SPESH_LOG_RETURN:
                MVM_profile_heap_add_collectable_rel_const_cstr_cached(tc, ss,
                    (MVMCollectable *)body->entries[i].type.type, "Return entry", &cache_5);
                break;
            case MVM_SPESH_LOG_INVOKE:
                MVM_profile_heap_add_collectable_rel_const_cstr_cached(tc, ss,
                    (MVMCollectable *)body->entries[i].invoke.sf, "Invoked staticframe entry", &cache_6);
                break;
        }
    }
}

/* Called by the VM in order to free memory associated with this object. */
static void gc_free(MVMThreadContext *tc, MVMObject *obj) {
    MVMSpeshLog *log = (MVMSpeshLog *)obj;
    MVM_free(log->body.entries);
    if (log->body.block_condvar) {
        uv_cond_destroy(log->body.block_condvar);
        MVM_free(log->body.block_condvar);
    }
    if (log->body.block_mutex) {
        uv_mutex_destroy(log->body.block_mutex);
        MVM_free(log->body.block_mutex);
    }
}

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. */
}

/* Set the size of the STable. */
static void deserialize_stable_size(MVMThreadContext *tc, MVMSTable *st, MVMSerializationReader *reader) {
    st->size = sizeof(MVMSpeshLog);
}

static MVMuint64 unmanaged_size(MVMThreadContext *tc, MVMSTable *st, void *data) {
    MVMSpeshLogBody *log = (MVMSpeshLogBody *)data;
    return log->limit * sizeof(MVMSpeshLogEntry);
}

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

static const MVMREPROps SpeshLog_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 */
    deserialize_stable_size,
    gc_mark,
    gc_free,
    NULL, /* gc_cleanup */
    NULL, /* gc_mark_repr_data */
    NULL, /* gc_free_repr_data */
    compose,
    NULL, /* spesh */
    "MVMSpeshLog", /* name */
    MVM_REPR_ID_MVMSpeshLog,
    unmanaged_size,
    describe_refs
};
