// stb_wingraph.h  v0.01 - public domain windows graphics programming
// wraps WinMain, ChoosePixelFormat, ChangeDisplayResolution, etc. for
// doing OpenGL graphics
//
// in ONE source file, put '#define STB_DEFINE' before including this
// OR put '#define STB_WINMAIN' to define a WinMain that calls stbwingraph_main(void)
//
// @TODO:
//    2d rendering interface (that can be done easily in software)
//    STB_WINGRAPH_SOFTWARE -- 2d software rendering only
//    STB_WINGRAPH_OPENGL   -- OpenGL only


#ifndef INCLUDE_STB_WINGRAPH_H
#define INCLUDE_STB_WINGRAPH_H

#ifdef STB_WINMAIN
   #ifndef STB_DEFINE
      #define STB_DEFINE
      #define STB_WINGRAPH_DISABLE_DEFINE_AT_END
   #endif
#endif

#ifdef STB_DEFINE
   #pragma comment(lib, "opengl32.lib")
   #pragma comment(lib, "glu32.lib")
   #pragma comment(lib, "winmm.lib")
#endif

#ifdef __cplusplus
#define STB_EXTERN extern "C"
#else
#define STB_EXTERN
#endif

#ifdef STB_DEFINE
#ifndef _WINDOWS_
   #ifdef APIENTRY
   #undef APIENTRY
   #endif
   #ifdef WINGDIAPI
   #undef WINGDIAPI
   #endif
   #define _WIN32_WINNT 0x0400  // WM_MOUSEWHEEL
   #include <windows.h>
#endif
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <string.h>
#include <assert.h>
#endif

typedef void * stbwingraph_hwnd;
typedef void * stbwingraph_hinstance;

enum
{
   STBWINGRAPH_unprocessed = -(1 << 24),
   STBWINGRAPH_do_not_show,
   STBWINGRAPH_winproc_exit,
   STBWINGRAPH_winproc_update,
   STBWINGRAPH_update_exit,
   STBWINGRAPH_update_pause,
};

typedef enum
{
   STBWGE__none=0,

   STBWGE_create,
   STBWGE_create_postshow,
   STBWGE_draw,
   STBWGE_destroy,
   STBWGE_char,
   STBWGE_keydown,
   STBWGE_syskeydown,
   STBWGE_keyup,
   STBWGE_syskeyup,
   STBWGE_deactivate,
   STBWGE_activate,
   STBWGE_size,

   STBWGE_mousemove ,
   STBWGE_leftdown  , STBWGE_leftup  ,
   STBWGE_middledown, STBWGE_middleup,
   STBWGE_rightdown , STBWGE_rightup ,
   STBWGE_mousewheel,
} stbwingraph_event_type;

typedef struct
{
   stbwingraph_event_type type;

   // for input events (mouse, keyboard)
   int mx,my; // mouse x & y
   int dx,dy;
   int shift, ctrl, alt;

   // for keyboard events
   int key;

   // for STBWGE_size:
   int width, height;

   // for STBWGE_crate
   int did_share_lists;  // if true, wglShareLists succeeded

   void *handle;

} stbwingraph_event;

typedef int (*stbwingraph_window_proc)(void *data, stbwingraph_event *event);

extern stbwingraph_hinstance   stbwingraph_app;
extern stbwingraph_hwnd        stbwingraph_primary_window;
extern int                     stbwingraph_request_fullscreen;
extern int                     stbwingraph_request_windowed;

STB_EXTERN void stbwingraph_ods(char *str, ...);
STB_EXTERN int stbwingraph_MessageBox(stbwingraph_hwnd win, unsigned int type,
                                              char *caption, char *text, ...);
STB_EXTERN int stbwingraph_ChangeResolution(unsigned int w, unsigned int h,
                                      unsigned int bits, int use_message_box);
STB_EXTERN int stbwingraph_SetPixelFormat(stbwingraph_hwnd win, int color_bits,
            int alpha_bits, int depth_bits, int stencil_bits, int accum_bits);
