/*
 * fg_main_mswin.c
 *
 * The Windows-specific mouse cursor related stuff.
 *
 * Copyright (c) 2012 Stephen J. Baker. All Rights Reserved.
 * Written by John F. Fay, <fayjf@sourceforge.net>
 * Creation date: Sat Jan 21, 2012
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <GL/freeglut.h>
#include "../fg_internal.h"

extern void fghRedrawWindow ( SFG_Window *window );
extern void fghRedrawWindowAndChildren ( SFG_Window *window );
extern void fghOnReshapeNotify(SFG_Window *window, int width, int height, GLboolean forceNotify);
extern void fghOnPositionNotify(SFG_Window *window, int x, int y, GLboolean forceNotify);
extern void fghComputeWindowRectFromClientArea_QueryWindow( RECT *clientRect, const SFG_Window *window, BOOL posIsOutside );
extern void fghGetClientArea( RECT *clientRect, const SFG_Window *window, BOOL posIsOutside );

extern void fgNewWGLCreateContext( SFG_Window* window );
extern GLboolean fgSetupPixelFormat( SFG_Window* window, GLboolean checkOnly,
                                     unsigned char layer_type );

extern void fgPlatformCheckMenuDeactivate(HWND newFocusWnd);

#ifdef WM_TOUCH
typedef BOOL (WINAPI *pGetTouchInputInfo)(HTOUCHINPUT,UINT,PTOUCHINPUT,int);
typedef BOOL (WINAPI *pCloseTouchInputHandle)(HTOUCHINPUT);
static pGetTouchInputInfo fghGetTouchInputInfo = (pGetTouchInputInfo)0xDEADBEEF;
static pCloseTouchInputHandle fghCloseTouchInputHandle = (pCloseTouchInputHandle)0xDEADBEEF;
#endif

#ifdef _WIN32_WCE
typedef struct GXDisplayProperties GXDisplayProperties;
typedef struct GXKeyList GXKeyList;
#include <gx.h>

typedef struct GXKeyList (*GXGETDEFAULTKEYS)(int);
typedef int (*GXOPENINPUT)();

GXGETDEFAULTKEYS GXGetDefaultKeys_ = NULL;
GXOPENINPUT GXOpenInput_ = NULL;

struct GXKeyList gxKeyList;
#endif /* _WIN32_WCE */

#ifdef _DEBUG
/* 
 * WM_ message to string, for debugging
 * This is taken from the 8.0 SDK, so Windows 8 API and everything earlier is included
 */
struct WM_MESSAGE_MAP
{
    UINT    nMsg;
    LPCSTR  lpszMsg;
};
#define DEFINE_MESSAGE(wm){ wm, #wm }
static struct WM_MESSAGE_MAP allMessages[] =
{
    DEFINE_MESSAGE(WM_NULL),
    DEFINE_MESSAGE(WM_CREATE),
    DEFINE_MESSAGE(WM_DESTROY),
    DEFINE_MESSAGE(WM_MOVE),
    DEFINE_MESSAGE(WM_SIZE),

    DEFINE_MESSAGE(WM_ACTIVATE),
    DEFINE_MESSAGE(WM_SETFOCUS),
    DEFINE_MESSAGE(WM_KILLFOCUS),
    DEFINE_MESSAGE(WM_ENABLE),
    DEFINE_MESSAGE(WM_SETREDRAW),
    DEFINE_MESSAGE(WM_SETTEXT),
    DEFINE_MESSAGE(WM_GETTEXT),
    DEFINE_MESSAGE(WM_GETTEXTLENGTH),
    DEFINE_MESSAGE(WM_PAINT),
    DEFINE_MESSAGE(WM_CLOSE),
#   ifndef _WIN32_WCE
        DEFINE_MESSAGE(WM_QUERYENDSESSION),
        DEFINE_MESSAGE(WM_QUERYOPEN),
        DEFINE_MESSAGE(WM_ENDSESSION),
#   endif
    DEFINE_MESSAGE(WM_QUIT),
    DEFINE_MESSAGE(WM_ERASEBKGND),
    DEFINE_MESSAGE(WM_SYSCOLORCHANGE),
    DEFINE_MESSAGE(WM_SHOWWINDOW),
    DEFINE_MESSAGE(WM_WININICHANGE),

    DEFINE_MESSAGE(WM_DEVMODECHANGE),
    DEFINE_MESSAGE(WM_ACTIVATEAPP),
    DEFINE_MESSAGE(WM_FONTCHANGE),
    DEFINE_MESSAGE(WM_TIMECHANGE),
    DEFINE_MESSAGE(WM_CANCELMODE),
    DEFINE_MESSAGE(WM_SETCURSOR),
    DEFINE_MESSAGE(WM_MOUSEACTIVATE),
    DEFINE_MESSAGE(WM_CHILDACTIVATE),
    DEFINE_MESSAGE(WM_QUEUESYNC),

    DEFINE_MESSAGE(WM_GETMINMAXINFO),

    DEFINE_MESSAGE(WM_PAINTICON),
    DEFINE_MESSAGE(WM_ICONERASEBKGND),
    DEFINE_MESSAGE(WM_NEXTDLGCTL),
    DEFINE_MESSAGE(WM_SPOOLERSTATUS),
    DEFINE_MESSAGE(WM_DRAWITEM),
    DEFINE_MESSAGE(WM_MEASUREITEM),
    DEFINE_MESSAGE(WM_DELETEITEM),
    DEFINE_MESSAGE(WM_VKEYTOITEM),
    DEFINE_MESSAGE(WM_CHARTOITEM),
    DEFINE_MESSAGE(WM_SETFONT),
    DEFINE_MESSAGE(WM_GETFONT),
    DEFINE_MESSAGE(WM_SETHOTKEY),
    DEFINE_MESSAGE(WM_GETHOTKEY),
    DEFINE_MESSAGE(WM_QUERYDRAGICON),
    DEFINE_MESSAGE(WM_COMPAREITEM),
#   if(WINVER >= 0x0500)
#       ifndef _WIN32_WCE
            DEFINE_MESSAGE(WM_GETOBJECT),
    #   endif
#   endif /* WINVER >= 0x0500 */
    DEFINE_MESSAGE(WM_COMPACTING),
    DEFINE_MESSAGE(WM_COMMNOTIFY),
    DEFINE_MESSAGE(WM_WINDOWPOSCHANGING),
    DEFINE_MESSAGE(WM_WINDOWPOSCHANGED),

    DEFINE_MESSAGE(WM_POWER),

    DEFINE_MESSAGE(WM_COPYDATA),
    DEFINE_MESSAGE(WM_CANCELJOURNAL),

#   if(WINVER >= 0x0400)
        DEFINE_MESSAGE(WM_NOTIFY),
        DEFINE_MESSAGE(WM_INPUTLANGCHANGEREQUEST),
        DEFINE_MESSAGE(WM_INPUTLANGCHANGE),
        DEFINE_MESSAGE(WM_TCARD),
        DEFINE_MESSAGE(WM_HELP),
        DEFINE_MESSAGE(WM_USERCHANGED),
        DEFINE_MESSAGE(WM_NOTIFYFORMAT),

        DEFINE_MESSAGE(WM_CONTEXTMENU),
        DEFINE_MESSAGE(WM_STYLECHANGING),
        DEFINE_MESSAGE(WM_STYLECHANGED),
        DEFINE_MESSAGE(WM_DISPLAYCHANGE),
        DEFINE_MESSAGE(WM_GETICON),
        DEFINE_MESSAGE(WM_SETICON),
#   endif /* WINVER >= 0x0400 */

    DEFINE_MESSAGE(WM_NCCREATE),
    DEFINE_MESSAGE(WM_NCDESTROY),
    DEFINE_MESSAGE(WM_NCCALCSIZE),
    DEFINE_MESSAGE(WM_NCHITTEST),
    DEFINE_MESSAGE(WM_NCPAINT),
    DEFINE_MESSAGE(WM_NCACTIVATE),
    DEFINE_MESSAGE(WM_GETDLGCODE),
#   ifndef _WIN32_WCE
        DEFINE_MESSAGE(WM_SYNCPAINT),
#   endif
    DEFINE_MESSAGE(WM_NCMOUSEMOVE),
    DEFINE_MESSAGE(WM_NCLBUTTONDOWN),
    DEFINE_MESSAGE(WM_NCLBUTTONUP),
    DEFINE_MESSAGE(WM_NCLBUTTONDBLCLK),
    DEFINE_MESSAGE(WM_NCRBUTTONDOWN),
    DEFINE_MESSAGE(WM_NCRBUTTONUP),
    DEFINE_MESSAGE(WM_NCRBUTTONDBLCLK),
    DEFINE_MESSAGE(WM_NCMBUTTONDOWN),
    DEFINE_MESSAGE(WM_NCMBUTTONUP),
    DEFINE_MESSAGE(WM_NCMBUTTONDBLCLK),



#   if(_WIN32_WINNT >= 0x0500) && defined(WM_NCXBUTTONDOWN)
        DEFINE_MESSAGE(WM_NCXBUTTONDOWN),
        DEFINE_MESSAGE(WM_NCXBUTTONUP),
        DEFINE_MESSAGE(WM_NCXBUTTONDBLCLK),
#   endif /* _WIN32_WINNT >= 0x0500 */


#   if(_WIN32_WINNT >= 0x0501)
        DEFINE_MESSAGE(WM_INPUT_DEVICE_CHANGE),
#   endif /* _WIN32_WINNT >= 0x0501 */

#   if(_WIN32_WINNT >= 0x0501)
        DEFINE_MESSAGE(WM_INPUT),
#   endif /* _WIN32_WINNT >= 0x0501 */

