#include "base/base_inc.h"
#include "os/os_inc.h"

#include "base/base_inc.c"
#include "base/base_big_functions.c"
#include "os/os_inc.c"

#include <Windows.h>

////////////////////////////////
// NOTE(allen): WGL Definitions

//- WGL_ARB_pixel_format constants

#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
#define WGL_DRAW_TO_WINDOW_ARB   0x2001
#define WGL_DRAW_TO_BITMAP_ARB   0x2002
#define WGL_ACCELERATION_ARB     0x2003
#define WGL_NEED_PALETTE_ARB     0x2004
#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005
#define WGL_SWAP_LAYER_BUFFERS_ARB  0x2006
#define WGL_SWAP_METHOD_ARB      0x2007
#define WGL_NUMBER_OVERLAYS_ARB  0x2008
#define WGL_NUMBER_UNDERLAYS_ARB 0x2009
#define WGL_TRANSPARENT_ARB      0x200A
#define WGL_TRANSPARENT_RED_VALUE_ARB   0x2037
#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038
#define WGL_TRANSPARENT_BLUE_VALUE_ARB  0x2039
#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A
#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B
#define WGL_SHARE_DEPTH_ARB      0x200C
#define WGL_SHARE_STENCIL_ARB    0x200D
#define WGL_SHARE_ACCUM_ARB      0x200E
#define WGL_SUPPORT_GDI_ARB      0x200F
#define WGL_SUPPORT_OPENGL_ARB   0x2010
#define WGL_DOUBLE_BUFFER_ARB    0x2011
#define WGL_STEREO_ARB           0x2012
#define WGL_PIXEL_TYPE_ARB       0x2013
#define WGL_COLOR_BITS_ARB       0x2014
#define WGL_RED_BITS_ARB         0x2015
#define WGL_RED_SHIFT_ARB        0x2016
#define WGL_GREEN_BITS_ARB       0x2017
#define WGL_GREEN_SHIFT_ARB      0x2018
#define WGL_BLUE_BITS_ARB        0x2019
#define WGL_BLUE_SHIFT_ARB       0x201A
#define WGL_ALPHA_BITS_ARB       0x201B
#define WGL_ALPHA_SHIFT_ARB      0x201C
#define WGL_ACCUM_BITS_ARB       0x201D
#define WGL_ACCUM_RED_BITS_ARB   0x201E
#define WGL_ACCUM_GREEN_BITS_ARB 0x201F
#define WGL_ACCUM_BLUE_BITS_ARB  0x2020
#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021
#define WGL_DEPTH_BITS_ARB       0x2022
#define WGL_STENCIL_BITS_ARB     0x2023
#define WGL_AUX_BUFFERS_ARB      0x2024

#define WGL_NO_ACCELERATION_ARB      0x2025
#define WGL_GENERIC_ACCELERATION_ARB 0x2026
#define WGL_FULL_ACCELERATION_ARB    0x2027

#define WGL_SWAP_EXCHANGE_ARB  0x2028
#define WGL_SWAP_COPY_ARB      0x2029
#define WGL_SWAP_UNDEFINED_ARB 0x202A

#define WGL_TYPE_RGBA_ARB       0x202B
#define WGL_TYPE_COLORINDEX_ARB 0x202C

//- WGL_ARB_create_context constants

#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#define WGL_CONTEXT_LAYER_PLANE_ARB   0x2093
#define WGL_CONTEXT_FLAGS_ARB         0x2094
#define WGL_CONTEXT_PROFILE_MASK_ARB  0x9126

#define WGL_CONTEXT_DEBUG_BIT_ARB              0x0001
#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002

#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB          0x00000001
#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002

#define ERROR_INVALID_VERSION_ARB 0x2095
#define ERROR_INVALID_PROFILE_ARB 0x2096

//- wgl funcs x-list

#define WGL_FUNCS_X_LIST() \
X(wglCreateContext,  HGLRC, (HDC dc))                     \
X(wglDeleteContext,  BOOL,  (HGLRC glrc))                 \
X(wglMakeCurrent,    BOOL,  (HDC dc,HGLRC glrc))          \
X(wglGetProcAddress, PROC,  (LPCSTR name))

//- wgl extension funcs x-list

#define WGL_EXT_FUNCS_X_LIST() \
X(wglChoosePixelFormatARB, BOOL, (HDC,int*,FLOAT*,UINT,int*,UINT*)) \
X(wglCreateContextAttribsARB, HGLRC, (HDC dc,HGLRC share,int*atri))


//- wgl function types

#define X(N,R,P) typedef R W32_##N P;
WGL_FUNCS_X_LIST()
WGL_EXT_FUNCS_X_LIST()
#undef X

//- wgl function pointers

#define X(N,R,P) global W32_##N * w32_##N = 0;
WGL_FUNCS_X_LIST()
WGL_EXT_FUNCS_X_LIST()
#undef X


