#include "moar.h"

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

    return st->WHAT;
}

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

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

/* Serializes the REPR data. */
static void serialize_repr_data(MVMThreadContext *tc, MVMSTable *st, MVMSerializationWriter *writer) {
    MVMNativeRefREPRData *repr_data = (MVMNativeRefREPRData *)st->REPR_data;
    if (repr_data) {
        MVM_serialization_write_int(tc, writer, repr_data->primitive_type);
        MVM_serialization_write_int(tc, writer, repr_data->ref_kind);
        MVM_serialization_write_int(tc, writer, repr_data->is_unsigned);
    }
    else {
        MVM_serialization_write_int(tc, writer, 0);
        MVM_serialization_write_int(tc, writer, 0);
        MVM_serialization_write_int(tc, writer, 0);
    }
}

/* Deserializes REPR data. */
static void deserialize_repr_data(MVMThreadContext *tc, MVMSTable *st, MVMSerializationReader *reader) {
    MVMNativeRefREPRData *repr_data = MVM_malloc(sizeof(MVMNativeRefREPRData));
    repr_data->primitive_type = MVM_serialization_read_int(tc, reader);
    repr_data->ref_kind       = MVM_serialization_read_int(tc, reader);
    repr_data->is_unsigned    = MVM_serialization_read_int(tc, reader);
    st->REPR_data = repr_data;
}

/* Called by the VM to mark any GCable items. */
static void gc_mark(MVMThreadContext *tc, MVMSTable *st, void *data, MVMGCWorklist *worklist) {
    MVMNativeRefBody *ref = (MVMNativeRefBody *)data;
    MVMNativeRefREPRData *repr_data = (MVMNativeRefREPRData *)st->REPR_data;
    switch (repr_data->ref_kind) {
        case MVM_NATIVEREF_LEX:
            MVM_gc_worklist_add(tc, worklist, &ref->u.lex.frame);
            break;
        case MVM_NATIVEREF_ATTRIBUTE:
            MVM_gc_worklist_add(tc, worklist, &ref->u.attribute.obj);
            MVM_gc_worklist_add(tc, worklist, &ref->u.attribute.class_handle);
            MVM_gc_worklist_add(tc, worklist, &ref->u.attribute.name);
            break;
        case MVM_NATIVEREF_POSITIONAL:
            MVM_gc_worklist_add(tc, worklist, &ref->u.positional.obj);
            break;
        case MVM_NATIVEREF_MULTIDIM:
            MVM_gc_worklist_add(tc, worklist, &ref->u.multidim.obj);
            MVM_gc_worklist_add(tc, worklist, &ref->u.multidim.indices);
            break;
    }
}

/* Frees the representation data, if any. */
static void gc_free_repr_data(MVMThreadContext *tc, MVMSTable *st) {
    MVM_free(st->REPR_data);
}

/* Gets the storage specification for this representation. */
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 */
};
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_hash) {
    MVMStringConsts *str_consts = &(tc->instance->str_consts);
    MVMObject *info = MVM_repr_at_key_o(tc, info_hash, str_consts->nativeref);
    if (IS_CONCRETE(info)) {
        MVMObject *type    = MVM_repr_at_key_o(tc, info, str_consts->type);
        const MVMStorageSpec *spec = REPR(type)->get_storage_spec(tc, STABLE(type));
        MVMuint16  prim    = spec->boxed_primitive;
        if (prim != MVM_STORAGE_SPEC_BP_NONE) {
            MVMObject *refkind = MVM_repr_at_key_o(tc, info, str_consts->refkind);
            if (IS_CONCRETE(refkind)) {
                MVMNativeRefREPRData *repr_data;
                MVMuint16 kind;
                MVMString *refkind_s = MVM_repr_get_str(tc, refkind);
                if (MVM_string_equal(tc, refkind_s, str_consts->lexical)) {
                    kind = MVM_NATIVEREF_LEX;
                }
                else if (MVM_string_equal(tc, refkind_s, str_consts->attribute)) {
                    kind = MVM_NATIVEREF_ATTRIBUTE;
                }
                else if (MVM_string_equal(tc, refkind_s, str_consts->positional)) {
                    kind = MVM_NATIVEREF_POSITIONAL;
                }
                else if (MVM_string_equal(tc, refkind_s, str_consts->multidim)) {
                    kind = MVM_NATIVEREF_MULTIDIM;
                }
                else {
                    MVM_exception_throw_adhoc(tc, "NativeRef: invalid refkind in compose");
                }

                repr_data = MVM_malloc(sizeof(MVMNativeRefREPRData));
                repr_data->primitive_type = prim;
                repr_data->ref_kind       = kind;
                repr_data->is_unsigned    = spec->is_unsigned;
                st->REPR_data             = repr_data;
            }
            else {
                MVM_exception_throw_adhoc(tc, "NativeRef: missing refkind in compose");
            }
        }
        else {
            MVM_exception_throw_adhoc(tc, "NativeRef: non-native type supplied in compose");
        }
    }
    else {
        MVM_exception_throw_adhoc(tc, "NativeRef: missing nativeref protocol in compose");
    }
}

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