    DEFINE_MESSAGE(WM_KEYDOWN),
    DEFINE_MESSAGE(WM_KEYUP),
    DEFINE_MESSAGE(WM_CHAR),
    DEFINE_MESSAGE(WM_DEADCHAR),
    DEFINE_MESSAGE(WM_SYSKEYDOWN),
    DEFINE_MESSAGE(WM_SYSKEYUP),
    DEFINE_MESSAGE(WM_SYSCHAR),
    DEFINE_MESSAGE(WM_SYSDEADCHAR),
#   if(_WIN32_WINNT >= 0x0501)
        DEFINE_MESSAGE(WM_UNICHAR),
#   endif /* _WIN32_WINNT >= 0x0501 */

#   if(WINVER >= 0x0400)
        DEFINE_MESSAGE(WM_IME_STARTCOMPOSITION),
        DEFINE_MESSAGE(WM_IME_ENDCOMPOSITION),
        DEFINE_MESSAGE(WM_IME_COMPOSITION),
        DEFINE_MESSAGE(WM_IME_KEYLAST),
#   endif /* WINVER >= 0x0400 */

    DEFINE_MESSAGE(WM_INITDIALOG),
    DEFINE_MESSAGE(WM_COMMAND),
    DEFINE_MESSAGE(WM_SYSCOMMAND),
    DEFINE_MESSAGE(WM_TIMER),
    DEFINE_MESSAGE(WM_HSCROLL),
    DEFINE_MESSAGE(WM_VSCROLL),
    DEFINE_MESSAGE(WM_INITMENU),
    DEFINE_MESSAGE(WM_INITMENUPOPUP),
#   if(WINVER >= 0x0601)
        DEFINE_MESSAGE(WM_GESTURE),
        DEFINE_MESSAGE(WM_GESTURENOTIFY),
#   endif /* WINVER >= 0x0601 */
    DEFINE_MESSAGE(WM_MENUSELECT),
    DEFINE_MESSAGE(WM_MENUCHAR),
    DEFINE_MESSAGE(WM_ENTERIDLE),
#   if(WINVER >= 0x0500)
#       ifndef _WIN32_WCE
            DEFINE_MESSAGE(WM_MENURBUTTONUP),
            DEFINE_MESSAGE(WM_MENUDRAG),
            DEFINE_MESSAGE(WM_MENUGETOBJECT),
            DEFINE_MESSAGE(WM_UNINITMENUPOPUP),
            DEFINE_MESSAGE(WM_MENUCOMMAND),

#           if(_WIN32_WINNT >= 0x0500) && defined(WM_CHANGEUISTATE)
                DEFINE_MESSAGE(WM_CHANGEUISTATE),
                DEFINE_MESSAGE(WM_UPDATEUISTATE),
                DEFINE_MESSAGE(WM_QUERYUISTATE),
#           endif /* _WIN32_WINNT >= 0x0500 */

#       endif
#   endif /* WINVER >= 0x0500 */

    DEFINE_MESSAGE(WM_CTLCOLORMSGBOX),
    DEFINE_MESSAGE(WM_CTLCOLOREDIT),
    DEFINE_MESSAGE(WM_CTLCOLORLISTBOX),
    DEFINE_MESSAGE(WM_CTLCOLORBTN),
    DEFINE_MESSAGE(WM_CTLCOLORDLG),
    DEFINE_MESSAGE(WM_CTLCOLORSCROLLBAR),
    DEFINE_MESSAGE(WM_CTLCOLORSTATIC),
#   define MN_GETHMENU                     0x01E1

    DEFINE_MESSAGE(WM_MOUSEMOVE),
    DEFINE_MESSAGE(WM_LBUTTONDOWN),
    DEFINE_MESSAGE(WM_LBUTTONUP),
    DEFINE_MESSAGE(WM_LBUTTONDBLCLK),
    DEFINE_MESSAGE(WM_RBUTTONDOWN),
    DEFINE_MESSAGE(WM_RBUTTONUP),
    DEFINE_MESSAGE(WM_RBUTTONDBLCLK),
    DEFINE_MESSAGE(WM_MBUTTONDOWN),
    DEFINE_MESSAGE(WM_MBUTTONUP),
    DEFINE_MESSAGE(WM_MBUTTONDBLCLK),
#   if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)
        DEFINE_MESSAGE(WM_MOUSEWHEEL),
#   endif
#   if (_WIN32_WINNT >= 0x0500) && defined(WM_XBUTTONDOWN)
        DEFINE_MESSAGE(WM_XBUTTONDOWN),
        DEFINE_MESSAGE(WM_XBUTTONUP),
        DEFINE_MESSAGE(WM_XBUTTONDBLCLK),
#   endif
#   if (_WIN32_WINNT >= 0x0600)
        DEFINE_MESSAGE(WM_MOUSEHWHEEL),
#   endif



    DEFINE_MESSAGE(WM_PARENTNOTIFY),
    DEFINE_MESSAGE(WM_ENTERMENULOOP),
    DEFINE_MESSAGE(WM_EXITMENULOOP),

#   if(WINVER >= 0x0400)
        DEFINE_MESSAGE(WM_NEXTMENU),
        DEFINE_MESSAGE(WM_SIZING),
        DEFINE_MESSAGE(WM_CAPTURECHANGED),
        DEFINE_MESSAGE(WM_MOVING),
#   endif /* WINVER >= 0x0400 */

#   if(WINVER >= 0x0400)
        DEFINE_MESSAGE(WM_POWERBROADCAST),
        DEFINE_MESSAGE(WM_DEVICECHANGE),
#   endif /* WINVER >= 0x0400 */

    DEFINE_MESSAGE(WM_MDICREATE),
    DEFINE_MESSAGE(WM_MDIDESTROY),
    DEFINE_MESSAGE(WM_MDIACTIVATE),
    DEFINE_MESSAGE(WM_MDIRESTORE),
    DEFINE_MESSAGE(WM_MDINEXT),
    DEFINE_MESSAGE(WM_MDIMAXIMIZE),
    DEFINE_MESSAGE(WM_MDITILE),
    DEFINE_MESSAGE(WM_MDICASCADE),
    DEFINE_MESSAGE(WM_MDIICONARRANGE),
    DEFINE_MESSAGE(WM_MDIGETACTIVE),


    DEFINE_MESSAGE(WM_MDISETMENU),
    DEFINE_MESSAGE(WM_ENTERSIZEMOVE),
    DEFINE_MESSAGE(WM_EXITSIZEMOVE),
    DEFINE_MESSAGE(WM_DROPFILES),
    DEFINE_MESSAGE(WM_MDIREFRESHMENU),

#   if(WINVER >= 0x0602)
        DEFINE_MESSAGE(WM_POINTERDEVICECHANGE),
        DEFINE_MESSAGE(WM_POINTERDEVICEINRANGE),
        DEFINE_MESSAGE(WM_POINTERDEVICEOUTOFRANGE),
#   endif /* WINVER >= 0x0602 */

#   if(WINVER >= 0x0601)
        DEFINE_MESSAGE(WM_TOUCH),
#   endif /* WINVER >= 0x0601 */

#   if(WINVER >= 0x0602)
        DEFINE_MESSAGE(WM_NCPOINTERUPDATE),
        DEFINE_MESSAGE(WM_NCPOINTERDOWN),
        DEFINE_MESSAGE(WM_NCPOINTERUP),
        DEFINE_MESSAGE(WM_POINTERUPDATE),
        DEFINE_MESSAGE(WM_POINTERDOWN),
        DEFINE_MESSAGE(WM_POINTERUP),
        DEFINE_MESSAGE(WM_POINTERENTER),
        DEFINE_MESSAGE(WM_POINTERLEAVE),
        DEFINE_MESSAGE(WM_POINTERACTIVATE),
        DEFINE_MESSAGE(WM_POINTERCAPTURECHANGED),
        DEFINE_MESSAGE(WM_TOUCHHITTESTING),
        DEFINE_MESSAGE(WM_POINTERWHEEL),
        DEFINE_MESSAGE(WM_POINTERHWHEEL),
#   endif /* WINVER >= 0x0602 */


#   if(WINVER >= 0x0400)
        DEFINE_MESSAGE(WM_IME_SETCONTEXT),
        DEFINE_MESSAGE(WM_IME_NOTIFY),
        DEFINE_MESSAGE(WM_IME_CONTROL),
        DEFINE_MESSAGE(WM_IME_COMPOSITIONFULL),
        DEFINE_MESSAGE(WM_IME_SELECT),
        DEFINE_MESSAGE(WM_IME_CHAR),
#   endif /* WINVER >= 0x0400 */
#   if(WINVER >= 0x0500)
        DEFINE_MESSAGE(WM_IME_REQUEST),
#   endif /* WINVER >= 0x0500 */
#   if(WINVER >= 0x0400)
        DEFINE_MESSAGE(WM_IME_KEYDOWN),
        DEFINE_MESSAGE(WM_IME_KEYUP),
#   endif /* WINVER >= 0x0400 */

#   if((_WIN32_WINNT >= 0x0400) || (WINVER >= 0x0500))
        DEFINE_MESSAGE(WM_MOUSEHOVER),
        DEFINE_MESSAGE(WM_MOUSELEAVE),
#   endif
#   if(WINVER >= 0x0500) && defined(WM_NCMOUSEHOVER)
        DEFINE_MESSAGE(WM_NCMOUSEHOVER),
        DEFINE_MESSAGE(WM_NCMOUSELEAVE),
#   endif /* WINVER >= 0x0500 */

#   if(_WIN32_WINNT >= 0x0501)
        DEFINE_MESSAGE(WM_WTSSESSION_CHANGE),
#   endif /* _WIN32_WINNT >= 0x0501 */