STB_EXTERN int stbwingraph_DefineClass(void *hinstance, char *iconname);
STB_EXTERN void stbwingraph_SwapBuffers(void *win);
STB_EXTERN void stbwingraph_Priority(int n);

STB_EXTERN void stbwingraph_MakeFonts(void *window, int font_base);
STB_EXTERN void stbwingraph_ShowWindow(void *window);
STB_EXTERN void *stbwingraph_CreateWindow(int primary, stbwingraph_window_proc func, void *data, char *text, int width, int height, int fullscreen, int resizeable, int dest_alpha, int stencil);
STB_EXTERN void *stbwingraph_CreateWindowSimple(stbwingraph_window_proc func, int width, int height);
STB_EXTERN void *stbwingraph_CreateWindowSimpleFull(stbwingraph_window_proc func, int fullscreen, int ww, int wh, int fw, int fh);
STB_EXTERN void stbwingraph_DestroyWindow(void *window);
STB_EXTERN void stbwingraph_ShowCursor(void *window, int visible);
STB_EXTERN float stbwingraph_GetTimestep(float minimum_time);
STB_EXTERN void stbwingraph_SetGLWindow(void *win);
typedef int (*stbwingraph_update)(float timestep, int real, int in_client);
STB_EXTERN int stbwingraph_MainLoop(stbwingraph_update func, float mintime);

#ifdef STB_DEFINE
stbwingraph_hinstance   stbwingraph_app;
stbwingraph_hwnd        stbwingraph_primary_window;
int stbwingraph_request_fullscreen;
int stbwingraph_request_windowed;

void stbwingraph_ods(char *str, ...)
{
   char buffer[1024];
   va_list v;
   va_start(v,str);
   vsprintf(buffer, str, v);
   va_end(v);
   OutputDebugString(buffer);
}

int stbwingraph_MessageBox(stbwingraph_hwnd win, unsigned int type, char *caption, char *text, ...)
{
   va_list v;
   char buffer[1024];
   va_start(v, text);
   vsprintf(buffer, text, v);
   va_end(v);
   return MessageBox(win, buffer, caption, type);
}

void stbwingraph_Priority(int n)
{
   int p;
   switch (n) {
      case -1: p = THREAD_PRIORITY_BELOW_NORMAL; break;
      case 0: p = THREAD_PRIORITY_NORMAL; break;
      case 1: p = THREAD_PRIORITY_ABOVE_NORMAL; break;
      default:
         if (n < 0) p = THREAD_PRIORITY_LOWEST;
         else p = THREAD_PRIORITY_HIGHEST;
   }
   SetThreadPriority(GetCurrentThread(), p);
}

static void stbwingraph_ResetResolution(void)
{
   ChangeDisplaySettings(NULL, 0);
}

static void stbwingraph_RegisterResetResolution(void)
{
   static int done=0;
   if (!done) {
      done = 1;
      atexit(stbwingraph_ResetResolution);
   }
}

int stbwingraph_ChangeResolution(unsigned int w, unsigned int h, unsigned int bits, int use_message_box)
{
   DEVMODE mode;
   int res;
   
   int i, tries=0;
   for (i=0; ; ++i) {
      int success = EnumDisplaySettings(NULL, i, &mode);
      if (!success) break;
      if (mode.dmBitsPerPel == bits && mode.dmPelsWidth == w && mode.dmPelsHeight == h) {
         ++tries;
         success = ChangeDisplaySettings(&mode, CDS_FULLSCREEN); 
         if (success == DISP_CHANGE_SUCCESSFUL) {
            stbwingraph_RegisterResetResolution();
            return TRUE;
         }
         break;
      }
   }

   if (!tries) {
      if (use_message_box)
         stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "The resolution %d x %d x %d-bits is not supported.", w, h, bits);
      return FALSE;
   }

   // we tried but failed, so try explicitly doing it without specifying refresh rate

   // Win95 support logic
   mode.dmBitsPerPel = bits; 
   mode.dmPelsWidth = w; 
   mode.dmPelsHeight = h; 
   mode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; 

   res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);

   switch (res) {
      case DISP_CHANGE_SUCCESSFUL:
         stbwingraph_RegisterResetResolution();
         return TRUE;

      case DISP_CHANGE_RESTART:
         if (use_message_box)
            stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "Please set your desktop to %d-bit color and then try again.");
         return FALSE;

      case DISP_CHANGE_FAILED:
         if (use_message_box)
            stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "The hardware failed to change modes.");
         return FALSE;

      case DISP_CHANGE_BADMODE:
         if (use_message_box)
            stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "The resolution %d x %d x %d-bits is not supported.", w, h, bits);
         return FALSE;

      default:
         if (use_message_box)
            stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "An unknown error prevented a change to a %d x %d x %d-bit display.", w, h, bits);
         return FALSE;
   }
}