static void spesh(MVMThreadContext *tc, MVMSTable *st, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
    MVMNativeRefREPRData * repr_data = (MVMNativeRefREPRData *)st->REPR_data;

    if (!repr_data)
        return;
    /* TODO re-implement spesh for this; lost due to native ref refactors */
}

static const MVMREPROps NativeRef_this_repr = {
    type_object_for,
    MVM_gc_allocate_object,
    NULL, /* 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 */
    serialize_repr_data,
    deserialize_repr_data,
    deserialize_stable_size,
    gc_mark,
    NULL, /* gc_free */
    NULL, /* gc_cleanup */
    NULL, /* gc_mark_repr_data */
    gc_free_repr_data,
    compose,
    spesh, /* spesh */
    "NativeRef", /* name */
    MVM_REPR_ID_NativeRef,
    NULL, /* unmanaged_size */
    NULL, /* describe_refs */
};

/* Validates the given type is a native reference of the required primitive
 * type and reference kind. */
void MVM_nativeref_ensure(MVMThreadContext *tc, MVMObject *type, MVMuint16 wantprim, MVMuint16 wantkind, char *guilty) {
    if (REPR(type)->ID == MVM_REPR_ID_NativeRef) {
        MVMNativeRefREPRData *repr_data = (MVMNativeRefREPRData *)STABLE(type)->REPR_data;
        if (!repr_data)
            MVM_exception_throw_adhoc(tc, "%s set to NativeRef that is not yet composed", guilty);
        if (repr_data->primitive_type != wantprim)
            MVM_exception_throw_adhoc(tc, "%s set to NativeRef of wrong primitive type, got %d, expected %d", guilty, repr_data->primitive_type, wantprim);
        if (repr_data->ref_kind != wantkind)
            MVM_exception_throw_adhoc(tc, "%s set to NativeRef of wrong reference kind", guilty);
    }
    else {
        MVM_exception_throw_adhoc(tc, "%s requires a type with REPR NativeRef", guilty);
    }
}

/* Creation of native references for lexicals. */
static MVMObject * lex_ref(MVMThreadContext *tc, MVMObject *type, MVMFrame *f,
                           MVMuint16 env_idx, MVMuint16 reg_type) {
    MVMNativeRef *ref;
    MVMROOT(tc, f) {
        ref = (MVMNativeRef *)MVM_gc_allocate_object(tc, STABLE(type));
    }
    MVM_ASSIGN_REF(tc, &(ref->common.header), ref->body.u.lex.frame, f);
    ref->body.u.lex.env_idx = env_idx;
    ref->body.u.lex.type = reg_type;
    return (MVMObject *)ref;
}