    DEFINE_MESSAGE(WM_CUT),
    DEFINE_MESSAGE(WM_COPY),
    DEFINE_MESSAGE(WM_PASTE),
    DEFINE_MESSAGE(WM_CLEAR),
    DEFINE_MESSAGE(WM_UNDO),
    DEFINE_MESSAGE(WM_RENDERFORMAT),
    DEFINE_MESSAGE(WM_RENDERALLFORMATS),
    DEFINE_MESSAGE(WM_DESTROYCLIPBOARD),
    DEFINE_MESSAGE(WM_DRAWCLIPBOARD),
    DEFINE_MESSAGE(WM_PAINTCLIPBOARD),
    DEFINE_MESSAGE(WM_VSCROLLCLIPBOARD),
    DEFINE_MESSAGE(WM_SIZECLIPBOARD),
    DEFINE_MESSAGE(WM_ASKCBFORMATNAME),
    DEFINE_MESSAGE(WM_CHANGECBCHAIN),
    DEFINE_MESSAGE(WM_HSCROLLCLIPBOARD),
    DEFINE_MESSAGE(WM_QUERYNEWPALETTE),
    DEFINE_MESSAGE(WM_PALETTEISCHANGING),
    DEFINE_MESSAGE(WM_PALETTECHANGED),
    DEFINE_MESSAGE(WM_HOTKEY),

#   if(WINVER >= 0x0400)
        DEFINE_MESSAGE(WM_PRINT),
        DEFINE_MESSAGE(WM_PRINTCLIENT),
#   endif /* WINVER >= 0x0400 */

#   if(_WIN32_WINNT >= 0x0500) && defined(WM_APPCOMMAND)
        DEFINE_MESSAGE(WM_APPCOMMAND),
#   endif /* _WIN32_WINNT >= 0x0500 */

#   if(_WIN32_WINNT >= 0x0501)
        DEFINE_MESSAGE(WM_THEMECHANGED),
#   endif /* _WIN32_WINNT >= 0x0501 */


#   if(_WIN32_WINNT >= 0x0501)
        DEFINE_MESSAGE(WM_CLIPBOARDUPDATE),
#   endif /* _WIN32_WINNT >= 0x0501 */

#   if(_WIN32_WINNT >= 0x0600)
        DEFINE_MESSAGE(WM_DWMCOMPOSITIONCHANGED),
        DEFINE_MESSAGE(WM_DWMNCRENDERINGCHANGED),
        DEFINE_MESSAGE(WM_DWMCOLORIZATIONCOLORCHANGED),
        DEFINE_MESSAGE(WM_DWMWINDOWMAXIMIZEDCHANGE),
#   endif /* _WIN32_WINNT >= 0x0600 */

#   if(_WIN32_WINNT >= 0x0601)
        DEFINE_MESSAGE(WM_DWMSENDICONICTHUMBNAIL),
        DEFINE_MESSAGE(WM_DWMSENDICONICLIVEPREVIEWBITMAP),
#   endif /* _WIN32_WINNT >= 0x0601 */


#   if(WINVER >= 0x0600)
        DEFINE_MESSAGE(WM_GETTITLEBARINFOEX),
#   endif /* WINVER >= 0x0600 */
    { 0, NULL, }    /* end of message list */
};
#undef DEFINE_MESSAGE

char* WMMsg2Str(DWORD dwMessage)
{
    struct WM_MESSAGE_MAP* pMapMsg = allMessages;
    for (/*null*/; pMapMsg->lpszMsg != NULL; pMapMsg++)
    {
        if (pMapMsg->nMsg == dwMessage )
        {
            return (char *)pMapMsg->lpszMsg;
        }
    }
    return "";
}
#endif /* _DEBUG */


/* Get system time, taking special precautions against 32bit timer wrap.
   We use timeGetTime and not GetTickCount because of its better stability,
   and because we can increase its granularity (to 1 ms in
   fgPlatformInitialize). For that reason we can't use GetTickCount64 which
   wouldn't have the wrap issue.
   Credit: this is based on code in glibc (https://mail.gnome.org/archives/commits-list/2011-November/msg04588.html)
   */
static fg_time_t lastTime32 = 0;
static fg_time_t timeEpoch = 0;
void fgPlatformInitSystemTime()
{
#if defined(_WIN32_WCE)
    lastTime32 = GetTickCount();
#else
    lastTime32 = timeGetTime();
#endif
}
fg_time_t fgPlatformSystemTime ( void )
{
    fg_time_t currTime32;
#if defined(_WIN32_WCE)
    currTime32 = GetTickCount();
#else
    currTime32 = timeGetTime();
#endif
    /* Check if we just wrapped */
    if (currTime32 < lastTime32)
        timeEpoch++;
    
    lastTime32 = currTime32;

    return currTime32 | timeEpoch << 32;
}


void fgPlatformSleepForEvents( fg_time_t msec )
{
    MsgWaitForMultipleObjects( 0, NULL, FALSE, (DWORD) msec, QS_ALLINPUT );
}


void fgPlatformProcessSingleEvent ( void )
{
    MSG stMsg;

    FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutMainLoopEvent" );

    while( PeekMessage( &stMsg, NULL, 0, 0, PM_NOREMOVE ) )
    {
        if( GetMessage( &stMsg, NULL, 0, 0 ) == 0 )
        {
            if( fgState.ActionOnWindowClose == GLUT_ACTION_EXIT )
            {
                fgDeinitialize( );
                exit( 0 );
            }
            else if( fgState.ActionOnWindowClose == GLUT_ACTION_GLUTMAINLOOP_RETURNS )
                fgState.ExecState = GLUT_EXEC_STATE_STOP;

            return;
        }

        TranslateMessage( &stMsg );
        DispatchMessage( &stMsg );
    }
}



static void fghPlatformOnWindowStatusNotify(SFG_Window *window, GLboolean visState, GLboolean forceNotify)
{
    GLboolean notify = GL_FALSE;
    SFG_Window* child;

    if (window->State.Visible != visState)
    {
        window->State.Visible = visState;

        /* If top level window (not a subwindow/child), and icon title text available, switch titles based on visibility state */
        if (!window->Parent && window->State.pWState.IconTitle)
        {
            if (visState)
                /* visible, set window title */
                SetWindowText( window->Window.Handle, window->State.pWState.WindowTitle );
            else
                /* not visible, set icon title */
                SetWindowText( window->Window.Handle, window->State.pWState.IconTitle );
        }

        notify = GL_TRUE;
    }

    if (notify || forceNotify)
    {
        SFG_Window *saved_window = fgStructure.CurrentWindow;

        /* On win32 we only have two states, window displayed and window not displayed (iconified) 
         * We map these to GLUT_FULLY_RETAINED and GLUT_HIDDEN respectively.
         */
        INVOKE_WCB( *window, WindowStatus, ( visState ? GLUT_FULLY_RETAINED:GLUT_HIDDEN ) );
        fgSetWindow( saved_window );
    }

    /* Also set windowStatus/visibility state for children */
    for( child = ( SFG_Window * )window->Children.First;
         child;
         child = ( SFG_Window * )child->Node.Next )
    {
        fghPlatformOnWindowStatusNotify(child, visState, GL_FALSE); /* No need to propagate forceNotify. Childs get this from their own INIT_WORK */
    }
}

void fgPlatformMainLoopPreliminaryWork ( void )
{
    /* no-op */
}


/*
 * Determine a GLUT modifier mask based on MS-WINDOWS system info.
 */
static int fgPlatformGetModifiers (void)
{
    return
        ( ( ( GetKeyState( VK_LSHIFT   ) < 0 ) ||
            ( GetKeyState( VK_RSHIFT   ) < 0 )) ? GLUT_ACTIVE_SHIFT : 0 ) |
        ( ( ( GetKeyState( VK_LCONTROL ) < 0 ) ||
            ( GetKeyState( VK_RCONTROL ) < 0 )) ? GLUT_ACTIVE_CTRL  : 0 ) |
        ( ( ( GetKeyState( VK_LMENU    ) < 0 ) ||
            ( GetKeyState( VK_RMENU    ) < 0 )) ? GLUT_ACTIVE_ALT   : 0 );
}