////////////////////////////////
// NOTE(allen): GL Definitions

//- gl types

typedef U32 GLenum;
typedef F32 GLfloat;
typedef U32 GLbitfield;
typedef U32 GLuint;
typedef S32 GLint;
typedef S32 GLsizei;
typedef char GLchar;
typedef unsigned char GLboolean;

//- gl constants

#define GL_COLOR_BUFFER_BIT 0x00004000

//- gl func x-list

#define GL_FUNC_X_LIST() \
X(glClearColor, void, (GLfloat r,GLfloat g,GLfloat b,GLfloat a)) \
X(glClear, void, (GLbitfield mask)) \

//- gl function types

#define X(N,R,P) typedef R GL_##N P;
GL_FUNC_X_LIST()
#undef X

//- gl function pointers

#define X(N,R,P) global GL_##N * N = 0;
GL_FUNC_X_LIST()
#undef X


////////////////////////////////
// NOTE(allen): Graphics Window Procedure

HGLRC graphics_context = 0;

function LRESULT
w32_graphics_window_proc(HWND   wnd,
                         UINT   msg,
                         WPARAM wparam,
                         LPARAM lparam){
  LRESULT result = 0;
  switch (msg){
    case WM_CLOSE:
    {
      ExitProcess(0);
    }break;
    
    case WM_SIZE:
    {
      // Here we need to re-render IF we want the window not
      // to have ugly black borders during resizing.
      
      // begin render
      HDC dc = GetDC(wnd);
      w32_wglMakeCurrent(dc, graphics_context);
      
      // render
      glClearColor(1.f, 0.f, 1.f, 1.f);
      glClear(GL_COLOR_BUFFER_BIT);
      
      // end render
      SwapBuffers(dc);
      ReleaseDC(wnd, dc);
    }break;
    
    default:
    {
      result = DefWindowProcW(wnd, msg, wparam, lparam);
    }break;
  }
  return(result);
}

////////////////////////////////
// NOTE(allen): Main

#define GET_PROC_ADDR(v,m,s) (*(PROC*)(&(v))) = GetProcAddress((m),(s))
#define WGL_GET_PROC_ADDR(v,s) (*(PROC*)(&(v))) = w32_wglGetProcAddress(s)
#define GL_FULL_PROC_ADDR(v,m,s) (*(PROC*)(&(v))) = w32_gl_load((m),(s))

#define BOOTSTRAP_WINDOW_CLASS_NAME L"mr4th-opengl-bootstrap"
#define GRAPHICS_WINDOW_CLASS_NAME L"mr4th-opengl-graphics"

static void
error_message(char *msg){
  MessageBoxA(0, msg, "Error", MB_OK);
  ExitProcess(1);
}

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