/* Creation of native references for lexicals. */
static MVMFrame * get_lexical_outer(MVMThreadContext *tc, MVMuint16 outers) {
    MVMFrame *f = tc->cur_frame;
    while (outers) {
        if (!f)
            MVM_exception_throw_adhoc(tc, "getlexref_*: outer index out of range");
        f = f->outer;
        outers--;
    }
    return f;
}
MVMObject * MVM_nativeref_lex_i(MVMThreadContext *tc, MVMuint16 outers, MVMuint16 idx) {
    MVMObject *ref_type;
    MVM_frame_force_to_heap(tc, tc->cur_frame);
    ref_type = MVM_hll_current(tc)->int_lex_ref;
    if (ref_type) {
        MVMFrame  *f = get_lexical_outer(tc, outers);
        MVMuint16 *lexical_types = f->spesh_cand && f->spesh_cand->body.lexical_types
            ? f->spesh_cand->body.lexical_types
            : f->static_info->body.lexical_types;
        MVMuint16 type = lexical_types[idx];
        if (type != MVM_reg_int64 && type != MVM_reg_int32 &&
                type != MVM_reg_int16 && type != MVM_reg_int8 &&
                type != MVM_reg_uint64 && type != MVM_reg_uint32 &&
                type != MVM_reg_uint16 && type != MVM_reg_uint8)
            MVM_exception_throw_adhoc(tc, "getlexref_i: lexical is not an int");
        return lex_ref(tc, ref_type, f, idx, type);
    }
    MVM_exception_throw_adhoc(tc, "No int lexical reference type registered for current HLL");
}
MVMObject * MVM_nativeref_lex_u(MVMThreadContext *tc, MVMuint16 outers, MVMuint16 idx) {
    MVMObject *ref_type;
    MVM_frame_force_to_heap(tc, tc->cur_frame);
    ref_type = MVM_hll_current(tc)->uint_lex_ref;
    if (ref_type) {
        MVMFrame  *f = get_lexical_outer(tc, outers);
        MVMuint16 *lexical_types = f->spesh_cand && f->spesh_cand->body.lexical_types
            ? f->spesh_cand->body.lexical_types
            : f->static_info->body.lexical_types;
        MVMuint16 type = lexical_types[idx];
        if (type != MVM_reg_uint64 && type != MVM_reg_uint32 &&
                type != MVM_reg_uint16 && type != MVM_reg_uint8)
            MVM_exception_throw_adhoc(tc, "getlexref_u: lexical is not an uint");
        return lex_ref(tc, ref_type, f, idx, type);
    }
    MVM_exception_throw_adhoc(tc, "No uint lexical reference type registered for current HLL");
}
MVMObject * MVM_nativeref_lex_n(MVMThreadContext *tc, MVMuint16 outers, MVMuint16 idx) {
    MVMObject *ref_type;
    MVM_frame_force_to_heap(tc, tc->cur_frame);
    ref_type = MVM_hll_current(tc)->num_lex_ref;
    if (ref_type) {
        MVMFrame  *f = get_lexical_outer(tc, outers);
        MVMuint16 *lexical_types = f->spesh_cand && f->spesh_cand->body.lexical_types
            ? f->spesh_cand->body.lexical_types
            : f->static_info->body.lexical_types;
        MVMuint16 type = lexical_types[idx];
        if (type != MVM_reg_num64 && type != MVM_reg_num32)
            MVM_exception_throw_adhoc(tc, "getlexref_n: lexical is not a num");
        return lex_ref(tc, ref_type, f, idx, type);
    }
    MVM_exception_throw_adhoc(tc, "No num lexical reference type registered for current HLL");
}
MVMObject * MVM_nativeref_lex_s(MVMThreadContext *tc, MVMuint16 outers, MVMuint16 idx) {
    MVMObject *ref_type;
    MVM_frame_force_to_heap(tc, tc->cur_frame);
    ref_type = MVM_hll_current(tc)->str_lex_ref;
    if (ref_type) {
        MVMFrame  *f = get_lexical_outer(tc, outers);
        MVMuint16 *lexical_types = f->spesh_cand && f->spesh_cand->body.lexical_types
            ? f->spesh_cand->body.lexical_types
            : f->static_info->body.lexical_types;
        if (lexical_types[idx] != MVM_reg_str)
            MVM_exception_throw_adhoc(tc, "getlexref_s: lexical is not a str (%d, %d)", outers, idx);
        return lex_ref(tc, ref_type, f, idx, MVM_reg_str);
    }
    MVM_exception_throw_adhoc(tc, "No str lexical reference type registered for current HLL");
}
#define LEXREF_ANY_INT -1
static MVMObject * lexref_by_name(MVMThreadContext *tc, MVMObject *type, MVMString *name, MVMint16 kind) {
    MVMFrame *cur_frame = tc->cur_frame;
    while (cur_frame != NULL) {
        MVMuint32 idx = MVM_get_lexical_by_name(tc, cur_frame->static_info, name);
        if (idx != MVM_INDEX_HASH_NOT_FOUND) {
            MVMint16 lex_kind = cur_frame->static_info->body.lexical_types[idx];
            if (lex_kind == kind) {
                return lex_ref(tc, type, cur_frame, idx, kind);
            }
            /* If kind == LEXREF_ANY_INT we will allow any of the native int
             * types so we don't need functions for every single type. */
            else if (kind == LEXREF_ANY_INT) {
                switch (lex_kind) {
                case MVM_reg_int8:
                case MVM_reg_int16:
                case MVM_reg_int32:
                case MVM_reg_int64:
                case MVM_reg_uint8:
                case MVM_reg_uint16:
                case MVM_reg_uint32:
                case MVM_reg_uint64:
                    return lex_ref(tc, type, cur_frame, idx, lex_kind);
                }
            }
            {
                char *c_name = MVM_string_utf8_encode_C_string(tc, name);
                char *waste[] = { c_name, NULL };
                MVM_exception_throw_adhoc_free(tc, waste,
                    "Lexical with name '%s' has wrong type. real type %i wanted type %i",
                        c_name, cur_frame->static_info->body.lexical_types[idx], kind);
            }
        }
        cur_frame = cur_frame->outer;
    }
    {
        char *c_name = MVM_string_utf8_encode_C_string(tc, name);
        char *waste[] = { c_name, NULL };
        MVM_exception_throw_adhoc_free(tc, waste, "No lexical found with name '%s'",
            c_name);
    }
}
MVMObject * MVM_nativeref_lex_name_i(MVMThreadContext *tc, MVMString *name) {
    MVMObject *ref_type;
    MVMROOT(tc, name) {
        MVM_frame_force_to_heap(tc, tc->cur_frame);
    }
    ref_type = MVM_hll_current(tc)->int_lex_ref;
    if (ref_type)
        /* LEXREF_ANY_INT will allow int8..int64 as well as uint8..uint64 */
        return lexref_by_name(tc, ref_type, name, LEXREF_ANY_INT);
    MVM_exception_throw_adhoc(tc, "No int lexical reference type registered for current HLL");
}
MVMObject * MVM_nativeref_lex_name_u(MVMThreadContext *tc, MVMString *name) {
    MVMObject *ref_type;
    MVMROOT(tc, name) {
        MVM_frame_force_to_heap(tc, tc->cur_frame);
    }
    ref_type = MVM_hll_current(tc)->uint_lex_ref;
    if (ref_type)
        /* LEXREF_ANY_INT will allow int8..int64 as well as uint8..uint64 */
        return lexref_by_name(tc, ref_type, name, LEXREF_ANY_INT);
    MVM_exception_throw_adhoc(tc, "No uint lexical reference type registered for current HLL");
}
MVMObject * MVM_nativeref_lex_name_n(MVMThreadContext *tc, MVMString *name) {
    MVMObject *ref_type;
    MVMROOT(tc, name) {
        MVM_frame_force_to_heap(tc, tc->cur_frame);
    }
    ref_type = MVM_hll_current(tc)->num_lex_ref;
    if (ref_type)
        return lexref_by_name(tc, ref_type, name, MVM_reg_num64);
    MVM_exception_throw_adhoc(tc, "No num lexical reference type registered for current HLL");
}
MVMObject * MVM_nativeref_lex_name_s(MVMThreadContext *tc, MVMString *name) {
    MVMObject *ref_type;
    MVMROOT(tc, name) {
        MVM_frame_force_to_heap(tc, tc->cur_frame);
    }
    ref_type = MVM_hll_current(tc)->str_lex_ref;
    if (ref_type)
        return lexref_by_name(tc, ref_type, name, MVM_reg_str);
    MVM_exception_throw_adhoc(tc, "No str lexical reference type registered for current HLL");
}