/* Check whether a button (VK_*BUTTON) is currently depressed. Returns
 * non-zero (not necessarily 1) if yes. */
static SHORT fgGetKeyState(int vKey)
{
    /* MSDN says: "If the high-order bit is 1, the key is down; otherwise, it is up". */
    return GetKeyState(vKey) & 0xFF00;
}

static LRESULT fghWindowProcKeyPress(SFG_Window *window, UINT uMsg, GLboolean keydown, WPARAM wParam, LPARAM lParam)
{
    static unsigned char lControl = 0, lShift = 0, lAlt = 0,
                         rControl = 0, rShift = 0, rAlt = 0;

    int keypress = -1;
    
    /* if keydown, check for repeat */
    /* If repeat is globally switched off, it cannot be switched back on per window.
     * But if it is globally switched on, it can be switched off per window. This matches
     * GLUT's behavior on X11, but not Nate Robbins' win32 GLUT, as he didn't implement the
     * global state switch.
     */
    if( keydown && ( fgState.KeyRepeat==GLUT_KEY_REPEAT_OFF || window->State.IgnoreKeyRepeat==GL_TRUE ) && (HIWORD(lParam) & KF_REPEAT) )
        return 1;
    
    /* Remember the current modifiers state so user can query it from their callback */
    fgState.Modifiers = fgPlatformGetModifiers( );

    /* Convert the Win32 keystroke codes to GLUTtish way */
#   define FG_KEY(a,b) case a: keypress = b; break;

    switch( wParam )
    {
        FG_KEY( VK_F1,     GLUT_KEY_F1        );
        FG_KEY( VK_F2,     GLUT_KEY_F2        );
        FG_KEY( VK_F3,     GLUT_KEY_F3        );
        FG_KEY( VK_F4,     GLUT_KEY_F4        );
        FG_KEY( VK_F5,     GLUT_KEY_F5        );
        FG_KEY( VK_F6,     GLUT_KEY_F6        );
        FG_KEY( VK_F7,     GLUT_KEY_F7        );
        FG_KEY( VK_F8,     GLUT_KEY_F8        );
        FG_KEY( VK_F9,     GLUT_KEY_F9        );
        FG_KEY( VK_F10,    GLUT_KEY_F10       );
        FG_KEY( VK_F11,    GLUT_KEY_F11       );
        FG_KEY( VK_F12,    GLUT_KEY_F12       );
        FG_KEY( VK_PRIOR,  GLUT_KEY_PAGE_UP   );
        FG_KEY( VK_NEXT,   GLUT_KEY_PAGE_DOWN );
        FG_KEY( VK_HOME,   GLUT_KEY_HOME      );
        FG_KEY( VK_END,    GLUT_KEY_END       );
        FG_KEY( VK_LEFT,   GLUT_KEY_LEFT      );
        FG_KEY( VK_UP,     GLUT_KEY_UP        );
        FG_KEY( VK_RIGHT,  GLUT_KEY_RIGHT     );
        FG_KEY( VK_DOWN,   GLUT_KEY_DOWN      );
        FG_KEY( VK_INSERT, GLUT_KEY_INSERT    );

    /* handle control, alt and shift. For GLUT, we want to distinguish between left and right presses.
     * The VK_L* & VK_R* left and right Alt, Ctrl and Shift virtual keys are however only used as parameters to GetAsyncKeyState() and GetKeyState()
     * so when we get an alt, shift or control keypress here, we manually check whether it was the left or the right
     */
#define FG_KEY_EVENT(winKey,glutKey,keyStateVar)\
    if (!keyStateVar && fgGetKeyState ( winKey ))\
    {\
        keypress   = glutKey;\
        keyStateVar = 1;\
    }\
    else if (keyStateVar && !fgGetKeyState ( winKey ))\
    {\
        keypress   = glutKey;\
        keyStateVar = 0;\
    }
    case VK_CONTROL:
        FG_KEY_EVENT(VK_LCONTROL,GLUT_KEY_CTRL_L,lControl);
        FG_KEY_EVENT(VK_RCONTROL,GLUT_KEY_CTRL_R,rControl);
        break;
    case VK_SHIFT:
        FG_KEY_EVENT(VK_LSHIFT,GLUT_KEY_SHIFT_L,lShift);
        FG_KEY_EVENT(VK_RSHIFT,GLUT_KEY_SHIFT_R,rShift);
        break;
    case VK_MENU:
        FG_KEY_EVENT(VK_LMENU,GLUT_KEY_ALT_L,lAlt);
        FG_KEY_EVENT(VK_RMENU,GLUT_KEY_ALT_R,rAlt);
        break;
#undef KEY_EVENT

    case VK_DELETE:
        /* The delete key should be treated as an ASCII keypress: */
        if (keydown)
            INVOKE_WCB( *window, Keyboard,
                        ( 127, window->State.MouseX, window->State.MouseY )
            );
        else
            INVOKE_WCB( *window, KeyboardUp,
                        ( 127, window->State.MouseX, window->State.MouseY )
            );
        break;

#if !defined(_WIN32_WCE)
    default:
        /* keydown displayable characters are handled with WM_CHAR message, but no corresponding up is generated. So get that here. */
        if (!keydown)
        {
            BYTE state[ 256 ];
            WORD code[ 2 ];

            GetKeyboardState( state );

            if( ToAscii( (UINT)wParam, 0, state, code, 0 ) == 1 )
                wParam=code[ 0 ];

            INVOKE_WCB( *window, KeyboardUp,
                   ( (char)(wParam & 0xFF), /* and with 0xFF to indicate to runtime that we want to strip out higher bits - otherwise we get a runtime error when "Smaller Type Checks" is enabled */
                        window->State.MouseX, window->State.MouseY )
            );
        }
#endif
    }

#if defined(_WIN32_WCE)
    if(keydown && !(lParam & 0x40000000)) /* Prevent auto-repeat */
    {
        if(wParam==(unsigned)gxKeyList.vkRight)
            keypress = GLUT_KEY_RIGHT;
        else if(wParam==(unsigned)gxKeyList.vkLeft)
            keypress = GLUT_KEY_LEFT;
        else if(wParam==(unsigned)gxKeyList.vkUp)
            keypress = GLUT_KEY_UP;
        else if(wParam==(unsigned)gxKeyList.vkDown)
            keypress = GLUT_KEY_DOWN;
        else if(wParam==(unsigned)gxKeyList.vkA)
            keypress = GLUT_KEY_F1;
        else if(wParam==(unsigned)gxKeyList.vkB)
            keypress = GLUT_KEY_F2;
        else if(wParam==(unsigned)gxKeyList.vkC)
            keypress = GLUT_KEY_F3;
        else if(wParam==(unsigned)gxKeyList.vkStart)
            keypress = GLUT_KEY_F4;
    }
#endif
    
    if( keypress != -1 )
        if (keydown)
            INVOKE_WCB( *window, Special,
                        ( keypress,
                            window->State.MouseX, window->State.MouseY )
            );
        else
            INVOKE_WCB( *window, SpecialUp,
                        ( keypress,
                            window->State.MouseX, window->State.MouseY )
            );

    fgState.Modifiers = INVALID_MODIFIERS;

    /* SYSKEY events should be sent to default window proc for system to handle them */
    if (uMsg==WM_SYSKEYDOWN || uMsg==WM_SYSKEYUP)
        return DefWindowProc( window->Window.Handle, uMsg, wParam, lParam );
    else
        return 1;
}

SFG_Window* fghWindowUnderCursor(SFG_Window *window)
{
    /* Check if the current window that the mouse is over is a child window
     * of the window the message was sent to. Some events only sent to main window,
     * and when handling some messages, we need to make sure that we process
     * callbacks on the child window instead. This mirrors how GLUT does things.
     * returns either the original window or the found child.
     */
    if (window && window->Children.First)   /* This window has childs */
    {
        HWND hwnd;
        SFG_Window* child_window;

        /* Get mouse position at time of message */
        DWORD mouse_pos_dw = GetMessagePos();
        POINT mouse_pos;
        mouse_pos.x = GET_X_LPARAM(mouse_pos_dw);
        mouse_pos.y = GET_Y_LPARAM(mouse_pos_dw);
        ScreenToClient( window->Window.Handle, &mouse_pos );
        
        hwnd = ChildWindowFromPoint(window->Window.Handle, mouse_pos);
        if (hwnd && hwnd!=window->Window.Handle)   /* can be NULL if mouse outside parent by the time we get here, or can be same as parent if we didn't find a child */
        {
            child_window = fgWindowByHandle(hwnd);
            if (child_window)    /* Verify we got a FreeGLUT window */
            {
                /* ChildWindowFromPoint only searches immediate children, so search again to see if actually in grandchild or further descendant */
                window = fghWindowUnderCursor(child_window);
            }
        }
    }

    return window;
}

/*
 * The window procedure for handling Win32 events
 */
LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    SFG_Window *window;
    LRESULT lRet = 1;
    static int setCaptureActive = 0;

    FREEGLUT_INTERNAL_ERROR_EXIT_IF_NOT_INITIALISED ( "Event Handler" ) ;

    window = fgWindowByHandle( hWnd );

    if ( ( window == NULL ) && ( uMsg != WM_CREATE ) )
      return DefWindowProc( hWnd, uMsg, wParam, lParam );

    /* printf ( "Window %3d message %s (<%04x>) %12d %12d\n", window?window->ID:0,
             WMMsg2Str(uMsg), uMsg, wParam, lParam ); */

    switch( uMsg )
    {
    case WM_CREATE:
        /* The window structure is passed as the creation structure parameter... */
        window = (SFG_Window *) (((LPCREATESTRUCT) lParam)->lpCreateParams);
        FREEGLUT_INTERNAL_ERROR_EXIT ( ( window != NULL ), "Cannot create window",
                                       "fgPlatformWindowProc" );

        window->Window.Handle = hWnd;
        window->Window.pContext.Device = GetDC( hWnd );
        if( window->IsMenu )
        {
            unsigned int current_DisplayMode = fgState.DisplayMode;
            fgState.DisplayMode = GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH;
#if !defined(_WIN32_WCE)
            fgSetupPixelFormat( window, GL_FALSE, PFD_MAIN_PLANE );
#endif
            fgState.DisplayMode = current_DisplayMode;

            if( fgStructure.MenuContext )
                wglMakeCurrent( window->Window.pContext.Device,
                                fgStructure.MenuContext->MContext
                );
            else
            {
                fgStructure.MenuContext =
                    (SFG_MenuContext *)malloc( sizeof(SFG_MenuContext) );
                fgStructure.MenuContext->MContext =
                    wglCreateContext( window->Window.pContext.Device );
            }

            /* window->Window.Context = wglGetCurrentContext ();   */
            window->Window.Context = wglCreateContext( window->Window.pContext.Device );
        }
        else
        {
#if !defined(_WIN32_WCE)
            fgSetupPixelFormat( window, GL_FALSE, PFD_MAIN_PLANE );
#endif

            if( ! fgState.UseCurrentContext )
                window->Window.Context =
                    wglCreateContext( window->Window.pContext.Device );
            else
            {
                window->Window.Context = wglGetCurrentContext( );
                if( ! window->Window.Context )
                    window->Window.Context =
                        wglCreateContext( window->Window.pContext.Device );
            }

#if !defined(_WIN32_WCE)
            fgNewWGLCreateContext( window );
#endif
        }

        ReleaseDC( window->Window.Handle, window->Window.pContext.Device );

#if defined(_WIN32_WCE)
        /* Take over button handling */
        {
            HINSTANCE dxDllLib=LoadLibrary(_T("gx.dll"));
            if (dxDllLib)
            {
                GXGetDefaultKeys_=(GXGETDEFAULTKEYS)GetProcAddress(dxDllLib, _T("?GXGetDefaultKeys@@YA?AUGXKeyList@@H@Z"));
                GXOpenInput_=(GXOPENINPUT)GetProcAddress(dxDllLib, _T("?GXOpenInput@@YAHXZ"));
            }

            if(GXOpenInput_)
                (*GXOpenInput_)();
            if(GXGetDefaultKeys_)
                gxKeyList = (*GXGetDefaultKeys_)(GX_LANDSCAPEKEYS);
        }

#endif /* defined(_WIN32_WCE) */
        break;

    case WM_SIZE:
        /* printf("WM_SIZE (ID: %i): wParam: %i, new size: %ix%i \n",window->ID,wParam,LOWORD(lParam),HIWORD(lParam)); */

        /* Update visibility state of the window */
        if (wParam==SIZE_MINIMIZED)
            fghPlatformOnWindowStatusNotify(window,GL_FALSE,GL_FALSE);
        else if (wParam==SIZE_RESTORED && !window->State.Visible)
            fghPlatformOnWindowStatusNotify(window,GL_TRUE,GL_FALSE);

        /* Check window visible, we don't want do anything when we get a WM_SIZE because the user or glutIconifyWindow minimized the window */
        if( window->State.Visible )
        {
            int width, height;
#if defined(_WIN32_WCE)
            width  = HIWORD(lParam);
            height = LOWORD(lParam);
#else
            width  = LOWORD(lParam);
            height = HIWORD(lParam);
#endif /* defined(_WIN32_WCE) */
            
            /* Update state and call callback, if there was a change */
            fghOnReshapeNotify(window, width, height, GL_FALSE);
        }

        /* according to docs, should return 0 */
        lRet = 0;
        break;

    case WM_SIZING:
        {
            /* User resize-dragging the window, call reshape callback and
             * force redisplay so display keeps running during dragging.
             * Screen still wont update when not moving the cursor though...
             */
            RECT rect;
            /* PRECT prect = (PRECT) lParam;
               printf("WM_SIZING: nc-area: %i,%i\n",prect->right-prect->left,prect->bottom-prect->top); */
            /* Get client area, the rect in lParam is including non-client area. */
            fghGetClientArea(&rect,window,FALSE);

            /* We'll get a WM_SIZE as well, but as state has
             * already been updated here, the fghOnReshapeNotify
             * in the handler for that message doesn't do anything.
             */
            fghOnReshapeNotify(window, rect.right-rect.left, rect.bottom-rect.top, GL_FALSE);

            /* Now directly call the drawing function to update
             * window and window's childs.
             * This mimics the WM_PAINT messages that are received during
             * resizing. Note that we don't have a WM_MOVING handler
             * as move-dragging doesn't generate WM_MOVE or WM_PAINT
             * messages until the mouse is released.
             */
            fghRedrawWindowAndChildren(window);
        }

        /* according to docs, should return TRUE */
        lRet = TRUE;
        break;

    case WM_MOVE:
        {
            /* Check window is minimized, we don't want to call the position callback when the user or glutIconifyWindow minimized the window */
            if (!IsIconic(window->Window.Handle))
            {
                RECT windowRect;
                
                /* lParam contains coordinates of top-left of client area.
                 * Get top-left of non-client area of window, matching coordinates of
                 * glutInitPosition and glutPositionWindow, but not those of 
                 * glutGet(GLUT_WINDOW_X) and glutGet(GLUT_WINDOW_Y), which return
                 * top-left of client area.
                 */
                GetWindowRect( window->Window.Handle, &windowRect );
            
                if (window->Parent)
                {
                    /* For child window, we should return relative to upper-left
                     * of parent's client area.
                     */
                    POINT topleft;
                    topleft.x = windowRect.left;
                    topleft.y = windowRect.top;

                    ScreenToClient(window->Parent->Window.Handle,&topleft);
                    windowRect.left = topleft.x;
                    windowRect.top  = topleft.y;
                }

                /* Update state and call callback, if there was a change */
                fghOnPositionNotify(window, windowRect.left, windowRect.top, GL_FALSE);
            }
        }

        /* according to docs, should return 0 */
        lRet = 0;
        break;

    case WM_SETFOCUS:
        /*printf("WM_SETFOCUS: %p\n", window );*/
        lRet = DefWindowProc( hWnd, uMsg, wParam, lParam );

        SetActiveWindow( window->Window.Handle );
        UpdateWindow ( hWnd );

        break;

    case WM_KILLFOCUS:
        /*printf("WM_KILLFOCUS: %p\n", window ); */
        lRet = DefWindowProc( hWnd, uMsg, wParam, lParam );

        /* Check if there are any open menus that need to be closed */
        fgPlatformCheckMenuDeactivate((HWND)wParam);
        break;

    case WM_MOUSEACTIVATE:
        /* Clicks should not activate the menu.
         * Especially important when clicking on a menu's submenu item which has no effect.
         */
        /*printf("WM_MOUSEACTIVATE\n");*/
        if (window->IsMenu)
            lRet = MA_NOACTIVATEANDEAT;
        else
            lRet = DefWindowProc( hWnd, uMsg, wParam, lParam );
        break;

    case WM_NCLBUTTONDOWN:
    case WM_NCMBUTTONDOWN:
    case WM_NCRBUTTONDOWN:
        {
            SFG_Menu *menu;
            if (fgState.ActiveMenus && (menu = fgGetActiveMenu()))
                /* user clicked non-client area of window while a menu is open. Close menu */
                fgDeactivateMenu(menu->ParentWindow);

            /* and always pass to DefWindowProc */
            lRet = DefWindowProc( hWnd, uMsg, wParam, lParam );
        }
        break;

#if 0
    case WM_ACTIVATE:
        /* printf("WM_ACTIVATE: %x (ID: %i) %d %d\n",lParam, window->ID, HIWORD(wParam), LOWORD(wParam)); */
        if (LOWORD(wParam) != WA_INACTIVE)
        {
/*            printf("WM_ACTIVATE: fgSetCursor( %p, %d)\n", window,
                   window->State.Cursor ); */
            fgSetCursor( window, window->State.Cursor );
        }

        lRet = DefWindowProc( hWnd, uMsg, wParam, lParam );
        break;
#endif

    case WM_SETCURSOR:
/*      printf ( "Cursor event %x %x %x %x\n", window, window->State.Cursor, lParam, wParam ) ; */
        if( LOWORD( lParam ) == HTCLIENT )
        {
            if (!window->State.pWState.MouseTracking)
            {
                TRACKMOUSEEVENT tme;

                /* Cursor just entered window, set cursor look */ 
                fgSetCursor ( window, window->State.Cursor ) ;

                /* If an EntryFunc callback is specified by the user, also
                 * invoke that callback and start mouse tracking so that
                 * we get a WM_MOUSELEAVE message
                 */
                if (FETCH_WCB( *window, Entry ))
                {
                    SFG_Window* saved_window = fgStructure.CurrentWindow;
                    INVOKE_WCB( *window, Entry, ( GLUT_ENTERED ) );
                    fgSetWindow(saved_window);

                    tme.cbSize = sizeof(TRACKMOUSEEVENT);
                    tme.dwFlags = TME_LEAVE;
                    tme.hwndTrack = window->Window.Handle;
                    TrackMouseEvent(&tme);

                    window->State.pWState.MouseTracking = TRUE;
                }
            }
        }
        else
            /* Only pass non-client WM_SETCURSOR to DefWindowProc, or we get WM_SETCURSOR on parents of children as well */
            lRet = DefWindowProc( hWnd, uMsg, wParam, lParam );
        break;

    case WM_MOUSELEAVE:
        {
            /* NB: This message is only received when a EntryFunc callback
             * is specified by the user, as that is the only condition under
             * which mouse tracking is setup in WM_SETCURSOR handler above
             */
            SFG_Window* saved_window = fgStructure.CurrentWindow;
            INVOKE_WCB( *window, Entry, ( GLUT_LEFT ) );
            fgSetWindow(saved_window);

            window->State.pWState.MouseTracking = FALSE;
            lRet = 0;   /* As per docs, must return zero */
        }
        break;

    case WM_SHOWWINDOW:
        /* printf("WM_SHOWWINDOW, shown? %i, source: %i\n",wParam,lParam); */
        if (wParam)
        {
            fghPlatformOnWindowStatusNotify(window, GL_TRUE, GL_FALSE);
            window->State.WorkMask |= GLUT_DISPLAY_WORK;
        }
        else
        {
            fghPlatformOnWindowStatusNotify(window, GL_FALSE, GL_FALSE);
            window->State.WorkMask &= ~GLUT_DISPLAY_WORK;
        }
        break;

    case WM_PAINT:
    {
        RECT rect;
        
        /* As per docs, upon receiving WM_PAINT, first check if the update region is not empty before you call BeginPaint */
        if (GetUpdateRect(hWnd,&rect,FALSE))
        {
            /* Dummy begin/end paint to validate rect that needs
             * redrawing, then signal that a redisplay is needed.
             * This allows us full control about when we do any
             * redrawing, and is the same as what original GLUT
             * does.
             */
            PAINTSTRUCT ps;
            BeginPaint( hWnd, &ps );
            EndPaint( hWnd, &ps );

            window->State.WorkMask |= GLUT_DISPLAY_WORK;
        }
        lRet = 0;   /* As per docs, should return 0 */
    }
    break;

    case WM_CLOSE:
        fgDestroyWindow ( window );
        if ( fgState.ActionOnWindowClose != GLUT_ACTION_CONTINUE_EXECUTION )
            PostQuitMessage(0);
        break;

    case WM_DESTROY:
        /*
         * The window already got destroyed, so don't bother with it.
         */
        return 0;

    case WM_MOUSEMOVE:
    {
        /* Per docs, use LOWORD/HIWORD for WinCE and GET_X_LPARAM/GET_Y_LPARAM for desktop windows */
#if defined(_WIN32_WCE)
        window->State.MouseX = 320-HIWORD( lParam );    /* XXX: Docs say x should be loword and y hiword? */
        window->State.MouseY = LOWORD( lParam );
#else
        window->State.MouseX = GET_X_LPARAM( lParam );
        window->State.MouseY = GET_Y_LPARAM( lParam );
#endif /* defined(_WIN32_WCE) */
        /* Restrict to [-32768, 32767] to match X11 behaviour       */
        /* See comment in "freeglut_developer" mailing list 10/4/04 */
        if ( window->State.MouseX > 32767 ) window->State.MouseX -= 65536;
        if ( window->State.MouseY > 32767 ) window->State.MouseY -= 65536;

        if ( window->ActiveMenu )
        {
            fgUpdateMenuHighlight( window->ActiveMenu );
            break;
        }

        fgState.Modifiers = fgPlatformGetModifiers( );

        if( ( wParam & MK_LBUTTON ) ||
            ( wParam & MK_MBUTTON ) ||
            ( wParam & MK_RBUTTON ) )
            INVOKE_WCB( *window, Motion, ( window->State.MouseX,
                                           window->State.MouseY ) );
        else
            INVOKE_WCB( *window, Passive, ( window->State.MouseX,
                                            window->State.MouseY ) );

        fgState.Modifiers = INVALID_MODIFIERS;
    }
    break;

    case WM_LBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_MBUTTONUP:
    case WM_RBUTTONUP:
    {
        GLboolean pressed = GL_TRUE;
        int button;

        /* Per docs, use LOWORD/HIWORD for WinCE and GET_X_LPARAM/GET_Y_LPARAM for desktop windows */
#if defined(_WIN32_WCE)
        window->State.MouseX = 320-HIWORD( lParam );    /* XXX: Docs say x should be loword and y hiword? */
        window->State.MouseY = LOWORD( lParam );
#else
        window->State.MouseX = GET_X_LPARAM( lParam );
        window->State.MouseY = GET_Y_LPARAM( lParam );
#endif /* defined(_WIN32_WCE) */

        /* Restrict to [-32768, 32767] to match X11 behaviour       */
        /* See comment in "freeglut_developer" mailing list 10/4/04 */
        if ( window->State.MouseX > 32767 ) window->State.MouseX -= 65536;
        if ( window->State.MouseY > 32767 ) window->State.MouseY -= 65536;

        switch( uMsg )
        {
        case WM_LBUTTONDOWN:
            pressed = GL_TRUE;
            button = GLUT_LEFT_BUTTON;
            break;
        case WM_MBUTTONDOWN:
            pressed = GL_TRUE;
            button = GLUT_MIDDLE_BUTTON;
            break;
        case WM_RBUTTONDOWN:
            pressed = GL_TRUE;
            button = GLUT_RIGHT_BUTTON;
            break;
        case WM_LBUTTONUP:
            pressed = GL_FALSE;
            button = GLUT_LEFT_BUTTON;
            break;
        case WM_MBUTTONUP:
            pressed = GL_FALSE;
            button = GLUT_MIDDLE_BUTTON;
            break;
        case WM_RBUTTONUP:
            pressed = GL_FALSE;
            button = GLUT_RIGHT_BUTTON;
            break;
        default:
            pressed = GL_FALSE;
            button = -1;
            break;
        }

#if !defined(_WIN32_WCE)
        if( GetSystemMetrics( SM_SWAPBUTTON ) )
        {
            if( button == GLUT_LEFT_BUTTON )
                button = GLUT_RIGHT_BUTTON;
            else
                if( button == GLUT_RIGHT_BUTTON )
                    button = GLUT_LEFT_BUTTON;
        }
#endif /* !defined(_WIN32_WCE) */

        if( button == -1 )
            return DefWindowProc( hWnd, uMsg, lParam, wParam );

        /*
         * Do not execute the application's mouse callback if a menu
         * is hooked to this button.  In that case an appropriate
         * private call should be generated.
         */
        if( fgCheckActiveMenu( window, button, pressed,
                               window->State.MouseX, window->State.MouseY ) )
            break;

        /* Set capture so that the window captures all the mouse messages
         *
         * The mouse is not released from the window until all buttons have
         * been released, even if the user presses a button in another window.
         * This is consistent with the behavior on X11.
         */
        if ( pressed == GL_TRUE )
        {
            if (!setCaptureActive)
                SetCapture ( window->Window.Handle ) ;
            setCaptureActive = 1; /* Set to false in WM_CAPTURECHANGED handler */
        }
        else if (!fgGetKeyState(VK_LBUTTON) && !fgGetKeyState(VK_MBUTTON) && !fgGetKeyState(VK_RBUTTON))
          /* Make sure all mouse buttons are released before releasing capture */
          ReleaseCapture () ;

        if( ! FETCH_WCB( *window, Mouse ) )
            break;

        fgSetWindow( window );
        fgState.Modifiers = fgPlatformGetModifiers( );

        INVOKE_WCB(
            *window, Mouse,
            ( button,
              pressed ? GLUT_DOWN : GLUT_UP,
              window->State.MouseX,
              window->State.MouseY
            )
        );

        fgState.Modifiers = INVALID_MODIFIERS;

        /* As per docs, should return zero */
        lRet = 0;
    }
    break;

    case WM_MOUSEWHEEL:
    {
        int wheel_number = 0;   /* Only one scroll wheel on windows */
#if defined(_WIN32_WCE)
        int modkeys = LOWORD(wParam); 
        short ticks = (short)HIWORD(wParam);
        /* commented out as should not be needed here, mouse motion is processed in WM_MOUSEMOVE first:
        xPos = LOWORD(lParam);  -- straight from docs, not consistent with mouse nutton and mouse motion above (which i think is wrong)
        yPos = HIWORD(lParam);
        */
#else
        /* int modkeys = GET_KEYSTATE_WPARAM( wParam ); */
        short ticks = HIWORD( wParam );
        /* commented out as should not be needed here, mouse motion is processed in WM_MOUSEMOVE first:
        window->State.MouseX = GET_X_LPARAM( lParam );
        window->State.MouseY = GET_Y_LPARAM( lParam );
        */
#endif /* defined(_WIN32_WCE) */

        window = fghWindowUnderCursor(window);

		fgState.MouseWheelTicks += ticks;
        if ( abs ( fgState.MouseWheelTicks ) >= WHEEL_DELTA )
		{
			int direction = ( fgState.MouseWheelTicks > 0 ) ? 1 : -1;

            if( ! FETCH_WCB( *window, MouseWheel ) &&
                ! FETCH_WCB( *window, Mouse ) )
                break;

            fgSetWindow( window );
            fgState.Modifiers = fgPlatformGetModifiers( );

            while( abs ( fgState.MouseWheelTicks ) >= WHEEL_DELTA )
			{
                if( FETCH_WCB( *window, MouseWheel ) )
                    INVOKE_WCB( *window, MouseWheel,
                                ( wheel_number,
                                  direction,
                                  window->State.MouseX,
                                  window->State.MouseY
                                )
                    );
                else  /* No mouse wheel, call the mouse button callback twice */
				{
                    /*
                     * Map wheel zero to button 3 and 4; +1 to 3, -1 to 4
                     *  "    "   one                     +1 to 5, -1 to 6, ...
                     *
                     * XXX The below assumes that you have no more than 3 mouse
                     * XXX buttons.  Sorry.
                     */
                    int button = wheel_number * 2 + 3;
                    if( direction < 0 )
                        ++button;
                    INVOKE_WCB( *window, Mouse,
                                ( button, GLUT_DOWN,
                                  window->State.MouseX, window->State.MouseY )
                    );
                    INVOKE_WCB( *window, Mouse,
                                ( button, GLUT_UP,
                                  window->State.MouseX, window->State.MouseY )
                    );
				}

				fgState.MouseWheelTicks -= WHEEL_DELTA * direction;
			}

            fgState.Modifiers = INVALID_MODIFIERS;
		}
        /* Per docs, should return zero */
        lRet = 0;
    }
    break ;

    case WM_SYSKEYDOWN:
    case WM_KEYDOWN:
    {
        window = fghWindowUnderCursor(window);
        lRet = fghWindowProcKeyPress(window,uMsg,GL_TRUE,wParam,lParam);
    }
    break;

    case WM_SYSKEYUP:
    case WM_KEYUP:
    {
        window = fghWindowUnderCursor(window);
        lRet = fghWindowProcKeyPress(window,uMsg,GL_FALSE,wParam,lParam);
    }
    break;

    case WM_SYSCHAR:
    case WM_CHAR:
    {
      window = fghWindowUnderCursor(window);

      if( (fgState.KeyRepeat==GLUT_KEY_REPEAT_OFF || window->State.IgnoreKeyRepeat==GL_TRUE) && (HIWORD(lParam) & KF_REPEAT) )
            break;

        fgState.Modifiers = fgPlatformGetModifiers( );
        INVOKE_WCB( *window, Keyboard,
                    ( (char)wParam,
                      window->State.MouseX, window->State.MouseY )
        );
        fgState.Modifiers = INVALID_MODIFIERS;
    }
    break;

    case WM_CAPTURECHANGED:
        if (!lParam || !fgWindowByHandle((HWND)lParam))
            /* Capture released or capture taken by non-FreeGLUT window */
            setCaptureActive = 0;
        /* Docs advise a redraw */
        InvalidateRect( hWnd, NULL, GL_FALSE );
        UpdateWindow(hWnd);
        lRet = 0;   /* Per docs, should return zero */
        break;

#if !defined(_WIN32_WCE)
    case WM_SYNCPAINT:  /* 0x0088 */
        /* Another window has moved, need to update this one */
        window->State.WorkMask |= GLUT_DISPLAY_WORK;
        lRet = DefWindowProc( hWnd, uMsg, wParam, lParam );
        /* Help screen says this message must be passed to "DefWindowProc" */
        break;

    case WM_DISPLAYCHANGE: /* 0x007E */
        /* The system display resolution/depth has changed */
        fgDisplay.ScreenWidth = LOWORD(lParam);
        fgDisplay.ScreenHeight = HIWORD(lParam);
        break;

    case WM_SYSCOMMAND :  /* 0x0112 */
        {
          /*
           * We have received a system command message.  Try to act on it.
           * The commands are passed in through the "wParam" parameter:
           * The least significant digit seems to be which edge of the window
           * is being used for a resize event:
           *     4  3  5
           *     1     2
           *     7  6  8
           * Congratulations and thanks to Richard Rauch for figuring this out..
           */
            switch ( wParam & 0xfff0 )
            {
            case SC_SIZE       :
                break ;

            case SC_MOVE       :
                break ;

            case SC_MINIMIZE   :
                /* User has clicked on the "-" to minimize the window */
                /* Turning off the visibility is handled in WM_SIZE handler */

                break ;

            case SC_MAXIMIZE   :
                break ;

            case SC_NEXTWINDOW :
                break ;

            case SC_PREVWINDOW :
                break ;

            case SC_CLOSE      :
                /* Followed very closely by a WM_CLOSE message */
                break ;

            case SC_VSCROLL    :
                break ;

            case SC_HSCROLL    :
                break ;

            case SC_MOUSEMENU  :
                break ;

            case SC_KEYMENU    :
                break ;

            case SC_ARRANGE    :
                break ;

            case SC_RESTORE    :
                break ;

            case SC_TASKLIST   :
                break ;

            case SC_SCREENSAVE :
                break ;

            case SC_HOTKEY     :
                break ;

#if(WINVER >= 0x0400)
            case SC_DEFAULT    :
                break ;

            case SC_MONITORPOWER    :
                break ;

            case SC_CONTEXTHELP    :
                break ;
#endif /* WINVER >= 0x0400 */

            default:
#if _DEBUG
                fgWarning( "Unknown wParam type 0x%x", wParam );
#endif
                break;
            }
        }
#endif /* !defined(_WIN32_WCE) */

        /* We need to pass the message on to the operating system as well */
        lRet = DefWindowProc( hWnd, uMsg, wParam, lParam );
        break;

#ifdef WM_TOUCH
	/* handle multi-touch messages */
	case WM_TOUCH:
	{
		unsigned int numInputs = (unsigned int)wParam;
		unsigned int i = 0;
		TOUCHINPUT* ti = (TOUCHINPUT*)malloc( sizeof(TOUCHINPUT)*numInputs);

		if (fghGetTouchInputInfo == (pGetTouchInputInfo)0xDEADBEEF) {
		    fghGetTouchInputInfo = (pGetTouchInputInfo)GetProcAddress(GetModuleHandle("user32"),"GetTouchInputInfo");
		    fghCloseTouchInputHandle = (pCloseTouchInputHandle)GetProcAddress(GetModuleHandle("user32"),"CloseTouchInputHandle");
		}

		if (!fghGetTouchInputInfo) { 
			free( (void*)ti );
			break;
		}

		if (fghGetTouchInputInfo( (HTOUCHINPUT)lParam, numInputs, ti, sizeof(TOUCHINPUT) )) {
			/* Handle each contact point */
			for (i = 0; i < numInputs; ++i ) {

				POINT tp;
				tp.x = TOUCH_COORD_TO_PIXEL(ti[i].x);
				tp.y = TOUCH_COORD_TO_PIXEL(ti[i].y);
				ScreenToClient( hWnd, &tp );

				ti[i].dwID = ti[i].dwID * 2;

				if (ti[i].dwFlags & TOUCHEVENTF_DOWN) {
					INVOKE_WCB( *window, MultiEntry,  ( ti[i].dwID, GLUT_ENTERED ) );
					INVOKE_WCB( *window, MultiButton, ( ti[i].dwID, tp.x, tp.y, 0, GLUT_DOWN ) );
				} else if (ti[i].dwFlags & TOUCHEVENTF_MOVE) {
					INVOKE_WCB( *window, MultiMotion, ( ti[i].dwID, tp.x, tp.y ) );
				} else if (ti[i].dwFlags & TOUCHEVENTF_UP)   { 
					INVOKE_WCB( *window, MultiButton, ( ti[i].dwID, tp.x, tp.y, 0, GLUT_UP ) );
					INVOKE_WCB( *window, MultiEntry,  ( ti[i].dwID, GLUT_LEFT ) );
				}
			}
		}
		fghCloseTouchInputHandle((HTOUCHINPUT)lParam);
		free( (void*)ti );
		lRet = 0; /*DefWindowProc( hWnd, uMsg, wParam, lParam );*/
		break;
	}
#endif
    default:
        /* Handle unhandled messages */
        lRet = DefWindowProc( hWnd, uMsg, wParam, lParam );
        break;
    }

    return lRet;
}