int stbwingraph_SetPixelFormat(stbwingraph_hwnd win, int color_bits, int alpha_bits, int depth_bits, int stencil_bits, int accum_bits)
{
   HDC dc = GetDC(win);
   PIXELFORMATDESCRIPTOR pfd = { sizeof(pfd) };
   int                   pixel_format;

   pfd.nVersion = 1;
   pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER;
   pfd.dwLayerMask = PFD_MAIN_PLANE;
   pfd.iPixelType = PFD_TYPE_RGBA;
   pfd.cColorBits = color_bits;
   pfd.cAlphaBits = alpha_bits;
   pfd.cDepthBits = depth_bits;
   pfd.cStencilBits = stencil_bits;
   pfd.cAccumBits = accum_bits;

   pixel_format = ChoosePixelFormat(dc, &pfd);
   if (!pixel_format) return FALSE;

   if (!DescribePixelFormat(dc, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), &pfd))
      return FALSE;
   SetPixelFormat(dc, pixel_format, &pfd);

   return TRUE;
}

typedef struct
{
   // app data
   stbwingraph_window_proc func;
   void *data;
   // creation parameters
   int   color, alpha, depth, stencil, accum;
   HWND  share_window;
   HWND  window;
   // internal data
   HGLRC rc;
   HDC   dc;
   int   hide_mouse;
   int   in_client;
   int   active;
   int   did_share_lists;
   int   mx,my; // last mouse positions
} stbwingraph__window;

static void stbwingraph__inclient(stbwingraph__window *win, int state)
{
   if (state != win->in_client) {
      win->in_client = state;
      if (win->hide_mouse)
         ShowCursor(!state);
   }
}

static void stbwingraph__key(stbwingraph_event *e, int type, int key, stbwingraph__window *z)
{
   e->type  = type;
   e->key   = key;
   e->shift = (GetKeyState(VK_SHIFT)   < 0);
   e->ctrl  = (GetKeyState(VK_CONTROL) < 0);
   e->alt   = (GetKeyState(VK_MENU)    < 0);
   if  (z) {
      e->mx    = z->mx;
      e->my    = z->my;
   } else {
      e->mx = e->my = 0;
   }
   e->dx = e->dy = 0;
}

static void stbwingraph__mouse(stbwingraph_event *e, int type, WPARAM wparam, LPARAM lparam, int capture, void *wnd, stbwingraph__window *z)
{
   static int captured = 0;
   e->type = type;
   e->mx = (short) LOWORD(lparam);
   e->my = (short) HIWORD(lparam);
   if (!z || z->mx == -(1 << 30)) {
      e->dx = e->dy = 0;
   } else {
      e->dx = e->mx - z->mx;
      e->dy = e->my - z->my;
   }
   e->shift = (wparam & MK_SHIFT) != 0;
   e->ctrl  = (wparam & MK_CONTROL) != 0;
   e->alt   = (wparam & MK_ALT) != 0;
   if (z) {
      z->mx = e->mx;
      z->my = e->my;
   }
   if (capture) {
      if (!captured && capture == 1)
         SetCapture(wnd);
      captured += capture;
      if (!captured && capture == -1)
         ReleaseCapture();
      if (captured < 0) captured = 0;
   }
}

