/*
 * 4coder_profile.cpp - Built in self profiling report.
 */

// TOP

global Profile_Global_List global_prof_list = {};
global System_Mutex global_prof_mutex = {};

function void
global_prof_init(void){
    global_prof_mutex = system_mutex_make();
    global_prof_list.node_arena = make_arena(get_base_allocator_system(),
                                             KB(4));
}

function Profile_Thread*
global_prof_get_thread(i32 thread_id){
    Profile_Thread *result = 0;
    for (Profile_Thread *node = global_prof_list.first_thread;
         node != 0;
         node = node->next){
        if (thread_id == node->thread_id){
            result = node;
            break;
        }
    }
    if (result == 0){
        result = push_array_zero(&global_prof_list.node_arena, Profile_Thread, 1);
        sll_queue_push(global_prof_list.first_thread, global_prof_list.last_thread, result);
        global_prof_list.thread_count += 1;
        result->thread_id = thread_id;
    }
    return(result);
}

function void
global_prof_clear(void){
    Mutex_Lock lock(global_prof_mutex);
    for (Arena_Node *node = global_prof_list.first_arena;
         node != 0;
         node = node->next){
        linalloc_clear(&node->arena);
    }
    global_prof_list.first_arena = 0;
    global_prof_list.last_arena = 0;
    
    linalloc_clear(&global_prof_list.node_arena);
    global_prof_list.first_thread = 0;
    global_prof_list.last_thread = 0;
    global_prof_list.thread_count = 0;
}

function void
thread_profile_flush(Thread_Context *tctx){
    if (tctx->prof_record_count > 0){
        Mutex_Lock lock(global_prof_mutex);
        if (global_prof_list.disable_bits == 0){
            Profile_Thread* thread = global_prof_get_thread(system_thread_get_id());
            
            Arena_Node* node = push_array(&global_prof_list.node_arena, Arena_Node, 1);
            sll_queue_push(global_prof_list.first_arena, global_prof_list.last_arena,
                           node);
            node->arena = tctx->prof_arena;
            tctx->prof_arena = make_arena(get_base_allocator_system(), KB(4));
            
            if (thread->first_record == 0){
                thread->first_record = tctx->prof_first;
                thread->last_record = tctx->prof_last;
            }
            else{
                thread->last_record->next = tctx->prof_first;
                thread->last_record = tctx->prof_last;
            }
            thread->record_count += tctx->prof_record_count;
            
            tctx->prof_record_count = 0;
            tctx->prof_first = 0;
            tctx->prof_last = 0;
        }
    }
}

function void
global_prof_set_enabled(b32 value, Profile_Enable_Flag flag){
    Mutex_Lock lock(global_prof_mutex);
    if (value){
        RemFlag(global_prof_list.disable_bits, flag);
    }
    else{
        AddFlag(global_prof_list.disable_bits, flag);
    }
}

function void
thread_profile_record__inner(Thread_Context *tctx, Profile_ID id, u64 time,
                             String_Const_u8 name, String_Const_u8 location){
    Profile_Record *record = push_array_zero(&tctx->prof_arena, Profile_Record, 1);
    sll_queue_push(tctx->prof_first, tctx->prof_last, record);
    tctx->prof_record_count += 1;
    record->id = id;
    record->time = time;
    record->location = location;
    record->name = name;
}

function Profile_ID
thread_profile_record_push(Thread_Context *tctx, u64 time,
                           String_Const_u8 name, String_Const_u8 location){
    Profile_ID id = tctx->prof_id_counter;
    tctx->prof_id_counter += 1;
    thread_profile_record__inner(tctx, id, time, name, location);
    return(id);
}
function void
thread_profile_record_pop(Thread_Context *tctx, u64 time, Profile_ID id){
    Assert(tctx->prof_id_counter > 1);
    tctx->prof_id_counter = id;
    thread_profile_record__inner(tctx, id, time, SCu8(""), SCu8(""));
}

function Profile_ID
thread_profile_record_push(Application_Links *app, u64 time,
                           String_Const_u8 name, String_Const_u8 location){
    Thread_Context *tctx = get_thread_context(app);
    return(thread_profile_record_push(tctx, time, name, location));
}
function void
thread_profile_record_pop(Application_Links *app, u64 time, Profile_ID id){
    Thread_Context *tctx = get_thread_context(app);
    thread_profile_record_pop(tctx, time, id);
}

////////////////////////////////

function void
profile_block__init(Thread_Context *tctx, String_Const_u8 name,
                    String_Const_u8 location, Profile_Block *block){
    block->tctx = tctx;
    block->is_closed = false;
    block->id = thread_profile_record_push(tctx, system_now_time(), name, location);
}
function void
profile_block__init(Thread_Context *tctx, String_Const_u8 name,
                    String_Const_u8 location, Profile_Scope_Block *block){
    block->tctx = tctx;
    block->is_closed = false;
    block->id = thread_profile_record_push(tctx, system_now_time(), name, location);
}

////////

Profile_Block::Profile_Block(Thread_Context *tctx, String_Const_u8 name,
                             String_Const_u8 location){
    profile_block__init(tctx, name, location, this);
}
Profile_Block::Profile_Block(Application_Links *app, String_Const_u8 name,
                             String_Const_u8 location){
    profile_block__init(get_thread_context(app), name, location, this);
}
Profile_Block::~Profile_Block(){
    this->close_now();
}
void
Profile_Block::close_now(){
    if (!this->is_closed){
        thread_profile_record_pop(this->tctx, system_now_time(), this->id);
        this->is_closed = true;
    }
}

////////

Profile_Scope_Block::Profile_Scope_Block(Thread_Context *tctx, String_Const_u8 name,
                                         String_Const_u8 location){
    profile_block__init(tctx, name, location, this);
}
Profile_Scope_Block::Profile_Scope_Block(Application_Links *app, String_Const_u8 name,
                                         String_Const_u8 location){
    profile_block__init(get_thread_context(app), name, location, this);
}
Profile_Scope_Block::~Profile_Scope_Block(){
    this->close_now();
    thread_profile_flush(this->tctx);
}
void
Profile_Scope_Block::close_now(){
    if (!this->is_closed){
        thread_profile_record_pop(this->tctx, system_now_time(), this->id);
        this->is_closed = true;
    }
}

////////////////////////////////

CUSTOM_COMMAND_SIG(profile_enable)
CUSTOM_DOC("Allow 4coder's self profiler to gather new profiling information.")
{
    global_prof_set_enabled(true, ProfileEnable_UserBit);
}

CUSTOM_COMMAND_SIG(profile_disable)
CUSTOM_DOC("Prevent 4coder's self profiler from gathering new profiling information.")
{
    global_prof_set_enabled(false, ProfileEnable_UserBit);
}

CUSTOM_COMMAND_SIG(profile_clear)
CUSTOM_DOC("Clear all profiling information from 4coder's self profiler.")
{
    global_prof_clear();
}

// BOTTOM