////////////////////////////////
// NOTE(allen): Profiling by Spall

// includes

#if ENABLE_MANUAL_PROFILE || ENABLE_AUTO_PROFILE
# error cannot build profiling-by-spall with profiling enabled
#endif

#define PROFILER_SPALL 1
#define ENABLE_MANUAL_PROFILE 1

#include "base/base_context.h"
#include "base/base_macros.h"
#include "base/base_basic_types.h"
#include "base/base_intrinsic.h"
#include "base_profiling.h"

#include "base_profiling_auto.c"

#define SPALL_BUFFER_CAP MB(1)

// linkable global/thread variables

SpallProfile spall_ctx = {0};
threadvar SpallBuffer spall_buffer = {0};

// windows implementation
#if OS_WINDOWS

#undef function
#include <Windows.h>
#define function static

function 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);
}

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

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

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

// profiling interface implementation

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

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

link_function 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);
}

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

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

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

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