/* Creation of native references for attributes. */
static MVMObject * attrref(MVMThreadContext *tc, MVMObject *type, MVMObject *obj, MVMObject *class_handle, MVMString *name) {
    MVMNativeRef *ref;
    MVMROOT3(tc, obj, class_handle, name) {
        ref = (MVMNativeRef *)MVM_gc_allocate_object(tc, STABLE(type));
        MVM_ASSIGN_REF(tc, &(ref->common.header), ref->body.u.attribute.obj, obj);
        MVM_ASSIGN_REF(tc, &(ref->common.header), ref->body.u.attribute.class_handle, class_handle);
        MVM_ASSIGN_REF(tc, &(ref->common.header), ref->body.u.attribute.name, name);
    }
    return (MVMObject *)ref;
}
MVMObject * MVM_nativeref_attr_i(MVMThreadContext *tc, MVMObject *obj, MVMObject *class_handle, MVMString *name) {
    MVMObject *ref_type = MVM_hll_current(tc)->int_attr_ref;
    if (ref_type)
        return attrref(tc, ref_type, obj, class_handle, name);
    MVM_exception_throw_adhoc(tc, "No int attribute reference type registered for current HLL");
}
MVMObject * MVM_nativeref_attr_u(MVMThreadContext *tc, MVMObject *obj, MVMObject *class_handle, MVMString *name) {
    MVMObject *ref_type = MVM_hll_current(tc)->uint_attr_ref;
    if (ref_type)
        return attrref(tc, ref_type, obj, class_handle, name);
    MVM_exception_throw_adhoc(tc, "No uint attribute reference type registered for current HLL");
}
MVMObject * MVM_nativeref_attr_n(MVMThreadContext *tc, MVMObject *obj, MVMObject *class_handle, MVMString *name) {
    MVMObject *ref_type = MVM_hll_current(tc)->num_attr_ref;
    if (ref_type)
        return attrref(tc, ref_type, obj, class_handle, name);
    MVM_exception_throw_adhoc(tc, "No num attribute reference type registered for current HLL");
}
MVMObject * MVM_nativeref_attr_s(MVMThreadContext *tc, MVMObject *obj, MVMObject *class_handle, MVMString *name) {
    MVMObject *ref_type = MVM_hll_current(tc)->str_attr_ref;
    if (ref_type)
        return attrref(tc, ref_type, obj, class_handle, name);
    MVM_exception_throw_adhoc(tc, "No str attribute reference type registered for current HLL");
}