static void stbwingraph__mousewheel(stbwingraph_event *e, int type, WPARAM wparam, LPARAM lparam, int capture, void *wnd, stbwingraph__window *z)
{
   // lparam seems bogus!
   static int captured = 0;
   e->type = type;
   if (z) {
      e->mx = z->mx;
      e->my = z->my;
   }
   e->dx = e->dy = 0;
   e->shift = (wparam & MK_SHIFT) != 0;
   e->ctrl  = (wparam & MK_CONTROL) != 0;
   e->alt   = (GetKeyState(VK_MENU)    < 0);
   e->key = ((int) wparam >> 16);
}

int stbwingraph_force_update;
static int WINAPI stbwingraph_WinProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
   int allow_default = TRUE;
   stbwingraph_event e = { STBWGE__none };
   // the following line is wrong for 64-bit windows, but VC6 doesn't have GetWindowLongPtr
   stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(wnd, GWL_USERDATA);

   switch (msg) {

      case WM_CREATE:
      {
         LPCREATESTRUCT lpcs = (LPCREATESTRUCT) lparam;
         assert(z == NULL);
         z = (stbwingraph__window *) lpcs->lpCreateParams;
         SetWindowLong(wnd, GWL_USERDATA, (LONG) z);
         z->dc = GetDC(wnd);
         if (stbwingraph_SetPixelFormat(wnd, z->color, z->alpha, z->depth, z->stencil, z->accum)) {
            z->rc = wglCreateContext(z->dc);
            if (z->rc) {
               e.type = STBWGE_create;
               z->did_share_lists = FALSE;
               if (z->share_window) {
                  stbwingraph__window *y = (stbwingraph__window *) GetWindowLong(z->share_window, GWL_USERDATA);
                  if (wglShareLists(z->rc, y->rc))
                     z->did_share_lists = TRUE;
               }
               wglMakeCurrent(z->dc, z->rc);
               return 0;
            }
         }
         return -1;
      }

      case WM_PAINT: {
         PAINTSTRUCT ps;
         HDC hdc = BeginPaint(wnd, &ps);
         SelectObject(hdc, GetStockObject(NULL_BRUSH));
         e.type = STBWGE_draw;
         e.handle = wnd;
         z->func(z->data, &e);
         EndPaint(wnd, &ps);
         return 0;
      }

      case WM_DESTROY:
         e.type = STBWGE_destroy;
         e.handle = wnd;
         if (z && z->func)
            z->func(z->data, &e);
         wglMakeCurrent(NULL, NULL) ; 
         if (z) {
            if (z->rc) wglDeleteContext(z->rc);
            z->dc = 0;
            z->rc = 0;
         }
         if (wnd == stbwingraph_primary_window)
            PostQuitMessage (0);
         return 0;

      case WM_CHAR:         stbwingraph__key(&e, STBWGE_char   , wparam, z); break;
      case WM_KEYDOWN:      stbwingraph__key(&e, STBWGE_keydown, wparam, z); break;
      case WM_KEYUP:        stbwingraph__key(&e, STBWGE_keyup  , wparam, z); break;

      case WM_NCMOUSEMOVE:  stbwingraph__inclient(z,0); break;
      case WM_MOUSEMOVE:    stbwingraph__inclient(z,1); stbwingraph__mouse(&e, STBWGE_mousemove,  wparam, lparam,0,wnd, z); break;
      case WM_LBUTTONDOWN:  stbwingraph__mouse(&e, STBWGE_leftdown,   wparam, lparam,1,wnd, z); break;
      case WM_MBUTTONDOWN:  stbwingraph__mouse(&e, STBWGE_middledown, wparam, lparam,1,wnd, z); break;
      case WM_RBUTTONDOWN:  stbwingraph__mouse(&e, STBWGE_rightdown,  wparam, lparam,1,wnd, z); break;
      case WM_LBUTTONUP:    stbwingraph__mouse(&e, STBWGE_leftup,     wparam, lparam,-1,wnd, z); break;
      case WM_MBUTTONUP:    stbwingraph__mouse(&e, STBWGE_middleup,   wparam, lparam,-1,wnd, z); break;
      case WM_RBUTTONUP:    stbwingraph__mouse(&e, STBWGE_rightup,    wparam, lparam,-1,wnd, z); break;
      case WM_MOUSEWHEEL:   stbwingraph__mousewheel(&e, STBWGE_mousewheel, wparam, lparam,0,wnd, z); break;

      case WM_ACTIVATE:
         allow_default = FALSE;
         if (LOWORD(wparam)==WA_INACTIVE ) {
            wglMakeCurrent(z->dc, NULL);
            e.type = STBWGE_deactivate;
            z->active = FALSE;
         } else {
            wglMakeCurrent(z->dc, z->rc);
            e.type = STBWGE_activate;
            z->active = TRUE;
         }
         e.handle = wnd;
         z->func(z->data, &e);
         return 0;

      case WM_SIZE: {
         RECT rect;
         allow_default = FALSE;
         GetClientRect(wnd, &rect);
         e.type = STBWGE_size;
         e.width = rect.right;
         e.height = rect.bottom;
         e.handle = wnd;
         z->func(z->data, &e);
         return 0;
      }

      default:
         return DefWindowProc (wnd, msg, wparam, lparam);
   }

   if (e.type != STBWGE__none) {
      int n;
      e.handle = wnd;
      n = z->func(z->data, &e);
      if (n == STBWINGRAPH_winproc_exit) {
         PostQuitMessage(0);
         n = 0;
      }
      if (n == STBWINGRAPH_winproc_update) {
         stbwingraph_force_update = TRUE;
         return 1;
      }
      if (n != STBWINGRAPH_unprocessed)
         return n;
   }
   return DefWindowProc (wnd, msg, wparam, lparam);
}

