#include "moar.h"

/* Starts profiling with the specified configuration. */
void MVM_profile_start(MVMThreadContext *tc, MVMObject *config) {
    if (tc->instance->profiling || MVM_profile_heap_profiling(tc))
        MVM_exception_throw_adhoc(tc, "Profiling is already started");

    if (MVM_repr_exists_key(tc, config, tc->instance->str_consts.kind)) {
        MVMString *kind = MVM_repr_get_str(tc,
            MVM_repr_at_key_o(tc, config, tc->instance->str_consts.kind));
        if (MVM_string_equal(tc, kind, tc->instance->str_consts.instrumented)) {
            MVMuint32 i;
            MVMuint64 s, e;
            MVM_profile_instrumented_start(tc, config);

            /* Call the profiling functions a bunch of times and record how long they took. */
            s = uv_hrtime();
            /* Need an intitial frame for the call_graph as the profiler assumes sensibly that
             * there's only 1 top level frame. Otherwise we'd leak all frames but the very
             * first */
            MVM_profile_log_enter(tc, tc->cur_frame->static_info, MVM_PROFILE_ENTER_NORMAL);
            for (i = 1; i < 1000; i++) {
                MVM_profile_log_enter(tc, tc->cur_frame->static_info, MVM_PROFILE_ENTER_NORMAL);
                MVM_profile_log_exit(tc);
            }
            MVM_profile_log_exit(tc);
            e = uv_hrtime();
            tc->instance->profiling_overhead = (MVMuint64) ((e - s) / 1000) * 0.9;

            /* Disable profiling and discard the data we just collected. */
            MVM_gc_mark_thread_blocked(tc);
            uv_mutex_lock(&(tc->instance->mutex_spesh_sync));
            while (tc->instance->spesh_working != 0)
                uv_cond_wait(&(tc->instance->cond_spesh_sync), &(tc->instance->mutex_spesh_sync));
            tc->instance->profiling = 0;
            MVM_free_null(tc->prof_data->collected_data);

            MVM_profile_instrumented_free_data(tc);

            uv_mutex_unlock(&(tc->instance->mutex_spesh_sync));
            MVM_gc_mark_thread_unblocked(tc);

            /* Now start profiling for real. */
            MVM_profile_instrumented_start(tc, config);
            MVM_profile_log_enter(tc, tc->cur_frame->static_info, MVM_PROFILE_ENTER_NORMAL);
        }
        else if (MVM_string_equal(tc, kind, tc->instance->str_consts.heap))
            MVM_profile_heap_start(tc, config);
        else
            MVM_exception_throw_adhoc(tc, "Unknown profiler specified");
    }
    else {
        /* Default to instrumented if no profiler kind specified, since that
         * used to be the only one we supported. */
        MVM_profile_instrumented_start(tc, config);
    }
}

/* Ends profiling and returns the result data structure. */
MVMObject * MVM_profile_end(MVMThreadContext *tc) {
    if (tc->instance->profiling) {
        MVM_profile_log_exit(tc);
        return MVM_profile_instrumented_end(tc);
    }
    else if (MVM_profile_heap_profiling(tc))
        return MVM_profile_heap_end(tc);
    else
        MVM_exception_throw_adhoc(tc, "Cannot end profiling if not profiling");
}
