////////////////////////////////////////////////
////////////////////////////////////////////////
////////////   PROFILING BY SPALL   ////////////
////////////////////////////////////////////////
////////////////////////////////////////////////


////////////////////////////////
// Setup

// disable profiling for this target

#if defined(MR4TH_PROFILING_MANUAL)
# undef MR4TH_PROFILING_MANUAL
#endif
#define  MR4TH_PROFILING_MANUAL 0

#if defined(MR4TH_PROFILING_AUTO)
# undef MR4TH_PROFILING_AUTO
#endif
#define  MR4TH_PROFILING_AUTO 0

// include base as a profiling provider

#define MR4TH_DISABLE_UNITY_BUILD 1
#define MR4TH_PROFILING_PROVIDER 1
#include "mr4th_base.h"

// setup spall

#include "spall/spall.h"

MR4TH_SYMBOL_STATIC SpallProfile spall_ctx = {0};
MR4TH_SYMBOL_STATIC MR4TH_THREADVAR SpallBuffer spall_buffer = {0};

#define SPALL_BUFFER_CAP MB(1)

// setup auto-profiling hooks

#define ProfAutoHook_Begin(n,z) spall_buffer_begin(&spall_ctx, &spall_buffer, (n), (z), intrinsic_rdtsc())
#define ProfAutoHook_End()      spall_buffer_end(&spall_ctx, &spall_buffer, intrinsic_rdtsc())

#include "mr4th_profiling_auto_hooks.c"


////////////////////////////////
// Spall Profiling Implementation

#if OS_WINDOWS

#include <Windows.h>

MR4TH_SYMBOL_STATIC U64
spallprof_tick_per_usecond(void){
    typedef int QueryInfoType(S32, void*, U32, U32*);
    
    U64 result = Billion(3);
    B32 accurate = 0;
    
    HMODULE ntdll = LoadLibrary("ntdll.dll");
    if (ntdll != 0){
        QueryInfoType *NtQuerySystemInformation =
        (QueryInfoType*)GetProcAddress(ntdll, "NtQuerySystemInformation");
        if (NtQuerySystemInformation != 0){
            // TODO(allen): audit
            volatile U64 *hsuv = 0;
            U32 size = 0;
            S32 query_result =
                NtQuerySystemInformation(0xc5, (void**)&hsuv, sizeof(hsuv), &size);
            if (size == sizeof(hsuv) && query_result >= 0){
                result = (Million(10) << 32)/(hsuv[1] >> 32);
                accurate = 1;
            }
        }
        FreeLibrary(ntdll);
    }
    
    if (!accurate){
        // TODO(allen): This is forcing any program that is built with profiling
        // by spall to also link as a 'graphical' program. How else could I deal
        // with this bit?
        MessageBoxA(0, "Could not acquire accurate rdtsc/sec value",
                    "Profiling by Spall: Error", MB_OK);
    }
    
    return(result);
}

MR4TH_SYMBOL_STATIC void*
spallprof_alloc(U64 size){
    void *result = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
    Assert(result != 0);
    return(result);
}

MR4TH_SYMBOL_STATIC void
spallprof_free(void *ptr, U64 size){
    VirtualFree(ptr, 0, MEM_RELEASE);
}

#else
# error no implementation for profiling by spall on this os
#endif

// profiling interface implementation

MR4TH_SYMBOL_LINK void
prof_open(char *name){
    U64 tick_per_usecond = spallprof_tick_per_usecond();
    spall_ctx = spall_init_file(name, 1000000./tick_per_usecond);
}

MR4TH_SYMBOL_LINK void
prof_close(void){
    spall_quit(&spall_ctx);
}

MR4TH_SYMBOL_LINK void
prof_thread_begin(void){
    void *buffer = spallprof_alloc(SPALL_BUFFER_CAP);
    spall_buffer.length = SPALL_BUFFER_CAP;
    spall_buffer.data = buffer;
    spall_buffer_init(&spall_ctx, &spall_buffer);
}

MR4TH_SYMBOL_LINK void
prof_thread_end(void){
    spall_buffer_quit(&spall_ctx, &spall_buffer);
    spallprof_free(spall_buffer.data, spall_buffer.length);
    MemoryZeroStruct(&spall_buffer);
}

MR4TH_SYMBOL_LINK void
prof_thread_flush(void){
    spall_buffer_flush(&spall_ctx, &spall_buffer);
}

MR4TH_SYMBOL_LINK void
prof_begin(char *name, U32 len){
    spall_buffer_begin(&spall_ctx, &spall_buffer, name, len, intrinsic_rdtsc());
}

MR4TH_SYMBOL_LINK void
prof_end(void){
    spall_buffer_end(&spall_ctx, &spall_buffer, intrinsic_rdtsc());
}