/* Creation of native references for positionals. */
static MVMObject * posref(MVMThreadContext *tc, MVMObject *type, MVMObject *obj, MVMint64 idx) {
    MVMNativeRef *ref;
    MVMROOT(tc, obj) {
        ref = (MVMNativeRef *)MVM_gc_allocate_object(tc, STABLE(type));
        MVM_ASSIGN_REF(tc, &(ref->common.header), ref->body.u.positional.obj, obj);
        ref->body.u.positional.idx = idx;
    }
    return (MVMObject *)ref;
}
MVMObject * MVM_nativeref_pos_i(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx) {
    MVMObject *ref_type = MVM_hll_current(tc)->int_pos_ref;
    if (ref_type)
        return posref(tc, ref_type, obj, idx);
    MVM_exception_throw_adhoc(tc, "No int positional reference type registered for current HLL");
}
MVMObject * MVM_nativeref_pos_u(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx) {
    MVMObject *ref_type = MVM_hll_current(tc)->uint_pos_ref;
    if (ref_type)
        return posref(tc, ref_type, obj, idx);
    MVM_exception_throw_adhoc(tc, "No uint positional reference type registered for current HLL");
}
MVMObject * MVM_nativeref_pos_n(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx) {
    MVMObject *ref_type = MVM_hll_current(tc)->num_pos_ref;
    if (ref_type)
        return posref(tc, ref_type, obj, idx);
    MVM_exception_throw_adhoc(tc, "No num positional reference type registered for current HLL");
}
MVMObject * MVM_nativeref_pos_s(MVMThreadContext *tc, MVMObject *obj, MVMint64 idx) {
    MVMObject *ref_type = MVM_hll_current(tc)->str_pos_ref;
    if (ref_type)
        return posref(tc, ref_type, obj, idx);
    MVM_exception_throw_adhoc(tc, "No str positional reference type registered for current HLL");
}

