////////////////////////////////
// NOTE(allen): Win32 OpenGL Helper Functions

function PROC
w32_ogl_load(HMODULE opengl_module, char *name){
  PROC result = (PROC)GetProcAddress(opengl_module, name);
  if (result == 0){
    result = (PROC)w32_wglGetProcAddress(name);
  }
  return(result);
}

////////////////////////////////
// NOTE(allen): OpenGL Function Implementations

#define WGL_GET_PROC_ADDR(v,s) (*(PROC*)(&(v))) = w32_wglGetProcAddress(s)
#define W32_GL_FULL_PROC_ADDR(v,m,s) (*(PROC*)(&(v))) = w32_ogl_load((m),(s))

#define W32_GL_BOOTSTRAP_WINDOW_CLASS_NAME L"mr4th-opengl-bootstrap"

global HMODULE w32_opengl_module = 0;
global HGLRC   w32_opengl_context = 0;
global int     w32_opengl_pixel_format = 0;
global W32_GLWindow w32_gl_window_slots[GFX_MAX_WINDOW_COUNT];
global HWND    w32_core_window = 0;

#define w32_gl_slot_from_handle(h) (w32_gl_window_slots + (U64)(h) - 1)

function B32
ogl_is_inited(void){
  B32 result = (w32_opengl_module != 0);
  return(result);
}