int
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR lpCmdLine,
        int nShowCmd){
  w32_WinMain_init(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
  
  ////////////////////////////////
  //- initialize system
  
  //- load opengl module
  HMODULE w32_opengl_module = LoadLibraryA("opengl32.dll");
  if (w32_opengl_module == 0){
    error_message("failed to initialize opengl32.dll");
  }
  
  //- load & check wgl functions
  {
#define X(N,R,P) GET_PROC_ADDR(w32_##N, w32_opengl_module, #N);
    WGL_FUNCS_X_LIST()
#undef X
    
    B32 missing_wgl_func = 0;
#define X(N,R,P) if (w32_##N == 0){ missing_wgl_func = 1; }
    WGL_FUNCS_X_LIST()
#undef X
    
    if (missing_wgl_func){
      error_message("failed to load wgl function(s)");
    }
  }
  
  // register bootstrap class
  WNDCLASSW bs_wnd_class = {0};
  bs_wnd_class.lpfnWndProc = DefWindowProcW;
  bs_wnd_class.hInstance = hInstance;
  bs_wnd_class.lpszClassName = BOOTSTRAP_WINDOW_CLASS_NAME;
  ATOM bs_atom = RegisterClassW(&bs_wnd_class);
  if (bs_atom == 0){
    error_message("failed to register bootstrap class");
  }
  
  // create bootstrap window
  HWND bootstrap_window =
    CreateWindowW(BOOTSTRAP_WINDOW_CLASS_NAME,
                  L"mr4th-opengl-bootstrap",
                  0, 0, 0, 0, 0,     // style, x,y,w,h
                  0, 0, hInstance, 0 // parent, menu, inst, param
                  );
  if (bootstrap_window == 0){
    error_message("failed to create bootstrap window");
  }
  
  //- load wgl extension functions
  {
    HDC dc = GetDC(bootstrap_window);
    
    // create bootstrap context
    HGLRC hglrc = 0;
    {
      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(dc, &format_desc);
      if (format_idx != 0){
        BOOL spf = SetPixelFormat(dc, format_idx, &format_desc);
        if (spf){
          hglrc = w32_wglCreateContext(dc);
        }
      }
    }
    
    if (hglrc == 0){
      error_message("failed to create bootstrap context");
    }
    
    // load wgl extension functions
    w32_wglMakeCurrent(dc, hglrc);
#define X(N,R,P) WGL_GET_PROC_ADDR(w32_##N, #N);
    WGL_EXT_FUNCS_X_LIST()
#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; }
    WGL_EXT_FUNCS_X_LIST()
#undef X
    
    // report error
    if (missing_wgl_ext_func){
      error_message("failed to load wgl extension function(s)");
    }
    
    // delete context
    {
      BOOL delete_context = w32_wglDeleteContext(hglrc);
      Assert(delete_context);
    }
    
    // cleanup dc
    ReleaseDC(bootstrap_window, dc);
  }
  
  //- destroy bootstrap window, and class
  {
    BOOL destroy_window = DestroyWindow(bootstrap_window);
    Assert(destroy_window);
    BOOL unregister = UnregisterClassW(BOOTSTRAP_WINDOW_CLASS_NAME, hInstance);
    Assert(unregister);
  }
  
  //- register graphics class
  WNDCLASSW gr_wnd_class = {0};
  gr_wnd_class.lpfnWndProc = w32_graphics_window_proc;
  gr_wnd_class.hInstance = hInstance;
  gr_wnd_class.lpszClassName = GRAPHICS_WINDOW_CLASS_NAME;
  ATOM gr_atom = RegisterClassW(&gr_wnd_class);
  if (gr_atom == 0){
    error_message("failed to register graphics class");
  }
  
  //- create graphics context
  int format_idx = 0;
  
  {
    // create dummy window
    HWND dummy_window =
      CreateWindowW(GRAPHICS_WINDOW_CLASS_NAME,
                    L"DUMMY",
                    WS_TILEDWINDOW,
                    CW_USEDEFAULT, CW_USEDEFAULT, // x,y
                    CW_USEDEFAULT, CW_USEDEFAULT, // w,h
                    0, 0, hInstance, 0 // parent, menu, inst, param
                    );
    if (dummy_window == 0){
      error_message("failed to create dummy window");
    }
    
    
    HDC dc = GetDC(dummy_window);
    
    // 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(dc, format_attribs, 0,
                                           1, &format_idx, &num_formats);
    if (cpf && num_formats > 0){
      
      // set pixel format
      PIXELFORMATDESCRIPTOR format_desc = {0};
      BOOL spf = SetPixelFormat(dc, format_idx, &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
        };
        
        graphics_context = w32_wglCreateContextAttribsARB(dc, 0, attribs);
      }
    }
    
    //- load & check gl functions
    w32_wglMakeCurrent(dc, graphics_context);
#define X(N,R,P) GL_FULL_PROC_ADDR(N, w32_opengl_module, #N);
    GL_FUNC_X_LIST()
#undef X
    w32_wglMakeCurrent(0, 0);
    
    B32 missing_gl_func = 0;
#define X(N,R,P) if (N == 0){ missing_gl_func = 1; }
    GL_FUNC_X_LIST()
#undef X
    
    if (missing_gl_func){
      error_message("failed to load gl functions");
    }
    
    // cleanup
    ReleaseDC(dummy_window, dc);
    DestroyWindow(dummy_window);
  }
  
  if (graphics_context == 0){
    error_message("failed to create graphics context");
  }
  
  
  ////////////////////////////////
  //- create graphics window
  
  //- create the window
  HWND graphics_window =
    CreateWindowW(GRAPHICS_WINDOW_CLASS_NAME,
                  L"TITLE ME",
                  WS_TILEDWINDOW,
                  CW_USEDEFAULT, CW_USEDEFAULT, // x,y
                  CW_USEDEFAULT, CW_USEDEFAULT, // w,h
                  0, 0, hInstance, 0 // parent, menu, inst, param
                  );
  if (graphics_window == 0){
    error_message("failed to create graphics window");
  }
  
  //- set pixel format
  {
    HDC dc = GetDC(graphics_window);
    PIXELFORMATDESCRIPTOR format_desc = {0};
    SetPixelFormat(dc, format_idx, &format_desc);
    ReleaseDC(graphics_window, dc);
  }
  
  ////////////////////////////////
  //- main loop
  
  //- let's look at the new window in action!
  ShowWindow(graphics_window, SW_SHOW);
  
  for (;;){
    // wait for input
    MSG msg = {0};
    BOOL get_message = GetMessage(&msg, 0, 0, 0);
    if (get_message <= 0){
      break;
    }
    DispatchMessage(&msg);
    
    // begin render
    HDC dc = GetDC(graphics_window);
    w32_wglMakeCurrent(dc, graphics_context);
    
    // render
    glClearColor(0.f, 0.f, 1.0f, 1.f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    // end render
    SwapBuffers(dc);
    ReleaseDC(graphics_window, dc);
  }
  
  return(0);
}

//$ graphical //