/* Creation of native references for multi-dimensional positionals. */
static MVMObject * md_posref(MVMThreadContext *tc, MVMObject *type, MVMObject *obj, MVMObject *indices) {
    MVMNativeRef *ref;
    MVMROOT2(tc, obj, indices) {
        ref = (MVMNativeRef *)MVM_gc_allocate_object(tc, STABLE(type));
        MVM_ASSIGN_REF(tc, &(ref->common.header), ref->body.u.multidim.obj, obj);
        MVM_ASSIGN_REF(tc, &(ref->common.header), ref->body.u.multidim.indices, indices);
    }
    return (MVMObject *)ref;
}
MVMObject * MVM_nativeref_multidim_i(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices) {
    MVMObject *ref_type = MVM_hll_current(tc)->int_multidim_ref;
    if (ref_type)
        return md_posref(tc, ref_type, obj, indices);
    MVM_exception_throw_adhoc(tc, "No int multidim positional reference type registered for current HLL");
}
MVMObject * MVM_nativeref_multidim_u(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices) {
    MVMObject *ref_type = MVM_hll_current(tc)->uint_multidim_ref;
    if (ref_type)
        return md_posref(tc, ref_type, obj, indices);
    MVM_exception_throw_adhoc(tc, "No int multidim positional reference type registered for current HLL");
}
MVMObject * MVM_nativeref_multidim_n(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices) {
    MVMObject *ref_type = MVM_hll_current(tc)->num_multidim_ref;
    if (ref_type)
        return md_posref(tc, ref_type, obj, indices);
    MVM_exception_throw_adhoc(tc, "No num multidim positional reference type registered for current HLL");
}
MVMObject * MVM_nativeref_multidim_s(MVMThreadContext *tc, MVMObject *obj, MVMObject *indices) {
    MVMObject *ref_type = MVM_hll_current(tc)->str_multidim_ref;
    if (ref_type)
        return md_posref(tc, ref_type, obj, indices);
    MVM_exception_throw_adhoc(tc, "No str multidim positional reference type registered for current HLL");
}

/* Reference read functions. These do no checks that the reference is of the
 * right kind and primitive type, they just go ahead and do the read. Thus
 * they are more suited to calling from optimized code. The checking path is
 * in the native ref container implementation, in containers.c; after checks,
 * they delegate here. */
MVMint64 MVM_nativeref_read_lex_i(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMRegister *var = &(ref->body.u.lex.frame->env[ref->body.u.lex.env_idx]);
    switch (ref->body.u.lex.type) {
        case MVM_reg_uint8:
            return var->u8;
        case MVM_reg_uint16:
            return var->u16;
        case MVM_reg_uint32:
            return var->u32;
        case MVM_reg_uint64:
            return var->u64;
        case MVM_reg_int8:
            return var->i8;
        case MVM_reg_int16:
            return var->i16;
        case MVM_reg_int32:
            return var->i32;
        default:
            return var->i64;
    }
}
MVMnum64 MVM_nativeref_read_lex_n(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMRegister *var = &(ref->body.u.lex.frame->env[ref->body.u.lex.env_idx]);
    switch (ref->body.u.lex.type) {
        case MVM_reg_num32:
            return var->n32;
        default:
            return var->n64;
    }
}
MVMString * MVM_nativeref_read_lex_s(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return ref->body.u.lex.frame->env[ref->body.u.lex.env_idx].s;
}
MVMint64 MVM_nativeref_read_attribute_i(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_get_attr_i(tc, ref->body.u.attribute.obj,
        ref->body.u.attribute.class_handle, ref->body.u.attribute.name, MVM_NO_HINT);
}
MVMuint64 MVM_nativeref_read_attribute_u(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_get_attr_u(tc, ref->body.u.attribute.obj,
        ref->body.u.attribute.class_handle, ref->body.u.attribute.name, MVM_NO_HINT);
}
MVMnum64 MVM_nativeref_read_attribute_n(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_get_attr_n(tc, ref->body.u.attribute.obj,
        ref->body.u.attribute.class_handle, ref->body.u.attribute.name, MVM_NO_HINT);
}
MVMString * MVM_nativeref_read_attribute_s(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_get_attr_s(tc, ref->body.u.attribute.obj,
        ref->body.u.attribute.class_handle, ref->body.u.attribute.name, MVM_NO_HINT);
}
MVMint64 MVM_nativeref_read_positional_i(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_at_pos_i(tc, ref->body.u.positional.obj, ref->body.u.positional.idx);
}
MVMuint64 MVM_nativeref_read_positional_u(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_at_pos_u(tc, ref->body.u.positional.obj, ref->body.u.positional.idx);
}
MVMnum64 MVM_nativeref_read_positional_n(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_at_pos_n(tc, ref->body.u.positional.obj, ref->body.u.positional.idx);
}
MVMString * MVM_nativeref_read_positional_s(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_at_pos_s(tc, ref->body.u.positional.obj, ref->body.u.positional.idx);
}
MVMint64 MVM_nativeref_read_multidim_i(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_at_pos_multidim_i(tc, ref->body.u.multidim.obj, ref->body.u.multidim.indices);
}
MVMuint64 MVM_nativeref_read_multidim_u(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_at_pos_multidim_u(tc, ref->body.u.multidim.obj, ref->body.u.multidim.indices);
}
MVMnum64 MVM_nativeref_read_multidim_n(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_at_pos_multidim_n(tc, ref->body.u.multidim.obj, ref->body.u.multidim.indices);
}
MVMString * MVM_nativeref_read_multidim_s(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    return MVM_repr_at_pos_multidim_s(tc, ref->body.u.multidim.obj, ref->body.u.multidim.indices);
}