function B32
ogl_init(void){
  B32 error = 0;
  
  // determine whether to proceed
  B32 proceed = 1;
  {
    if (!gfx_is_inited()){
      error = 1;
      proceed = 0;
    }
    if (ogl_is_inited()){
      proceed = 0;
    }
  }
  
  // only proceed if not already initialized
  if (proceed){
    HINSTANCE instance = w32_get_instance();
    
    // set up window slots
    if (!error){
      MemoryZeroArray(w32_gl_window_slots);
    }
    
    // load opengl module
    if (!error){
      w32_opengl_module = LoadLibraryA("opengl32.dll");
      if (w32_opengl_module == 0){
        er_emit(str8_lit("failed to initialize opengl32.dll"));
        error = 1;
      }
    }
    
    // load & check wgl functions
    if (!error){
#define X(N,R,P) W32_PROC_ADDR(w32_##N, w32_opengl_module, #N);
#include "win32_wgl_api_funcs.h"
#undef X
      
      B32 missing_wgl_func = 0;
#define X(N,R,P) if (w32_##N == 0){ missing_wgl_func = 1; }
#include "win32_wgl_api_funcs.h"
#undef X
      
      if (missing_wgl_func){
        er_emit(str8_lit("failed to load wgl function(s)"));
        error = 1;
      }
    }
    
    // create bootstrap window
    ATOM bootstrap_atom = 0;
    HWND bootstrap_window = 0;
    
    if (!error){
      // register class
      WNDCLASSW wnd_class = {0};
      wnd_class.lpfnWndProc = DefWindowProcW;
      wnd_class.hInstance = instance;
      wnd_class.lpszClassName = W32_GL_BOOTSTRAP_WINDOW_CLASS_NAME;
      bootstrap_atom = RegisterClassW(&wnd_class);
      if (bootstrap_atom != 0){
        // create window
        bootstrap_window =
          CreateWindowW(W32_GL_BOOTSTRAP_WINDOW_CLASS_NAME,
                        L"mr4th-opengl-bootstrap",
                        0, 0, 0, 0, 0,    // style, x,y,w,h
                        0, 0, instance, 0 // parent, menu, inst, param
                        );
      }
      
      // report error
      if (bootstrap_window == 0){
        er_emit(str8_lit("failed to create bootstrap window"));
        error = 1;
      }
    }
    
    // get bootstrap dc
    HDC bootstrap_dc = 0;
    if (bootstrap_window != 0){
      bootstrap_dc = GetDC(bootstrap_window);
    }
    
    // load wgl extension functions
    HGLRC bootstrap_ctx = 0;
    
    if (!error){
      // create bootstrap context
      {
        PIXELFORMATDESCRIPTOR format_desc = {0};
        format_desc.nSize = sizeof(format_desc);
        format_desc.nVersion = 1;
        format_desc.dwFlags = PFD_SUPPORT_OPENGL;
        format_desc.cColorBits  = 24;
        format_desc.cRedBits    = 8;
        format_desc.cRedShift   = 0;
        format_desc.cGreenBits  = 8;
        format_desc.cGreenShift = 8;
        format_desc.cBlueBits   = 8;
        format_desc.cBlueShift  = 16;
        
        int format_idx = ChoosePixelFormat(bootstrap_dc, &format_desc);
        if (format_idx != 0){
          BOOL spf = SetPixelFormat(bootstrap_dc, format_idx, &format_desc);
          if (spf){
            bootstrap_ctx = w32_wglCreateContext(bootstrap_dc);
          }
        }
      }
      
      if (bootstrap_ctx != 0){
        // load wgl extension functions
        w32_wglMakeCurrent(bootstrap_dc, bootstrap_ctx);
#define X(N,R,P) WGL_GET_PROC_ADDR(w32_##N, #N);
#include "win32_wgl_api_ext_funcs.h"
#undef X
        w32_wglMakeCurrent(0, 0);
      }
      
      // check wgl extension functions
      B32 missing_wgl_ext_func = 0;
#define X(N,R,P) if (w32_##N == 0){ missing_wgl_ext_func = 1; }
#include "win32_wgl_api_ext_funcs.h"
#undef X
      
      // report error
      if (missing_wgl_ext_func){
        er_emit(str8_lit("failed to load wgl extension function(s)"));
        error = 1;
      }
    }
    
    // cleanup bootstrap dc
    if (bootstrap_window != 0 && bootstrap_dc != 0){
      ReleaseDC(bootstrap_window, bootstrap_dc);
    }
    
    // create core window for graphics context
    if (!error){
      // create dummy window
      w32_core_window = CreateWindowW(W32_GRAPHICS_WINDOW_CLASS_NAME,
                                      L"mr4th-dummy",
                                      0, 0, 0, 0, 0,    // style,x,y,w,h
                                      0, 0, instance, 0 // parent,menu,inst,param
                                      );
      
      if (w32_core_window == 0){
        er_emit(str8_lit("failed to create dummy window for main gl context"));
        error = 1;
      }
    }
    
    // get core window dc
    HDC core_dc = 0;
    if (w32_core_window != 0){
      core_dc = GetDC(w32_core_window);
    }
    
    // create graphics context
    if (!error){
      // choose pixel format
      int format_attribs[] = {
        WGL_DRAW_TO_WINDOW_ARB, TRUE,
        WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
        WGL_SWAP_METHOD_ARB, WGL_SWAP_EXCHANGE_ARB,
        WGL_SUPPORT_OPENGL_ARB, TRUE,
        WGL_DOUBLE_BUFFER_ARB, TRUE,
        WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
        WGL_COLOR_BITS_ARB,  24,
        WGL_RED_BITS_ARB,     8,
        WGL_GREEN_BITS_ARB,   8,
        WGL_BLUE_BITS_ARB,    8,
        0
      };
      
      UINT num_formats = 0;
      BOOL cpf = w32_wglChoosePixelFormatARB(core_dc, format_attribs, 0,
                                             1, &w32_opengl_pixel_format,
                                             &num_formats);
      if (cpf && num_formats > 0){
        
        // set pixel format
        PIXELFORMATDESCRIPTOR format_desc = {0};
        BOOL spf = SetPixelFormat(core_dc, w32_opengl_pixel_format, &format_desc);
        if (spf){
          
          // create context
          int attribs[] = {
            WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
            WGL_CONTEXT_MINOR_VERSION_ARB, 3,
            WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
            WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
            0
          };
          
          w32_opengl_context = w32_wglCreateContextAttribsARB(core_dc, 0, attribs);
        }
      }
      
      // report error
      if (w32_opengl_context == 0){
        er_emit(str8_lit("failed to create opengl graphics context"));
        error = 1;
      }
    }
    
    // load & check gl functions
    if (!error){
      w32_wglMakeCurrent(core_dc, w32_opengl_context);
#define X(N,R,P) W32_GL_FULL_PROC_ADDR(N, w32_opengl_module, #N);
#include "opengl/ogl_api_funcs.h"
#undef X
      w32_wglMakeCurrent(0, 0);
      
      B32 missing_gl_func = 0;
#define X(N,R,P) if (N == 0){ missing_gl_func = 1; }
#include "opengl/ogl_api_funcs.h"
#undef X
      
      if (missing_gl_func){
        er_emit(str8_lit("failed to load gl function(s)"));
        error = 1;
      }
    }
    
    // cleanup core window dc
    if (w32_core_window != 0 && core_dc != 0){
      ReleaseDC(w32_core_window, core_dc);
    }
    
    // clean up all "temps"
    {
      if (bootstrap_ctx != 0){
        BOOL delete_context = w32_wglDeleteContext(bootstrap_ctx);
        Assert(delete_context);
      }
      if (bootstrap_window != 0){
        BOOL destroy_window = DestroyWindow(bootstrap_window);
        Assert(destroy_window);
      }
      if (bootstrap_atom != 0){
        BOOL unregister = UnregisterClassW(W32_GL_BOOTSTRAP_WINDOW_CLASS_NAME, instance);
        Assert(unregister);
      }
    }
    
    // on error clean up "non-temps"
    if (error){
      if (w32_opengl_module != 0){
        FreeLibrary(w32_opengl_module);
      }
      if (w32_opengl_context != 0){
        BOOL delete_context = w32_wglDeleteContext(w32_opengl_context);
        Assert(delete_context);
      }
      if (w32_core_window != 0){
        BOOL destroy_window = DestroyWindow(w32_core_window);
        Assert(destroy_window);
      }
#define X(N,R,P) w32_##N = 0;
#include "win32_wgl_api_funcs.h"
#include "win32_wgl_api_ext_funcs.h"
#undef X
#define X(N,R,P) N = 0;
#include "opengl/ogl_api_funcs.h"
#undef X
      w32_opengl_module = 0;
      w32_opengl_context = 0;
      w32_opengl_pixel_format = 0;
      w32_core_window = 0;
    }
  }
  
  // success is returned IFF ogl is initialized
  Assert((!error) == ogl_is_inited());
  
  // fill result
  B32 result = (!error);
  return(result);
}