int stbwingraph_DefineClass(HINSTANCE hInstance, char *iconname)
{
   WNDCLASSEX  wndclass;

   stbwingraph_app = hInstance;

   wndclass.cbSize        = sizeof(wndclass);
   wndclass.style         = CS_OWNDC;
   wndclass.lpfnWndProc   = (WNDPROC) stbwingraph_WinProc;
   wndclass.cbClsExtra    = 0;
   wndclass.cbWndExtra    = 0;
   wndclass.hInstance     = hInstance;
   wndclass.hIcon         = LoadIcon(hInstance, iconname);
   wndclass.hCursor       = LoadCursor(NULL,IDC_ARROW);
   wndclass.hbrBackground = GetStockObject(NULL_BRUSH);
   wndclass.lpszMenuName  = "zwingraph";
   wndclass.lpszClassName = "zwingraph";
   wndclass.hIconSm       = NULL;

   if (!RegisterClassEx(&wndclass))
      return FALSE;
   return TRUE;
}

void stbwingraph_ShowWindow(void *window)
{
   stbwingraph_event e = { STBWGE_create_postshow };
   stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(window, GWL_USERDATA);
   ShowWindow(window, SW_SHOWNORMAL);
   InvalidateRect(window, NULL, TRUE);
   UpdateWindow(window);
   e.handle = window;
   z->func(z->data, &e);
}

