#include "mr4th_osw.ogl.wgl.h"
#include "mr4th_wgl.h"

////////////////////////////////
// WGL Implementation: OSW OGL Functions

MR4TH_SYMBOL OSW_Backend osw_ogl_wgl_backend = {0};
MR4TH_SYMBOL OSW_Backend*
osw_ogl_backend_open(void){
  OSW_Backend *backend = 0;
  if (!osw_ogl_wgl_backend.initialized){
    if (osw__wgl_open()){
      backend = &osw_ogl_wgl_backend;
      backend->initialized = 1;
      backend->close = osw__wgl_close;
      backend->begin = osw__wgl_begin;
      backend->end   = osw__wgl_end;
    }
  }
  return(backend);
}

////////////////////////////////
// WGL Functions

static OSW_WGL_Ctx    osw__wgl_ctx = {0};
static OSW_WGL_Window osw__wgl_windows[MR4TH_OSW_MAX_WINDOW_COUNT] = {0};

#define OSW_WGL__BOOTSTRAP_CLASS L"mr4th-wgl-bootstrap"

static B32
osw__wgl_open(void){
  B32 error = 0;
  HINSTANCE instance = w32_get_instance();
  
  // load opengl32.dll
  HMODULE opengl_module = LoadLibraryA("opengl32.dll");
  if (opengl_module == 0){
    er_emit_lit("osw: failed to load opengl32.dll");
    error = 1;
  }
  
  // load & check wgl functions
  if (!error){
#define X(N,R,P) \
(*(PROC*)(&(w32_##N))) = GetProcAddress(opengl_module,#N); \
if (w32_##N == 0){ error = 1; }
    MR4TH_WGL_FUNCTION_XLIST(X)
#undef X
    if (error){
      er_emit_lit("osw: failed to load wgl functions");
    }
  }
  
  // register bootstrap class
  ATOM bootstrap_atom = 0;
  if (!error){
    WNDCLASSW bs_wnd_class = {0};
    bs_wnd_class.lpfnWndProc = DefWindowProcW;
    bs_wnd_class.hInstance = instance;
    bs_wnd_class.lpszClassName = OSW_WGL__BOOTSTRAP_CLASS;
    bootstrap_atom = RegisterClassW(&bs_wnd_class);
    if (bootstrap_atom == 0){
      er_emit_lit("osw: failed to create wgl bootstrap window class");
      error = 1;
    }
  }
  
  // create bootstrap window
  HWND bootstrap_window = 0;
  if (!error){
    bootstrap_window =
      CreateWindowW(OSW_WGL__BOOTSTRAP_CLASS, L"mr4th-wgl-bootstrap",
                    0, 0, 0, 0, 0,    // style, x,y,w,h
                    0, 0, instance, 0 // parent, menu, inst, param
                    );
    if (bootstrap_window == 0){
      er_emit_lit("osw: failed to create bootstrap window");
      error = 1;
    }
  }
  
  // get bootstrap device context
  HDC bootstrap_dc = 0;
  if (!error){
    bootstrap_dc = GetDC(bootstrap_window);
  }
  
  // create bootstrap opengl context
  HGLRC bootstrap_hglrc = 0;
  if (!error){
    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){
      if (SetPixelFormat(bootstrap_dc, format_idx, &format_desc)){
        bootstrap_hglrc = w32_wglCreateContext(bootstrap_dc);
      }
    }
    if (bootstrap_hglrc == 0){
      er_emit_lit("osw: failed to create bootstrap opengl context");
      error = 1;
    }
  }
  
  // load wgl extension functions
  if (!error){
    w32_wglMakeCurrent(bootstrap_dc, bootstrap_hglrc);
#define X(N,R,P) \
(*(PROC*)(&(w32_##N))) = w32_wglGetProcAddress(#N); \
if (w32_##N == 0){ error = 1; }
    MR4TH_WGL_EXT_FUNCTION_XLIST(X)
#undef X
    w32_wglMakeCurrent(0, 0);
    if (error){
      er_emit_lit("osw: failed to load wgl extension funcions");
    }
  }
  
  // cleanup bootstrap
  if (bootstrap_hglrc != 0){
    w32_wglDeleteContext(bootstrap_hglrc);
  }
  if (bootstrap_dc != 0){
    ReleaseDC(bootstrap_window, bootstrap_dc);
  }
  if (bootstrap_window != 0){
    DestroyWindow(bootstrap_window);
  }
  
  // create invisible core window.
  //  This setup allows the opengl context to function
  //  un-interupted while visible windows are come and go.
  HWND core_window = 0;
  if (!error){
    core_window =
      CreateWindowW(OSW_WGL__BOOTSTRAP_CLASS, L"mr4th-wgl-dummy",
                    0, 0, 0, 0, 0,    // style, x,y,w,h
                    0, 0, instance, 0 // parent, menu, inst, param
                    );
    if (core_window == 0){
      er_emit_lit("osw: failed to create dummy window");
      error = 1;
    }
  }
  
  // get gl load dc
  HDC glload_dc = 0;
  if (!error){
    glload_dc = GetDC(core_window);
  }
  
  // create core opengl context
  HGLRC core_hglrc = 0;
  int core_format_idx = 0;
  if (!error){
    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(glload_dc, format_attribs, 0,
                                           1, &core_format_idx, &num_formats);
    if (cpf && num_formats > 0){
      PIXELFORMATDESCRIPTOR format_desc = {0};
      BOOL spf = SetPixelFormat(glload_dc, core_format_idx, &format_desc);
      if (spf){
        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
        };
        core_hglrc = w32_wglCreateContextAttribsARB(glload_dc, 0, attribs);
      }
    }
    if (core_hglrc == 0){
      er_emit_lit("osw: failed to create core opengl context");
      error = 1;
    }
  }
  
  // load & check opengl functions
  if (!error){
    w32_wglMakeCurrent(glload_dc, core_hglrc);
#define X(N,R,P) \
(*(PROC*)(&(N))) = osw__opengl_proc_address(opengl_module,#N); \
if ((N) == 0){ error = 1; }
#include "mr4th_ogl.funcs.h"
#undef X
    w32_wglMakeCurrent(0, 0);
    if (error){
      er_emit_lit("osw: failed to load opengl function");
    }
  }
  
  // cleanup gl load dc
  if (glload_dc != 0){
    ReleaseDC(core_window, glload_dc);
  }
  
  // cleanup after error
  if (error){
    if (opengl_module != 0){
      FreeLibrary(opengl_module);
    }
    if (core_hglrc != 0){
      w32_wglDeleteContext(core_hglrc);
    }
    if (core_window != 0){
      DestroyWindow(core_window);
    }
    if (bootstrap_atom != 0){
      UnregisterClassW(OSW_WGL__BOOTSTRAP_CLASS, instance);
    }
  }
  
  // fill context after success
  if (!error){
    osw__wgl_ctx.opengl_module   = opengl_module;
    osw__wgl_ctx.bootstrap_atom  = bootstrap_atom;
    osw__wgl_ctx.core_window     = core_window;
    osw__wgl_ctx.core_hglrc      = core_hglrc;
    osw__wgl_ctx.core_format_idx = core_format_idx;
  }
  
  return(!error);
}

static void
osw__wgl_close(void){
  if (osw__wgl_ctx.opengl_module != 0){
    FreeLibrary(osw__wgl_ctx.opengl_module);
    osw__wgl_ctx.opengl_module = 0;
  }
  if (osw__wgl_ctx.core_hglrc != 0){
    w32_wglDeleteContext(osw__wgl_ctx.core_hglrc);
    osw__wgl_ctx.core_hglrc = 0;
  }
  if (osw__wgl_ctx.core_window != 0){
    DestroyWindow(osw__wgl_ctx.core_window);
    osw__wgl_ctx.core_window = 0;
  }
  if (osw__wgl_ctx.bootstrap_atom != 0){
    UnregisterClassW(OSW_WGL__BOOTSTRAP_CLASS, w32_get_instance());
    osw__wgl_ctx.bootstrap_atom = 0;
  }
}

static void
osw__wgl_begin(OSW_Window *window){
  if (window == 0){
    osw__wgl_ctx.hdc = GetDC(osw__wgl_ctx.core_window);
  }
  else{
    osw__wgl_ctx.hdc = GetDC(window->hwnd);
    
    U32 idx = osw_window__idx_from_ptr(window);
    OSW_WGL_Window *wgl_window = &osw__wgl_windows[idx];
    
    if (!wgl_window->activated){
      PIXELFORMATDESCRIPTOR ignore = {0};
      SetPixelFormat(osw__wgl_ctx.hdc, osw__wgl_ctx.core_format_idx, &ignore);
      wgl_window->activated = 1;
    }
  }
  
  w32_wglMakeCurrent(osw__wgl_ctx.hdc, osw__wgl_ctx.core_hglrc);
}

static void
osw__wgl_end(OSW_Window *window){
  w32_wglMakeCurrent(0, 0);
  if (window == 0){
    ReleaseDC(osw__wgl_ctx.core_window, osw__wgl_ctx.hdc);
  }
  else{
    SwapBuffers(osw__wgl_ctx.hdc);
    ReleaseDC(window->hwnd, osw__wgl_ctx.hdc);
  }
}

static PROC
osw__opengl_proc_address(HMODULE opengl, char *name){
  PROC result = (PROC)GetProcAddress(opengl, name);
  if (result == 0){
    result = (PROC)w32_wglGetProcAddress(name);
  }
  return(result);
}