function void
w32_ogl_window_release(GFX_Window *window){}

function B32
ogl_equip_window(GFX_Window *window){
  B32 error = 0;
  
  // api check
  if (!ogl_is_inited()){
    er_emit(str8_lit("ogl api is not initialized"));
    error = 1;
  }
  
  // handle checks
  if (!error){
    if (!gfx_window_is_valid(window)){
      er_emit(str8_lit("invalid window handle"));
      error = 1;
    }
    else if (gfx_window_get_equipped_data(window) != 0){
      er_emit(str8_lit("window is already equipped"));
      error = 1;
    }
  }
  
  // set pixel format
  if (!error){
    W32_Window *window_slot = gfx_w32_window_from_handle(window);
    HWND wnd = window_slot->wnd;
    
    HDC dc = GetDC(wnd);
    PIXELFORMATDESCRIPTOR desc = {0};
    BOOL spf = SetPixelFormat(dc, w32_opengl_pixel_format, &desc);
    if (!spf){
      er_emit(str8_lit("failed to set window's pixel format"));
      error = 1;
    }
    ReleaseDC(wnd, dc);
  }
  
  // fill equipment
  if (!error){
    W32_GLWindow *slot = w32_gl_slot_from_handle(window);
    slot->dummy = 1;
    gfx_window_set_equipped_data(window, slot, w32_ogl_window_release);
  }
  
  // fill result
  B32 result = (!error);
  return(result);
}

global HDC  w32_opengl_render_dc = 0;
global HWND w32_opengl_render_wnd = 0;

function void
ogl_begin(GFX_Window *window){
  // api check
  if (!ogl_is_inited()){
    er_emit(str8_lit("ogl api is not initialized"));
  }
  
  // main path
  else{
    if (window != 0){
      // TODO(allen): Check equipment here
      if (gfx_window_is_valid(window) &&
          w32_opengl_render_dc == 0){
        W32_Window *window_slot = w32_slot_from_handle(window);
        W32_GLWindow *slot = (W32_GLWindow*)window_slot->equipped_data;
        w32_opengl_render_wnd = window_slot->wnd;
        w32_opengl_render_dc = GetDC(w32_opengl_render_wnd);
        w32_wglMakeCurrent(w32_opengl_render_dc, w32_opengl_context);
      }
    }
    else{
      if (w32_opengl_render_dc == 0){
        w32_opengl_render_wnd = w32_core_window;
        w32_opengl_render_dc = GetDC(w32_opengl_render_wnd);
        w32_wglMakeCurrent(w32_opengl_render_dc, w32_opengl_context);
      }
    }
  }
}

function void
ogl_end(void){
  // api check
  if (!ogl_is_inited()){
    er_emit(str8_lit("ogl api is not initialized"));
  }
  
  // main path
  else{
    if (w32_opengl_render_dc != 0){
      if (w32_opengl_render_wnd != w32_core_window){
        SwapBuffers(w32_opengl_render_dc);
      }
      w32_wglMakeCurrent(0, 0);
      ReleaseDC(w32_opengl_render_wnd, w32_opengl_render_dc);
      w32_opengl_render_dc = 0;
      w32_opengl_render_wnd = 0;
    }
  }
}