/* Reference write functions. Same (non-checking) rules as the reads above. */
void MVM_nativeref_write_lex_i(MVMThreadContext *tc, MVMObject *ref_obj, MVMint64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMRegister *var = &(ref->body.u.lex.frame->env[ref->body.u.lex.env_idx]);
    switch (ref->body.u.lex.type) {
        case MVM_reg_uint8:
        case MVM_reg_uint16:
        case MVM_reg_uint32:
        case MVM_reg_uint64:
            MVM_exception_throw_adhoc(tc, "Attempting to MVM_nativeref_write_lex_i (%ld) to an unsigned variable", value);
            break;
        case MVM_reg_int8:
            var->i8 = (MVMint8)value;
            break;
        case MVM_reg_int16:
            var->i16 = (MVMint16)value;
            break;
        case MVM_reg_int32:
            var->i32 = (MVMint32)value;
            break;
        default:
            var->i64 = value;
            break;
    }
}
void MVM_nativeref_write_lex_u(MVMThreadContext *tc, MVMObject *ref_obj, MVMuint64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMRegister *var = &(ref->body.u.lex.frame->env[ref->body.u.lex.env_idx]);
    switch (ref->body.u.lex.type) {
        case MVM_reg_int8:
        case MVM_reg_int16:
        case MVM_reg_int32:
        case MVM_reg_int64:
            MVM_exception_throw_adhoc(tc, "Attempting to MVM_nativeref_write_lex_u (%lu) to a signed variable", value);
            break;
        case MVM_reg_uint8:
            var->u8 = (MVMuint8)value;
            break;
        case MVM_reg_uint16:
            var->u16 = (MVMuint16)value;
            break;
        case MVM_reg_uint32:
            var->u32 = (MVMuint32)value;
            break;
        default:
            var->u64 = value;
            break;
    }
}
void MVM_nativeref_write_lex_n(MVMThreadContext *tc, MVMObject *ref_obj, MVMnum64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMRegister *var = &(ref->body.u.lex.frame->env[ref->body.u.lex.env_idx]);
    switch (ref->body.u.lex.type) {
        case MVM_reg_num32:
            var->n32 = (MVMnum32)value;
            break;
        default:
            var->n64 = value;
            break;
    }
}
void MVM_nativeref_write_lex_s(MVMThreadContext *tc, MVMObject *ref_obj, MVMString *value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVM_ASSIGN_REF(tc, &(ref->body.u.lex.frame->header),
        ref->body.u.lex.frame->env[ref->body.u.lex.env_idx].s, value);
}
void MVM_nativeref_write_attribute_i(MVMThreadContext *tc, MVMObject *ref_obj, MVMint64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMRegister r;
    r.i64 = value;
    MVM_repr_bind_attr_inso(tc, ref->body.u.attribute.obj, ref->body.u.attribute.class_handle,
        ref->body.u.attribute.name, MVM_NO_HINT, r, MVM_reg_int64);
}
void MVM_nativeref_write_attribute_u(MVMThreadContext *tc, MVMObject *ref_obj, MVMuint64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMRegister r;
    r.u64 = value;
    MVM_repr_bind_attr_inso(tc, ref->body.u.attribute.obj, ref->body.u.attribute.class_handle,
        ref->body.u.attribute.name, MVM_NO_HINT, r, MVM_reg_uint64);
}
void MVM_nativeref_write_attribute_n(MVMThreadContext *tc, MVMObject *ref_obj, MVMnum64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMRegister r;
    r.n64 = value;
    MVM_repr_bind_attr_inso(tc, ref->body.u.attribute.obj, ref->body.u.attribute.class_handle,
        ref->body.u.attribute.name, MVM_NO_HINT, r, MVM_reg_num64);
}
void MVM_nativeref_write_attribute_s(MVMThreadContext *tc, MVMObject *ref_obj, MVMString *value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMRegister r;
    r.s = value;
    MVM_repr_bind_attr_inso(tc, ref->body.u.attribute.obj, ref->body.u.attribute.class_handle,
        ref->body.u.attribute.name, MVM_NO_HINT, r, MVM_reg_str);
}
void MVM_nativeref_write_positional_i(MVMThreadContext *tc, MVMObject *ref_obj, MVMint64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVM_repr_bind_pos_i(tc, ref->body.u.positional.obj, ref->body.u.positional.idx, value);
}
void MVM_nativeref_write_positional_u(MVMThreadContext *tc, MVMObject *ref_obj, MVMuint64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVM_repr_bind_pos_u(tc, ref->body.u.positional.obj, ref->body.u.positional.idx, value);
}
void MVM_nativeref_write_positional_n(MVMThreadContext *tc, MVMObject *ref_obj, MVMnum64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVM_repr_bind_pos_n(tc, ref->body.u.positional.obj, ref->body.u.positional.idx, value);
}
void MVM_nativeref_write_positional_s(MVMThreadContext *tc, MVMObject *ref_obj, MVMString *value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVM_repr_bind_pos_s(tc, ref->body.u.positional.obj, ref->body.u.positional.idx, value);
}