/* deal with work list items */
void fgPlatformInitWork(SFG_Window* window)
{
    RECT windowRect;

    /* Notify windowStatus/visibility */
    fghPlatformOnWindowStatusNotify(window, window->State.Visible, GL_TRUE);

    /* get and notify window's position */
    GetWindowRect(window->Window.Handle,&windowRect);
    fghOnPositionNotify(window, windowRect.left, windowRect.top, GL_TRUE);

    /* get and notify window's size */
    GetClientRect(window->Window.Handle,&windowRect);
    fghOnReshapeNotify(window, windowRect.right-windowRect.left, windowRect.bottom-windowRect.top, GL_TRUE);
}

/* On windows we can position, resize and change z order at the same time */
void fgPlatformPosResZordWork(SFG_Window* window, unsigned int workMask)
{
    UINT flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOZORDER;
    HWND insertAfter = HWND_TOP;
    RECT clientRect;

#if !defined(_WIN32_WCE) /* FIXME: what about WinCE */
    if (workMask & GLUT_FULL_SCREEN_WORK)
    {
        /* This asks us to toggle fullscreen mode */
        flags |= SWP_FRAMECHANGED;

        if (window->State.IsFullscreen)
        {
            /* If we are fullscreen, resize the current window back to its original size */
            /* printf("OldRect %i,%i to %i,%i\n",window->State.pWState.OldRect.left,window->State.pWState.OldRect.top,window->State.pWState.OldRect.right,window->State.pWState.OldRect.bottom); */

            /* restore style of window before making it fullscreen */
            SetWindowLong(window->Window.Handle, GWL_STYLE, window->State.pWState.OldStyle);
            SetWindowLong(window->Window.Handle, GWL_EXSTYLE, window->State.pWState.OldStyleEx);

            /* Then set up resize/reposition, unless user already queued up reshape/position work */
            if (!(workMask & GLUT_POSITION_WORK))
            {
                workMask |= GLUT_POSITION_WORK;
                window->State.DesiredXpos   = window->State.pWState.OldRect.left;
                window->State.DesiredYpos   = window->State.pWState.OldRect.top;
            }
            if (!(workMask & GLUT_SIZE_WORK))
            {
                workMask |= GLUT_SIZE_WORK;
                window->State.DesiredWidth  = window->State.pWState.OldRect.right  - window->State.pWState.OldRect.left;
                window->State.DesiredHeight = window->State.pWState.OldRect.bottom - window->State.pWState.OldRect.top;
            }
                
            /* We'll finish off the fullscreen operation below after the other GLUT_POSITION_WORK|GLUT_SIZE_WORK|GLUT_ZORDER_WORK */
        }
        else
        {
            /* we are currently not fullscreen, go to fullscreen:
                * remove window decoration and then maximize
                */
            RECT rect;
            HMONITOR hMonitor;
            MONITORINFO mi;
        
            /* save current window rect, style, exstyle and maximized state */
            window->State.pWState.OldMaximized = !!IsZoomed(window->Window.Handle);
            if (window->State.pWState.OldMaximized)
                /* We force the window into restored mode before going
                    * fullscreen because Windows doesn't seem to hide the
                    * taskbar if the window is in the maximized state.
                    */
                SendMessage(window->Window.Handle, WM_SYSCOMMAND, SC_RESTORE, 0);

            fghGetClientArea( &window->State.pWState.OldRect, window, GL_TRUE );
            window->State.pWState.OldStyle   = GetWindowLong(window->Window.Handle, GWL_STYLE);
            window->State.pWState.OldStyleEx = GetWindowLong(window->Window.Handle, GWL_EXSTYLE);

            /* remove decorations from style */
            SetWindowLong(window->Window.Handle, GWL_STYLE,
                            window->State.pWState.OldStyle & ~(WS_CAPTION | WS_THICKFRAME));
            SetWindowLong(window->Window.Handle, GWL_EXSTYLE,
                            window->State.pWState.OldStyleEx & ~(WS_EX_DLGMODALFRAME |
                            WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));

            /* For fullscreen mode, find the monitor that is covered the most
                * by the window and get its rect as the resize target.
	            */
            hMonitor= MonitorFromWindow(window->Window.Handle, MONITOR_DEFAULTTONEAREST);
            mi.cbSize = sizeof(mi);
            GetMonitorInfo(hMonitor, &mi);
            rect = mi.rcMonitor;

            /* then setup window resize, overwriting other work queued on the window */
            window->State.WorkMask |= GLUT_POSITION_WORK | GLUT_SIZE_WORK;
            window->State.WorkMask &= ~GLUT_ZORDER_WORK;
            window->State.DesiredXpos   = rect.left;
            window->State.DesiredYpos   = rect.top;
            window->State.DesiredWidth  = rect.right  - rect.left;
            window->State.DesiredHeight = rect.bottom - rect.top;
        }
    }
#endif /*!defined(_WIN32_WCE) */

    /* Now deal with normal position, reshape and z order requests (some might have been set when handling GLUT_FULLSCREEN_WORK above */
    {
        /* get rect describing window's current position and size, 
            * in screen coordinates and in FreeGLUT format
            * (size (right-left, bottom-top) is client area size, top and left
            * are outside of window including decorations).
            */
        fghGetClientArea( &clientRect, window, TRUE );

        if (workMask & GLUT_POSITION_WORK)
        {
            flags &= ~SWP_NOMOVE;
                
            /* Move rect so that top-left is at requested position */
            /* This also automatically makes sure that child window requested coordinates are relative
                * to top-left of parent's client area (needed input for SetWindowPos on child windows),
                * so no need to further correct rect for child windows below (childs don't have decorations either).
                */
            OffsetRect(&clientRect,window->State.DesiredXpos-clientRect.left,window->State.DesiredYpos-clientRect.top);
        }
        if (workMask & GLUT_SIZE_WORK)
        {
            flags &= ~SWP_NOSIZE;
                
            /* Note on maximizing behavior of Windows: the resize borders are off
                * the screen such that the client area extends all the way from the
                * leftmost corner to the rightmost corner to maximize screen real
                * estate. A caption is still shown however to allow interaction with
                * the window controls. This is default behavior of Windows that
                * FreeGLUT sticks with. To alter, one would have to check if
                * WS_MAXIMIZE style is set when a resize event is triggered, and
                * then manually correct the windowRect to put the borders back on
                * screen.
                */

            /* Set new size of window, WxH specify client area */
            clientRect.right    = clientRect.left + window->State.DesiredWidth;
            clientRect.bottom   = clientRect.top  + window->State.DesiredHeight;
        }
        if (workMask & GLUT_ZORDER_WORK)
        {
            flags &= ~SWP_NOZORDER;

            /* Could change this to push it down or up one window at a time with some
                * more code using GetWindow with GW_HWNDPREV and GW_HWNDNEXT.
                * What would be consistent with X11? Win32 GLUT does what we do here...
                */
            if (window->State.DesiredZOrder < 0)
                insertAfter = HWND_BOTTOM;
        }
    }

    /* Adjust for window decorations
        * Child windows don't have decoration, so no need to correct
        */
    if (!window->Parent)
        /* get the window rect from this to feed to SetWindowPos, correct for window decorations */
        fghComputeWindowRectFromClientArea_QueryWindow(&clientRect,window,TRUE);
    
    /* Do the requested positioning, moving, and z order push/pop. */
    SetWindowPos( window->Window.Handle,
                    insertAfter,
                    clientRect.left, clientRect.top,
                    clientRect.right - clientRect.left,
                    clientRect.bottom- clientRect.top,
                    flags
    );

    /* Finish off the fullscreen operation we were doing, if any */
    if (workMask & GLUT_FULL_SCREEN_WORK)
    {
        if (window->State.IsFullscreen)
        {
            /* leaving fullscreen, restore maximized state, if any */
            if (window->State.pWState.OldMaximized)
                SendMessage(window->Window.Handle, WM_SYSCOMMAND, SC_MAXIMIZE, 0);

            window->State.IsFullscreen = GL_FALSE;
        }
        else
            window->State.IsFullscreen = GL_TRUE;
    }
}


void fgPlatformVisibilityWork(SFG_Window* window)
{
    /* Visibility status of window gets updated in the WM_SHOWWINDOW and WM_SIZE handlers */
    int cmdShow = 0;
    SFG_Window *win = window;
    switch (window->State.DesiredVisibility)
    {
    case DesireHiddenState:
        cmdShow = SW_HIDE;
        break;
    case DesireIconicState:
        cmdShow = SW_MINIMIZE;
        /* Call on top-level window */
        while (win->Parent)
            win = win->Parent;
        break;
    case DesireNormalState:
        if (win->IsMenu && !fgStructure.GameModeWindow)
            cmdShow = SW_SHOWNA;    /* Just show, don't activate window if its a menu. Only exception is when there is a gamemode window as the menu would pop under it when we do this... */
        else
            cmdShow = SW_SHOW;
        break;
    }

    ShowWindow( win->Window.Handle, cmdShow );
}
