#include "moar.h"
#include "platform/time.h"

/* Initializes a new thread context. Note that this doesn't set up a
 * thread itself, it just creates the data structure that exists in
 * MoarVM per thread. */
MVMThreadContext * MVM_tc_create(MVMThreadContext *parent, MVMInstance *instance) {
    MVMThreadContext *tc = MVM_calloc(1, sizeof(MVMThreadContext));
    MVMint32 i;

    /* Associate with VM instance. */
    tc->instance = instance;

    /* Set up GC nursery. We only allocate tospace initially, and allocate
     * fromspace the first time this thread GCs, provided it ever does. */
    tc->nursery_tospace_size = MVM_gc_new_thread_nursery_size(instance);
    tc->nursery_tospace     = MVM_calloc(1, tc->nursery_tospace_size);
    tc->nursery_alloc       = tc->nursery_tospace;
    tc->nursery_alloc_limit = (char *)tc->nursery_alloc + tc->nursery_tospace_size;

    /* Set up temporary root handling. */
    tc->num_temproots   = 0;
    tc->alloc_temproots = MVM_TEMP_ROOT_BASE_ALLOC;
    tc->temproots       = MVM_malloc(sizeof(MVMCollectable **) * tc->alloc_temproots);

    /* Set up intergenerational root handling. */
    tc->num_gen2roots   = 0;
    tc->alloc_gen2roots = 64;
    tc->gen2roots       = MVM_malloc(sizeof(MVMCollectable *) * tc->alloc_gen2roots);

    /* Set up the second generation allocator. */
    tc->gen2 = MVM_gc_gen2_create(instance);

    /* Allocate a call stack for the thread. */
    MVM_callstack_init(tc);

    /* Initialize random number generator state. */
    MVM_proc_seed(tc, (MVM_platform_now() / 10000) * MVM_proc_getpid(tc));

    /* Allocate temporary big integers. */
    for (i = 0; i < MVM_NUM_TEMP_BIGINTS; i++) {
        tc->temp_bigints[i] = MVM_malloc(sizeof(mp_int));
        mp_err err;
        if ((err = mp_init(tc->temp_bigints[i])) != MP_OKAY) {
            MVMint32 j;
            for (j = 0; j < i; j++) {
                mp_clear(tc->temp_bigints[j]);
                MVM_free(tc->temp_bigints[j]);
            }
            MVM_free(tc->temp_bigints[i]);
            MVM_tc_destroy(tc);
            MVM_exception_throw_adhoc(parent, "Error creating a temporary big integer: %s", mp_error_to_string(err));
        }
    }

    /* Initialize last_payload, so we can be sure it's never NULL and don't
     * need to check. */
    tc->last_payload = instance->VMNull;

    /* Note that the last assignment above is repeated in
     * MVM_6model_bootstrap because VMNull doesn't exist yet when the very
     * first tc is created. */

    return tc;
}

/* Destroys a given thread context. This will also free the nursery.
 * This means that it must no longer be in use, at all; this can be
 * ensured by a GC run at thread exit that forces evacuation of all
 * objects from this nursery to the second generation. Only after
 * that is true should this be called. */
void MVM_tc_destroy(MVMThreadContext *tc) {
    MVMint32 i;

    /* If an exception handler calls nqp::exit, we don't unwind the stack and
     * exception handlers aren't cleaned up yet */
    while (tc->active_handlers) {
        MVMActiveHandler *ah = tc->active_handlers;
        tc->active_handlers = ah->next_handler;
        MVM_free(ah);
    }

    /* Free the native callback cache. */
    MVM_str_hash_demolish(tc, &tc->native_callback_cache);

    /* Free specialization state. */
    MVM_spesh_sim_stack_destroy(tc, tc->spesh_sim_stack);

    /* Free the nursery and finalization queue. */
#if MVM_GC_DEBUG >= 3
    memset(tc->nursery_fromspace, 0xfe, tc->nursery_fromspace_size);
#endif
    MVM_free(tc->nursery_fromspace);
#if MVM_GC_DEBUG >= 3
    memset(tc->nursery_tospace, 0xfe, tc->nursery_tospace_size);
#endif
    MVM_free(tc->nursery_tospace);
    MVM_free(tc->finalizing);

    /* Destroy the second generation allocator. */
    MVM_gc_gen2_destroy(tc->instance, tc->gen2);

    /* Destroy the callstack, releasing all allocated memory. */
    MVM_callstack_destroy(tc);

    /* Free the thread-specific storage */
    MVM_free(tc->gc_work);
    MVM_free(tc->temproots);
    MVM_free(tc->gen2roots);
    MVM_free(tc->finalize);

    /* Free any memory allocated for NFAs and multi-dim indices. */
    MVM_free(tc->nfa_done);
    MVM_free(tc->nfa_curst);
    MVM_free(tc->nfa_nextst);
    MVM_free(tc->nfa_fates);
    MVM_free(tc->nfa_longlit);
    MVM_free(tc->multi_dim_indices);

    /* Free temporary working big integers. */
    for (i = 0; i < MVM_NUM_TEMP_BIGINTS; i++) {
        mp_clear(tc->temp_bigints[i]);
        MVM_free(tc->temp_bigints[i]);
    }

    /* Free the thread context itself. */
    memset(tc, 0xfe, sizeof(MVMThreadContext));
    MVM_free(tc);
}

/* Setting and clearing mutex to release on exception throw.
 * If the LSB of the mutex' address is set, it's not actually a mutex but a
 * simple flag (an AO_t) that will be cleared on release. */
void MVM_tc_set_ex_release_mutex(MVMThreadContext *tc, uv_mutex_t *mutex) {
    if (tc->ex_release_mutex)
        MVM_exception_throw_adhoc(tc, "Internal error: multiple ex_release_mutex");
    tc->ex_release_mutex = mutex;
}
void MVM_tc_set_ex_release_atomic(MVMThreadContext *tc, AO_t *flag) {
    if (tc->ex_release_mutex)
        MVM_exception_throw_adhoc(tc, "Internal error: multiple ex_release_mutex");
    tc->ex_release_mutex = (uv_mutex_t *)((uintptr_t)flag | 1);
}
void MVM_tc_release_ex_release_mutex(MVMThreadContext *tc) {
    if (tc->ex_release_mutex) {
        if (MVM_UNLIKELY((uintptr_t)tc->ex_release_mutex & 1)) {
            *((AO_t*)((uintptr_t)tc->ex_release_mutex & ~(uintptr_t)1)) = 0;
        } else {
            uv_mutex_unlock(tc->ex_release_mutex);
        }
    }
    tc->ex_release_mutex = NULL;
}
void MVM_tc_clear_ex_release_mutex(MVMThreadContext *tc) {
    tc->ex_release_mutex = NULL;
}