void MVM_nativeref_write_multidim_i(MVMThreadContext *tc, MVMObject *ref_obj, MVMint64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVM_repr_bind_pos_multidim_i(tc, ref->body.u.multidim.obj, ref->body.u.multidim.indices, value);
}
void MVM_nativeref_write_multidim_u(MVMThreadContext *tc, MVMObject *ref_obj, MVMuint64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVM_repr_bind_pos_multidim_u(tc, ref->body.u.multidim.obj, ref->body.u.multidim.indices, value);
}
void MVM_nativeref_write_multidim_n(MVMThreadContext *tc, MVMObject *ref_obj, MVMnum64 value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVM_repr_bind_pos_multidim_n(tc, ref->body.u.multidim.obj, ref->body.u.multidim.indices, value);
}
void MVM_nativeref_write_multidim_s(MVMThreadContext *tc, MVMObject *ref_obj, MVMString *value) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVM_repr_bind_pos_multidim_s(tc, ref->body.u.multidim.obj, ref->body.u.multidim.indices, value);
}

/* Functions to turn native integer references into an AO_t * that can be used
 * in an atomic operation. The reference *must* be used and discarded *before*
 * the next safepoint, after which it could become invalidated. */
AO_t * MVM_nativeref_as_atomic_lex_i(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMRegister *var = &(ref->body.u.lex.frame->env[ref->body.u.lex.env_idx]);
    if (sizeof(AO_t) == 8 && ref->body.u.lex.type == MVM_reg_int64)
        return (AO_t *)&(var->i64);
    if (sizeof(AO_t) == 4 && ref->body.u.lex.type == MVM_reg_int32)
        return (AO_t *)&(var->i32);
    MVM_exception_throw_adhoc(tc,
        "Cannot atomic load from an integer lexical not of the machine's native size");
}
AO_t * MVM_nativeref_as_atomic_attribute_i(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMObject *obj = ref->body.u.attribute.obj;
    return REPR(obj)->attr_funcs.attribute_as_atomic(tc, STABLE(obj), OBJECT_BODY(obj),
        ref->body.u.attribute.class_handle, ref->body.u.attribute.name, MVM_reg_int64);
}
AO_t * MVM_nativeref_as_atomic_positional_i(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMObject *obj = ref->body.u.positional.obj;
    return REPR(obj)->pos_funcs.pos_as_atomic(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        ref->body.u.positional.idx);
}
AO_t * MVM_nativeref_as_atomic_multidim_i(MVMThreadContext *tc, MVMObject *ref_obj) {
    MVMNativeRef *ref = (MVMNativeRef *)ref_obj;
    MVMObject *obj = ref->body.u.multidim.obj;
    MVMint64 num_indices;
    MVM_repr_populate_indices_array(tc, ref->body.u.multidim.indices, &num_indices);
    return REPR(obj)->pos_funcs.pos_as_atomic_multidim(tc, STABLE(obj), obj, OBJECT_BODY(obj),
        num_indices, tc->multi_dim_indices);
}