void *stbwingraph_CreateWindow(int primary, stbwingraph_window_proc func, void *data, char *text,
           int width, int height, int fullscreen, int resizeable, int dest_alpha, int stencil)
{
   HWND win;
   DWORD dwstyle;
   stbwingraph__window *z = (stbwingraph__window *) malloc(sizeof(*z));

   if (z == NULL) return NULL;
   memset(z, 0, sizeof(*z));
   z->color = 24;
   z->depth = 24;
   z->alpha = dest_alpha;
   z->stencil = stencil;
   z->func = func;
   z->data = data;
   z->mx = -(1 << 30);
   z->my = 0;

   if (primary) {
      if (stbwingraph_request_windowed)
         fullscreen = FALSE;
      else if (stbwingraph_request_fullscreen)
         fullscreen = TRUE;
   }

   if (fullscreen) {
      #ifdef STB_SIMPLE
      stbwingraph_ChangeResolution(width, height, 32, 1);
      #else
      if (!stbwingraph_ChangeResolution(width, height, 32, 0))
         return NULL;
      #endif
      dwstyle = WS_POPUP | WS_CLIPSIBLINGS;
   } else {
      RECT rect;
      dwstyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
      if (resizeable)
         dwstyle |= WS_SIZEBOX | WS_MAXIMIZEBOX;
      rect.top = 0;
      rect.left = 0;
      rect.right = width;
      rect.bottom = height;
      AdjustWindowRect(&rect, dwstyle, FALSE);
      width = rect.right - rect.left;
      height = rect.bottom - rect.top;
   }

   win = CreateWindow("zwingraph", text ? text : "sample", dwstyle,
                      CW_USEDEFAULT,0, width, height,
                      NULL, NULL, stbwingraph_app, z);

   if (win == NULL) return win;

   if (primary) {
      if (stbwingraph_primary_window)
         stbwingraph_DestroyWindow(stbwingraph_primary_window);
      stbwingraph_primary_window = win;
   }

   {
      stbwingraph_event e = { STBWGE_create };
      stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(win, GWL_USERDATA);
      z->window = win;
      e.did_share_lists = z->did_share_lists;
      e.handle = win;
      if (z->func(z->data, &e) != STBWINGRAPH_do_not_show)
         stbwingraph_ShowWindow(win);
   }

   return win;
}

void *stbwingraph_CreateWindowSimple(stbwingraph_window_proc func, int width, int height)
{
   int fullscreen = 0;
   #ifndef _DEBUG
   if (width ==  640 && height ==  480) fullscreen = 1;
   if (width ==  800 && height ==  600) fullscreen = 1;
   if (width == 1024 && height ==  768) fullscreen = 1;
   if (width == 1280 && height == 1024) fullscreen = 1;
   if (width == 1600 && height == 1200) fullscreen = 1;
   //@TODO: widescreen widths
   #endif
   return stbwingraph_CreateWindow(1, func, NULL, NULL, width, height, fullscreen, 1, 0, 0);
}

void *stbwingraph_CreateWindowSimpleFull(stbwingraph_window_proc func, int fullscreen, int ww, int wh, int fw, int fh)
{
   if (fullscreen == -1) {
   #ifdef _DEBUG
      fullscreen = 0;
   #else
      fullscreen = 1;
   #endif
   }

   if (fullscreen) {
      if (fw) ww = fw;
      if (fh) wh = fh;
   }
   return stbwingraph_CreateWindow(1, func, NULL, NULL, ww, wh, fullscreen, 1, 0, 0);
}

void stbwingraph_DestroyWindow(void *window)
{
   stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(window, GWL_USERDATA);
   DestroyWindow(window);
   free(z);
   if (stbwingraph_primary_window == window)
      stbwingraph_primary_window = NULL;
}

void stbwingraph_ShowCursor(void *window, int visible)
{
   int hide;
   stbwingraph__window *win;
   if (!window)
      window = stbwingraph_primary_window;
   win = (stbwingraph__window *) GetWindowLong((HWND) window, GWL_USERDATA);
   hide = !visible;
   if (hide != win->hide_mouse) {
      win->hide_mouse = hide;
      if (!hide)
         ShowCursor(TRUE);
      else if (win->in_client)
         ShowCursor(FALSE);
   }
}

float stbwingraph_GetTimestep(float minimum_time)
{
   float elapsedTime;
   double thisTime;
   static double lastTime = -1;
   
   if (lastTime == -1)
      lastTime = timeGetTime() / 1000.0 - minimum_time;

   for(;;) {
      thisTime = timeGetTime() / 1000.0;
      elapsedTime = (float) (thisTime - lastTime);
      if (elapsedTime >= minimum_time) {
         lastTime = thisTime;         
         return elapsedTime;
      }
      #if 1
      Sleep(2);
      #endif
   }
}

void stbwingraph_SetGLWindow(void *win)
{
   stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(win, GWL_USERDATA);
   if (z)
      wglMakeCurrent(z->dc, z->rc);
}

