/*
 * Mr. 4th Dimention - Allen Webster
 * 
 * 16.05.2015
 * 
 * Fascilities available for development but not intended for shipping.
 * 
 */

/*
 * Profiling
 */

#if FRED_INTERNAL == 1
enum Debug_Event_Type{
    DBGEV_START,
    DBGEV_END,
    DBGEV_MOMENT,
    // never below this
    DBGEV_COUNT
};

struct Debug_Event{
    i64 time;
    char *name;
    Debug_Event_Type type;
    i32 which_hit;
    i32 event_index;
    i32 thread_index;
};

struct Debug_Event_Array{
    volatile u32 count;
    Debug_Event e[4096];
};

struct Profile_Frame{
    Debug_Event_Array events;
    i32 dbg_procing_start;
    i32 dbg_procing_end;
    i32 index;
    i32 first_key;
};

Profile_Frame profile_frame;

#define PAST_PROFILE_COUNT 30
Profile_Frame past_frames[PAST_PROFILE_COUNT];

extern const i32 INTERNAL_event_index_count;
extern u32 INTERNAL_event_hits[];
i64 INTERNAL_frame_start_time;

bool32 INTERNAL_collecting_events;

inline u32
post_debug_event(char *name, Debug_Event_Type type, i32 event_index, i32 thread_index, u32 which_hit){
    u32 result = 0;
    if (INTERNAL_collecting_events){
        u32 index =
            InterlockedIncrement(&profile_frame.events.count);
        --index;
    
        Assert(index < ArrayCount(profile_frame.events.e));
    
        Debug_Event ev;
        ev.time = system_time() - INTERNAL_frame_start_time;
        ev.name = name;
        ev.type = type;
        ev.event_index = event_index;
        ev.thread_index = thread_index;
    
        if (type == DBGEV_END){
            ev.which_hit = which_hit;
        }
        else{
            ev.which_hit = InterlockedIncrement(INTERNAL_event_hits + event_index) - 1;
        }
    
        profile_frame.events.e[index] = ev;
        result = ev.which_hit;
    }

    return result;
}

internal u32
quick_partition(Debug_Event *es, u32 start, u32 pivot){
    Debug_Event *p = es + pivot;
    
    i32 m = (start + pivot) >> 1;
    Swap(*p, es[m]);
    
    i32 pn = p->thread_index;
    i32 pe = p->event_index;
    i32 ph = p->which_hit;
    i32 pt = p->type;
    
    for (u32 i = start; i < pivot; ++i){
        Debug_Event *e = es + i;
        
        bool32 smaller = 0;
        
        if (e->thread_index < pn) smaller = 1;
        else if (e->thread_index == pn){
            if (e->type != DBGEV_MOMENT && pt == DBGEV_MOMENT) smaller = 1;
            else if (e->type != DBGEV_MOMENT){
                if (e->event_index < pe) smaller = 1;
                else if (e->event_index == pe){
                    if (e->which_hit < ph) smaller = 1;
                    else if (e->which_hit == ph){
                        if (e->type < pt) smaller = 1;
                    }
                }
            }
            else if (pt == DBGEV_MOMENT){
                if (e->time < p->time) smaller = 1;
            }
        }
        
        if (smaller){
            Swap(*e, es[start]);
            ++start;
        }
    }
    Swap(*p, es[start]);
    
    return start;
}

internal void
quick_sort(Debug_Event *e, u32 start, u32 pivot){
    u32 mid = quick_partition(e, start, pivot);
    if (start + 1 < mid) quick_sort(e, start, mid - 1);
    if (mid + 1 < pivot) quick_sort(e, mid + 1, pivot);
}

inline void
sort(Debug_Event_Array *events){
    quick_sort(events->e, 0, events->count - 1);
}

globalvar i32 INTERNAL_frame_index;
globalvar bool32 INTERNAL_updating_profile;

#define ProfileStart_(name, start, counter, hit, thread, n, c)\
    name = n; counter = c; start = system_time(); hit = post_debug_event(n, DBGEV_START, counter, thread, 0)
#define ProfileEnd_(name, start, counter, hit, thread) post_debug_event(name, DBGEV_END, counter, thread, hit)
#define ProfileMoment_(name, counter, thread) post_debug_event(name, DBGEV_MOMENT, counter, thread, 0)

struct INTERNAL_Profile_Block{
    char *name;
    i64 start;
    i32 counter;
    i32 thread;
    i32 hit;
    
    INTERNAL_Profile_Block(char *n, i32 c, i32 t){
        ProfileStart_(name, start, counter, hit, t, n, c);
        thread = t;
    }
    
    ~INTERNAL_Profile_Block(){
        ProfileEnd_(name, start, counter, hit, thread);
    }
};

#define ProfileBlock(name, thread) INTERNAL_Profile_Block name(#name, __COUNTER__, thread)
#define ProfileBlockFunction() INTERNAL_Profile_Block name(__FUNCTION__, __COUNTER__, 0)

#define ProfileStart(name) char *_pname_##name; i64 _pstart_##name; i32 _pcounter_##name; u32 _phit_##name; \
    ProfileStart_(_pname_##name, _pstart_##name, _pcounter_##name, _phit_##name, system_thread_get_id(thread), #name, __COUNTER__)

#define ProfileEnd(name) ProfileEnd_(_pname_##name, _pstart_##name, _pcounter_##name, _phit_##name, system_thread_get_id(thread))

#define ProfileMoment(name, thread) ProfileMoment_(#name, __COUNTER__, thread)
#define ProfileMomentFunction() ProfileMoment_(__FUNCTION__, __COUNTER__, 0)

#else

#define ProfileBlock(name)
#define ProfileStart(name)
#define ProfileEnd(name)
#define ProfileMoment(name)
#define ProfileMomentFunction()

#endif