void stbwingraph_MakeFonts(void *window, int font_base)
{
   wglUseFontBitmaps(GetDC(window ? window : stbwingraph_primary_window), 0, 256, font_base);
}

// returns 1 if WM_QUIT, 0 if 'func' returned 0
int stbwingraph_MainLoop(stbwingraph_update func, float mintime)
{
   int needs_drawing = FALSE;
   MSG msg;

   int is_animating = TRUE;
   if (mintime <= 0) mintime = 0.01f;

   for(;;) {
      int n;

      is_animating = TRUE;
      // wait for a message if: (a) we're animating and there's already a message
      // or (b) we're not animating
      if (!is_animating || PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
         stbwingraph_force_update = FALSE;
         if (GetMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
         } else {
            return 1;   // WM_QUIT
         }

         // only force a draw for certain messages...
         // if I don't do this, we peg at 50% for some reason... must
         // be a bug somewhere, because we peg at 100% when rendering...
         // very weird... looks like NVIDIA is pumping some messages
         // through our pipeline? well, ok, I guess if we can get
         // non-user-generated messages we have to do this
         if (!stbwingraph_force_update) {
            switch (msg.message) {
               case WM_MOUSEMOVE:
               case WM_NCMOUSEMOVE:
                  break;
               case WM_CHAR:         
               case WM_KEYDOWN:      
               case WM_KEYUP:        
               case WM_LBUTTONDOWN:  
               case WM_MBUTTONDOWN:  
               case WM_RBUTTONDOWN:  
               case WM_LBUTTONUP:    
               case WM_MBUTTONUP:    
               case WM_RBUTTONUP:    
               case WM_TIMER:
               case WM_SIZE:
               case WM_ACTIVATE:
                  needs_drawing = TRUE;
                  break;
            }
         } else
            needs_drawing = TRUE;
      }

      // if another message, process that first
      // @TODO: i don't think this is working, because I can't key ahead
      // in the SVT demo app
      if (PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE))
         continue;

      // and now call update
      if (needs_drawing || is_animating) {
         int real=1, in_client=1;
         if (stbwingraph_primary_window) {
            stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(stbwingraph_primary_window, GWL_USERDATA);
            if (z && !z->active) {
               real = 0;
            }
            if (z)
               in_client = z->in_client;
         }

         if (stbwingraph_primary_window)
            stbwingraph_SetGLWindow(stbwingraph_primary_window);
         n = func(stbwingraph_GetTimestep(mintime), real, in_client);
         if (n == STBWINGRAPH_update_exit)
            return 0; // update_quit

         is_animating = (n != STBWINGRAPH_update_pause);

         needs_drawing = FALSE;
      }
   }
}

void stbwingraph_SwapBuffers(void *win)
{
   stbwingraph__window *z;
   if (win == NULL) win = stbwingraph_primary_window;
   z = (stbwingraph__window *) GetWindowLong(win, GWL_USERDATA);
   if (z && z->dc)
      SwapBuffers(z->dc);
}
#endif

#ifdef STB_WINMAIN    
void stbwingraph_main(void);

char *stb_wingraph_commandline;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
   {
      char buffer[1024];
      // add spaces to either side of the string
      buffer[0] = ' ';
      strcpy(buffer+1, lpCmdLine);
      strcat(buffer, " ");
      if (strstr(buffer, " -reset ")) {
         ChangeDisplaySettings(NULL, 0); 
         exit(0);
      }
      if (strstr(buffer, " -window ") || strstr(buffer, " -windowed "))
         stbwingraph_request_windowed = TRUE;
      else if (strstr(buffer, " -full ") || strstr(buffer, " -fullscreen "))
         stbwingraph_request_fullscreen = TRUE;
   }
   stb_wingraph_commandline = lpCmdLine;

   stbwingraph_DefineClass(hInstance, "appicon");
   stbwingraph_main();

   return 0;
}
#endif

#undef STB_EXTERN
#ifdef STB_WINGRAPH_DISABLE_DEFINE_AT_END
#undef STB_DEFINE
#endif

#endif // INCLUDE_STB_WINGRAPH_H