/*
 * Copyright (C) 1996-1997 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */
// vid_wgl.c -- Windows 9x/NT OpenGL driver

#include "quakedef.h"
#include "winquake.h"
#include "resource.h"
#include <commctrl.h>

#define MAX_MODE_LIST	128
#define WARP_WIDTH		320
#define WARP_HEIGHT		200
#define MAXWIDTH		10000
#define MAXHEIGHT		10000
#define BASEWIDTH		320
#define BASEHEIGHT		200

#define MODE_WINDOWED	0
#define NO_MODE			(MODE_WINDOWED - 1)
#define MODE_FULLSCREEN_DEFAULT	(MODE_WINDOWED + 1)

qboolean      gl_have_stencil = false;

typedef struct {
    modestate_t   type;
    int           width;
    int           height;
    int           modenum;
    int           dib;
    int           fullscreen;
    int           bpp;
    int           halfscreen;
    char          modedesc[17];
} vmode_t;

typedef struct {
    int           width;
    int           height;
} lmode_t;

lmode_t       lowresmodes[] = {
    {320, 200},
    {320, 240},
    {400, 300},
    {512, 384},
};

qboolean      DDActive;
qboolean      scr_skipupdate;

static vmode_t modelist[MAX_MODE_LIST];
static int    nummodes;
static vmode_t *pcurrentmode;
static vmode_t badmode;

static DEVMODE gdevmode;
static qboolean vid_initialized = false;
static qboolean windowed, leavecurrentmode;
static qboolean vid_canalttab = false;
static qboolean vid_wassuspended = false;
static qboolean windowed_mouse = true;
extern qboolean mouseactive;           // from in_win.c
static HICON  hIcon;

int           DIBWidth, DIBHeight;
RECT          WindowRect;
DWORD         WindowStyle, ExWindowStyle;

HWND          mainwindow, dibwindow;

int           vid_modenum = NO_MODE;
int           vid_default = MODE_WINDOWED;
static int    windowed_default;
unsigned char vid_curpal[256 * 3];
qboolean      fullsbardraw = false;

HGLRC         baseRC;
HDC           maindc;

glvert_t      glv;

unsigned short *currentgammaramp = NULL;
static unsigned short systemgammaramp[3][256];

qboolean      vid_gammaworks = false;
qboolean      vid_hwgamma_enabled = false;
qboolean      customgamma = false;

void          RestoreHWGamma (void);

HWND WINAPI   InitializeWindow (HINSTANCE hInstance, int nCmdShow);

modestate_t   modestate = MS_UNINIT;

void          VID_MenuDraw (void);
void          VID_MenuKey (int key);

LONG WINAPI   MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void          AppActivate (BOOL fActive, BOOL minimize);
char         *VID_GetModeDescription (int mode);
void          ClearAllStates (void);
void          VID_UpdateWindowStatus (void);
void          GL_Init (void);

cvar_t        vid_mode = { "vid_mode", "0" };
cvar_t        _vid_default_mode = { "_vid_default_mode", "0", CVAR_ARCHIVE };
cvar_t        _vid_default_mode_win = { "_vid_default_mode_win", "3", CVAR_ARCHIVE };
cvar_t        vid_config_x = { "vid_config_x", "800", CVAR_ARCHIVE };
cvar_t        vid_config_y = { "vid_config_y", "600", CVAR_ARCHIVE };
cvar_t        _windowed_mouse = { "_windowed_mouse", "1", CVAR_ARCHIVE };
cvar_t        vid_displayfrequency = { "vid_displayfrequency", "0", CVAR_INIT };
cvar_t        vid_hwgammacontrol = { "vid_hwgammacontrol", "1" };

typedef       BOOL (APIENTRY * SWAPINTERVALFUNCPTR) (int);
SWAPINTERVALFUNCPTR wglSwapIntervalEXT = NULL;
static qboolean update_vsync = false;
qboolean      OnChange_vid_vsync (cvar_t * var, char *string);
//cvar_t        vid_vsync = { "vid_vsync", "0", 0, OnChange_vid_vsync }; // lxndr: useless onchange (?)
cvar_t        vid_vsync = { "vid_vsync", "0", 0 };

int           window_center_x, window_center_y, window_x, window_y, window_width, window_height;
RECT          window_rect;

// lxndr: not tested
void         *qglGetProcAddress (const char *proc) {
    return wglGetProcAddress ((byte *) proc);
}

void CheckVsyncControlExtensions (void) {
    if (!COM_CheckParm ("-noswapctrl") && CheckExtension ("WGL_EXT_swap_control")) {
        if ((wglSwapIntervalEXT = qglGetProcAddress ("wglSwapIntervalEXT"))) {
            Con_Printf ("Vsync control extensions found\n");
            Cvar_Register (&vid_vsync);
        }
    }
}

qboolean OnChange_vid_vsync (cvar_t * var, char *string) {
    update_vsync = true;
    return false;
}

// direct draw software compatability stuff
void VID_LockBuffer (void) {
}
void VID_UnlockBuffer (void) {
}
void D_BeginDirectRect (int x, int y, byte * pbitmap, int width, int height) {
}
void D_EndDirectRect (int x, int y, int width, int height) {
}

void CenterWindow (HWND hWndCenter, int width, int height, BOOL lefttopjustify) {
    int           CenterX, CenterY;

    CenterX = (GetSystemMetrics (SM_CXSCREEN) - width) / 2;
    CenterY = (GetSystemMetrics (SM_CYSCREEN) - height) / 2;
    if (CenterX > CenterY * 2)
        CenterX >>= 1;                 // dual screens
    CenterX = (CenterX < 0) ? 0 : CenterX;
    CenterY = (CenterY < 0) ? 0 : CenterY;
    SetWindowPos (hWndCenter, NULL, CenterX, CenterY, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW | SWP_DRAWFRAME);
}

qboolean VID_SetWindowedMode (int modenum) {
    HDC           hdc;
    int           lastmodestate, width, height;
    RECT          rect;

    lastmodestate = modestate;

    WindowRect.top = WindowRect.left = 0;

    WindowRect.right = modelist[modenum].width;
    WindowRect.bottom = modelist[modenum].height;

    DIBWidth = modelist[modenum].width;
    DIBHeight = modelist[modenum].height;

    WindowStyle = WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
    ExWindowStyle = 0;

    rect = WindowRect;
    AdjustWindowRectEx (&rect, WindowStyle, FALSE, 0);

    width = rect.right - rect.left;
    height = rect.bottom - rect.top;

    // Create the DIB window
    dibwindow =
        CreateWindowEx (ExWindowStyle, ENGINE_NAME, ENGINE_NAME, WindowStyle, rect.left, rect.top, width, height, NULL,
                        NULL, global_hInstance, NULL);

    if (!dibwindow)
        Sys_Error ("Couldn't create DIB window");

    // Center and show the DIB window
    CenterWindow (dibwindow, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top, false);

    ShowWindow (dibwindow, SW_SHOWDEFAULT);
    UpdateWindow (dibwindow);

    modestate = MS_WINDOWED;

    // because we have set the background brush for the window to NULL
    // (to avoid flickering when re-sizing the window on the desktop),
    // we clear the window to black when created, otherwise it will be
    // empty while Quake starts up.
    hdc = GetDC (dibwindow);
    PatBlt (hdc, 0, 0, WindowRect.right, WindowRect.bottom, BLACKNESS);
    ReleaseDC (dibwindow, hdc);

    if (vid.conheight > modelist[modenum].height)
        vid.conheight = modelist[modenum].height;
    if (vid.conwidth > modelist[modenum].width)
        vid.conwidth = modelist[modenum].width;
    vid.width = vid.conwidth;
    vid.height = vid.conheight;

    vid.numpages = 2;

    mainwindow = dibwindow;

    SendMessage (mainwindow, WM_SETICON, (WPARAM) TRUE, (LPARAM) hIcon);
    SendMessage (mainwindow, WM_SETICON, (WPARAM) FALSE, (LPARAM) hIcon);

    return true;
}

qboolean VID_SetFullDIBMode (int modenum) {
    int           lastmodestate, width, height;
    HDC           hdc;
    RECT          rect;

    if (!leavecurrentmode) {
        gdevmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
        gdevmode.dmBitsPerPel = modelist[modenum].bpp;
        gdevmode.dmPelsWidth = modelist[modenum].width << modelist[modenum].halfscreen;
        gdevmode.dmPelsHeight = modelist[modenum].height;
        gdevmode.dmSize = sizeof (gdevmode);

        if (vid_displayfrequency.value > 0) {
            gdevmode.dmDisplayFrequency = vid_displayfrequency.value;
            gdevmode.dmFields |= DM_DISPLAYFREQUENCY;
            Con_Printf ("Forcing display frequency to %i Hz\n", gdevmode.dmDisplayFrequency);
        }

        if (ChangeDisplaySettings (&gdevmode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
            Sys_Error ("Couldn't set fullscreen DIB mode");
    }

    lastmodestate = modestate;
    modestate = MS_FULLDIB;

    WindowRect.top = WindowRect.left = 0;

    WindowRect.right = modelist[modenum].width;
    WindowRect.bottom = modelist[modenum].height;

    DIBWidth = modelist[modenum].width;
    DIBHeight = modelist[modenum].height;

    WindowStyle = WS_POPUP;
    ExWindowStyle = 0;

    rect = WindowRect;
    AdjustWindowRectEx (&rect, WindowStyle, FALSE, 0);

    width = rect.right - rect.left;
    height = rect.bottom - rect.top;

    // Create the DIB window
    dibwindow =
        CreateWindowEx (ExWindowStyle, ENGINE_NAME, ENGINE_NAME, WindowStyle, rect.left, rect.top, width, height, NULL,
                        NULL, global_hInstance, NULL);

    if (!dibwindow)
        Sys_Error ("Couldn't create DIB window");

    ShowWindow (dibwindow, SW_SHOWDEFAULT);
    UpdateWindow (dibwindow);

    // Because we have set the background brush for the window to NULL
    // (to avoid flickering when re-sizing the window on the desktop),
    // we clear the window to black when created, otherwise it will be
    // empty while Quake starts up.
    hdc = GetDC (dibwindow);
    PatBlt (hdc, 0, 0, WindowRect.right, WindowRect.bottom, BLACKNESS);
    ReleaseDC (dibwindow, hdc);

    if (vid.conheight > modelist[modenum].height)
        vid.conheight = modelist[modenum].height;
    if (vid.conwidth > modelist[modenum].width)
        vid.conwidth = modelist[modenum].width;
    vid.width = vid.conwidth;
    vid.height = vid.conheight;

    vid.numpages = 2;

    // needed because we're not getting WM_MOVE messages fullscreen on NT
    window_x = 0;
    window_y = 0;

    mainwindow = dibwindow;

    SendMessage (mainwindow, WM_SETICON, (WPARAM) TRUE, (LPARAM) hIcon);
    SendMessage (mainwindow, WM_SETICON, (WPARAM) FALSE, (LPARAM) hIcon);

    return true;
}

int VID_SetMode (int modenum, unsigned char *palette) {
    int           original_mode, temp;
    qboolean      stat;
    MSG           msg;

    if ((windowed && modenum) || (!windowed && (modenum < 1))
        || (!windowed && (modenum >= nummodes)))
        Sys_Error ("Bad video mode");

    // so Con_Printfs don't mess us up by forcing vid and snd updates
    temp = scr_disabled_for_loading;
    scr_disabled_for_loading = true;

    CDAudio_Pause ();

    if (vid_modenum == NO_MODE)
        original_mode = windowed_default;
    else
        original_mode = vid_modenum;

    // Set either the fullscreen or windowed mode
    if (modelist[modenum].type == MS_WINDOWED) {
        if (_windowed_mouse.value && key_dest == key_game) {
            stat = VID_SetWindowedMode (modenum);
            IN_ActivateMouse ();
            IN_HideMouse ();
        } else {
            IN_DeactivateMouse ();
            IN_ShowMouse ();
            stat = VID_SetWindowedMode (modenum);
        }
    } else if (modelist[modenum].type == MS_FULLDIB) {
        stat = VID_SetFullDIBMode (modenum);
        IN_ActivateMouse ();
        IN_HideMouse ();
    } else {
        Sys_Error ("VID_SetMode: Bad mode type in modelist");
    }

    window_width = DIBWidth;
    window_height = DIBHeight;
    VID_UpdateWindowStatus ();

    CDAudio_Resume ();
    scr_disabled_for_loading = temp;

    if (!stat)
        Sys_Error ("Couldn't set video mode");

    // now we try to make sure we get the focus on the mode switch, because
    // sometimes in some systems we don't. We grab the foreground, then
    // finish setting up, pump all our messages, and sleep for a little while
    // to let messages finish bouncing around the system, then we put
    // ourselves at the top of the z order, then grab the foreground again,
    // Who knows if it helps, but it probably doesn't hurt
    SetForegroundWindow (mainwindow);
    // VID_SetPalette (palette);
    vid_modenum = modenum;
    Cvar_SetValue (&vid_mode, (float) vid_modenum);

    while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }

    Sleep (100);

    SetWindowPos (mainwindow, HWND_TOP, 0, 0, 0, 0,
                  SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOCOPYBITS);
    SetForegroundWindow (mainwindow);

    // fix the leftover Alt from any Alt-Tab or the like that switched us away
    ClearAllStates ();

    // joe if (!msg_suppress_1)
    Con_Printf ("Video mode %s initialized\n", VID_GetModeDescription (vid_modenum));

    // VID_SetPalette (palette);

    vid.recalc_refdef = 1;

    return true;
}

/*
 * VID_UpdateWindowStatus
 */
void VID_UpdateWindowStatus (void) {
    window_rect.left = window_x;
    window_rect.top = window_y;
    window_rect.right = window_x + window_width;
    window_rect.bottom = window_y + window_height;
    window_center_x = (window_rect.left + window_rect.right) / 2;
    window_center_y = (window_rect.top + window_rect.bottom) / 2;

    IN_UpdateClipCursor ();
}

// 

/*
 * GL_BeginRendering
 */
void GL_BeginRendering (int *x, int *y, int *width, int *height) {
    *x = *y = 0;
    *width = WindowRect.right - WindowRect.left;
    *height = WindowRect.bottom - WindowRect.top;
}

/*
 * GL_EndRendering
 */
void GL_EndRendering (void) {
    static qboolean old_hwgamma_enabled;

    vid_hwgamma_enabled = vid_hwgammacontrol.value && vid_gammaworks && ActiveApp && !Minimized;
    vid_hwgamma_enabled = vid_hwgamma_enabled && (modestate == MS_FULLDIB || vid_hwgammacontrol.value == 2);
    if (vid_hwgamma_enabled != old_hwgamma_enabled) {
        old_hwgamma_enabled = vid_hwgamma_enabled;
        if (vid_hwgamma_enabled && currentgammaramp)
            VID_SetDeviceGammaRamp (currentgammaramp);
        else
            RestoreHWGamma ();
    }

    if (!scr_skipupdate || block_drawing) {
        // lxndr: useless update_vsync ?
        //        if (wglSwapIntervalEXT && update_vsync && vid_vsync.string[0])
        if (wglSwapIntervalEXT && vid_vsync.string[0])
            wglSwapIntervalEXT (vid_vsync.value ? 1 : 0);
        //        update_vsync = false;

        SwapBuffers (maindc);
    }
    // handle the mouse state when windowed if that's changed
    if (modestate == MS_WINDOWED) {
        if (!_windowed_mouse.value) {
            if (windowed_mouse) {
                IN_DeactivateMouse ();
                IN_ShowMouse ();
                windowed_mouse = false;
            }
        } else {
            windowed_mouse = true;
            if (key_dest == key_game && !mouseactive && ActiveApp) {
                IN_ActivateMouse ();
                IN_HideMouse ();
            } else if (mouseactive && key_dest != key_game) {
                IN_DeactivateMouse ();
                IN_ShowMouse ();
            }
        }
    }

    if (fullsbardraw)
        Sbar_Changed ();
}

void VID_SetDefaultMode (void) {
    IN_DeactivateMouse ();
}

void VID_ShiftPalette (unsigned char *palette) {
}

/*
 * VID_SetDeviceGammaRamp
 *
 * Note: ramps must point to a static array
 */
void VID_SetDeviceGammaRamp (unsigned short *ramps) {
    if (vid_gammaworks) {
        currentgammaramp = ramps;
        if (vid_hwgamma_enabled) {
            SetDeviceGammaRamp (maindc, ramps);
            customgamma = true;
        }
    }
}

void InitHWGamma (void) {
    if (COM_CheckParm ("-nohwgamma"))
        return;

    vid_gammaworks = GetDeviceGammaRamp (maindc, systemgammaramp);
}

void RestoreHWGamma (void) {
    if (vid_gammaworks && customgamma) {
        customgamma = false;
        SetDeviceGammaRamp (maindc, systemgammaramp);
    }
}

// 

void VID_Shutdown (void) {
    HGLRC         hRC;
    HDC           hDC;

    if (vid_initialized) {
        RestoreHWGamma ();

        vid_canalttab = false;
        hRC = wglGetCurrentContext ();
        hDC = wglGetCurrentDC ();

        wglMakeCurrent (NULL, NULL);

        if (hRC)
            wglDeleteContext (hRC);

        if (hDC && dibwindow)
            ReleaseDC (dibwindow, hDC);

        if (modestate == MS_FULLDIB)
            ChangeDisplaySettings (NULL, 0);

        if (maindc && dibwindow)
            ReleaseDC (dibwindow, maindc);

        AppActivate (false, false);
    }
}

int bChosePixelFormat (HDC hDC, PIXELFORMATDESCRIPTOR * pfd, PIXELFORMATDESCRIPTOR * retpfd) {
    int           pixelformat;

    if (!(pixelformat = ChoosePixelFormat (hDC, pfd))) {
        MessageBox (NULL, "ChoosePixelFormat failed", "Error", MB_OK);
        return 0;
    }

    if (!(DescribePixelFormat (hDC, pixelformat, sizeof (PIXELFORMATDESCRIPTOR), retpfd))) {
        MessageBox (NULL, "DescribePixelFormat failed", "Error", MB_OK);
        return 0;
    }

    return pixelformat;
}

BOOL bSetupPixelFormat (HDC hDC) {
    int           pixelformat;
    PIXELFORMATDESCRIPTOR retpfd, pfd = {
        sizeof (PIXELFORMATDESCRIPTOR), // size of this pfd
        1,                             // version number
        PFD_DRAW_TO_WINDOW             // support window
            | PFD_SUPPORT_OPENGL       // support OpenGL
            | PFD_DOUBLEBUFFER,        // double buffered
        PFD_TYPE_RGBA,                 // RGBA type
        24,                            // 24-bit color depth
        0, 0, 0, 0, 0, 0,              // color bits ignored
        0,                             // no alpha buffer
        0,                             // shift bit ignored
        0,                             // no accumulation buffer
        0, 0, 0, 0,                    // accum bits ignored
        24,                            // 24-bit z-buffer
        8,                             // 8-bit stencil buffer
        0,                             // no auxiliary buffer
        PFD_MAIN_PLANE,                // main layer
        0,                             // reserved
        0, 0, 0                        // layer masks ignored
    };

    if (!(pixelformat = bChosePixelFormat (hDC, &pfd, &retpfd)))
        return FALSE;

    if (retpfd.cDepthBits < 24) {
        pfd.cDepthBits = 24;
        if (!(pixelformat = bChosePixelFormat (hDC, &pfd, &retpfd)))
            return FALSE;
    }

    if (!SetPixelFormat (hDC, pixelformat, &retpfd)) {
        MessageBox (NULL, "SetPixelFormat failed", "Error", MB_OK);
        return FALSE;
    }

    if (retpfd.cDepthBits < 24)
        gl_allow_ztrick = false;

    gl_have_stencil = true;
    return TRUE;
}

/*
 *
 *
 * MAIN WINDOW
 *
 *
 */

/*
 * ClearAllStates
 */
void ClearAllStates (void) {
    int           i;

    // send an up event for each key, to make sure the server clears them all
    for (i = 0; i < 256; i++) {
        if (keydown[i])
            Key_Event (i, false);
    }

    Key_ClearStates ();
    IN_ClearStates ();
}

void AppActivate (BOOL fActive, BOOL minimize)
/****************************************************************************
*
* Function:     AppActivate
* Parameters:   fActive - True if app is activating
*
* Description:  If the application is activating, then swap the system
*               into SYSPAL_NOSTATIC mode so that our palettes will display
*               correctly.
*
****************************************************************************/
{
    static BOOL   sound_active;

    ActiveApp = fActive;
    Minimized = minimize;

    // enable/disable sound on focus gain/loss
    if (!ActiveApp && sound_active) {
        S_BlockSound ();
        sound_active = false;
    } else if (ActiveApp && !sound_active) {
        S_UnblockSound ();
        sound_active = true;
    }

    if (fActive) {
        if (modestate == MS_FULLDIB) {
            IN_ActivateMouse ();
            IN_HideMouse ();

            if (vid_canalttab && !Minimized && currentgammaramp)
                VID_SetDeviceGammaRamp (currentgammaramp);

            if (vid_canalttab && vid_wassuspended) {
                vid_wassuspended = false;
                if (ChangeDisplaySettings (&gdevmode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
                    Sys_Error ("Couldn't set fullscreen DIB mode");
                ShowWindow (mainwindow, SW_SHOWNORMAL);

                // Fix for alt-tab bug in NVidia drivers
                MoveWindow (mainwindow, 0, 0, gdevmode.dmPelsWidth, gdevmode.dmPelsHeight, false);

                // scr_fullupdate = 0;
                Sbar_Changed ();
            }
        } else if (modestate == MS_WINDOWED && Minimized)
            ShowWindow (mainwindow, SW_RESTORE);
        else if ((modestate == MS_WINDOWED) && _windowed_mouse.value && key_dest == key_game) {
            IN_ActivateMouse ();
            IN_HideMouse ();
        }
    }

    if (!fActive) {
        if (modestate == MS_FULLDIB) {
            IN_DeactivateMouse ();
            IN_ShowMouse ();
            RestoreHWGamma ();
            if (vid_canalttab) {
                ChangeDisplaySettings (NULL, 0);
                vid_wassuspended = true;
            }
        } else if ((modestate == MS_WINDOWED) && _windowed_mouse.value) {
            IN_DeactivateMouse ();
            IN_ShowMouse ();
        }
    }
}

LONG          CDAudio_MessageHandler (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int           IN_MapKey (int key);
void          MW_Hook_Message (long buttons);

#define WM_MWHOOK (WM_USER + 1)

/*
 * Main Window procedure
 */
LONG WINAPI MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    int           fActive, fMinimized, temp;
    LONG          lRet = 1;
    extern unsigned int uiWheelMessage;
    extern cvar_t cl_confirmquit;

    if (uMsg == uiWheelMessage) {
        uMsg = WM_MOUSEWHEEL;
        wParam <<= 16;
    }

    switch (uMsg) {
        case WM_KILLFOCUS:
            if (modestate == MS_FULLDIB)
                ShowWindow (mainwindow, SW_SHOWMINNOACTIVE);
            break;

        case WM_CREATE:
            break;

        case WM_MOVE:
            window_x = (int) LOWORD (lParam);
            window_y = (int) HIWORD (lParam);
            VID_UpdateWindowStatus ();
            break;

        case WM_KEYDOWN:
        case WM_SYSKEYDOWN:
            Key_Event (IN_MapKey (lParam), true);
            break;

        case WM_KEYUP:
        case WM_SYSKEYUP:
            Key_Event (IN_MapKey (lParam), false);
            break;

        case WM_SYSCHAR:
            // keep Alt-Space from happening
            break;

            // this is complicated because Win32 seems to pack multiple mouse
            // events into
            // one update sometimes, so we always check all states and look for
            // events
        case WM_LBUTTONDOWN:
        case WM_LBUTTONUP:
        case WM_RBUTTONDOWN:
        case WM_RBUTTONUP:
        case WM_MBUTTONDOWN:
        case WM_MBUTTONUP:
        case WM_MOUSEMOVE:
            temp = 0;

            if (wParam & MK_LBUTTON)
                temp |= 1;

            if (wParam & MK_RBUTTON)
                temp |= 2;

            if (wParam & MK_MBUTTON)
                temp |= 4;

            IN_MouseEvent (temp);

            break;

            // JACK: This is the mouse wheel with the Intellimouse
            // Its delta is either positive or neg, and we generate the proper
            // Event.
        case WM_MOUSEWHEEL:
            if ((short) HIWORD (wParam) > 0) {
                Key_Event (K_MWHEELUP, true);
                Key_Event (K_MWHEELUP, false);
            } else {
                Key_Event (K_MWHEELDOWN, true);
                Key_Event (K_MWHEELDOWN, false);
            }
            break;

        case WM_MWHOOK:
            MW_Hook_Message (lParam);
            break;

        case WM_SIZE:
            break;

        case WM_CLOSE:
            if (!cl_confirmquit.value
                || MessageBox (mainwindow, "Are you sure you want to quit?", "Confirm Exit",
                               MB_YESNO | MB_SETFOREGROUND | MB_ICONQUESTION) == IDYES)
                Sys_Quit ();
            break;

        case WM_ACTIVATE:
            fActive = LOWORD (wParam);
            fMinimized = (BOOL) HIWORD (wParam);
            AppActivate (!(fActive == WA_INACTIVE), fMinimized);

            // fix the leftover Alt from any Alt-Tab or the like that switched
            // us away
            ClearAllStates ();

            break;

        case WM_DESTROY:
            if (dibwindow)
                DestroyWindow (dibwindow);
            PostQuitMessage (0);
            break;

        case MM_MCINOTIFY:
            lRet = CDAudio_MessageHandler (hWnd, uMsg, wParam, lParam);
            break;

        default:
            // pass all unhandled messages to DefWindowProc
            lRet = DefWindowProc (hWnd, uMsg, wParam, lParam);
            break;
    }

    // return 1 if handled message, 0 if not
    return lRet;
}

/*
 * VID_NumModes
 */
int VID_NumModes (void) {
    return nummodes;
}

/*
 * VID_GetModePtr
 */
vmode_t      *VID_GetModePtr (int modenum) {
    if ((modenum >= 0) && (modenum < nummodes))
        return &modelist[modenum];

    return &badmode;
}

/*
 * VID_GetModeDescription
 */
char         *VID_GetModeDescription (int mode) {
    char         *pinfo;
    vmode_t      *pv;
    static char   temp[100];

    if ((mode < 0) || (mode >= nummodes))
        return NULL;

    if (!leavecurrentmode) {
        pv = VID_GetModePtr (mode);
        pinfo = pv->modedesc;
    } else {
        sprintf (temp, "Desktop resolution (%dx%d)", modelist[MODE_FULLSCREEN_DEFAULT].width,
                 modelist[MODE_FULLSCREEN_DEFAULT].height);
        pinfo = temp;
    }

    return pinfo;
}

// KJB: Added this to return the mode driver name in description for console

char         *VID_GetExtModeDescription (int mode) {
    static char   pinfo[40];
    vmode_t      *pv;

    if ((mode < 0) || (mode >= nummodes))
        return NULL;

    pv = VID_GetModePtr (mode);
    if (modelist[mode].type == MS_FULLDIB) {
        if (!leavecurrentmode)
            sprintf (pinfo, "%s fullscreen", pv->modedesc);
        else
            sprintf (pinfo, "Desktop resolution (%dx%d)", modelist[MODE_FULLSCREEN_DEFAULT].width,
                     modelist[MODE_FULLSCREEN_DEFAULT].height);
    } else {
        if (modestate == MS_WINDOWED)
            sprintf (pinfo, "%s windowed", pv->modedesc);
        else
            sprintf (pinfo, "windowed");
    }

    return pinfo;
}

/*
 * VID_DescribeCurrentMode_f
 */
void VID_DescribeCurrentMode_f (void) {
    Con_Printf ("%s\n", VID_GetExtModeDescription (vid_modenum));
}

/*
 * VID_NumModes_f
 */
void VID_NumModes_f (void) {
    Con_Printf ("Number of available video modes: %d\n", nummodes);
}

/*
 * VID_DescribeMode_f
 */
void VID_DescribeMode_f (void) {
    int           t, modenum;

    modenum = Q_atoi (Cmd_Argv (1));

    t = leavecurrentmode;
    leavecurrentmode = 0;

    Con_Printf ("%s\n", VID_GetExtModeDescription (modenum));

    leavecurrentmode = t;
}

/*
 * VID_DescribeModes_f
 */
void VID_DescribeModes_f (void) {
    int           i, lnummodes, t;
    char         *pinfo;

    lnummodes = VID_NumModes ();

    t = leavecurrentmode;
    leavecurrentmode = 0;

    for (i = 1; i < lnummodes; i++) {
        pinfo = VID_GetExtModeDescription (i);
        Con_Printf ("%2d: %s\n", i, pinfo);
    }

    leavecurrentmode = t;
}

void VID_InitDIB (HINSTANCE hInstance) {
    int           i;
    WNDCLASS      wc;

    /*
     * Register the frame class
     */
    wc.style = 0;
    wc.lpfnWndProc = (WNDPROC) MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = 0;
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName = 0;
    wc.lpszClassName = ENGINE_NAME;

    if (!RegisterClass (&wc))
        Sys_Error ("Couldn't register window class");

    modelist[0].type = MS_WINDOWED;

    if ((i = COM_CheckParm ("-width")) && i + 1 < com_argc)
        modelist[0].width = Q_atoi (com_argv[i + 1]);
    else
        modelist[0].width = 640;

    if (modelist[0].width < 320)
        modelist[0].width = 320;

    if ((i = COM_CheckParm ("-height")) && i + 1 < com_argc)
        modelist[0].height = Q_atoi (com_argv[i + 1]);
    else
        modelist[0].height = modelist[0].width * 240 / 320;

    if (modelist[0].height < 240)
        modelist[0].height = 240;

    sprintf (modelist[0].modedesc, "%dx%d", modelist[0].width, modelist[0].height);

    modelist[0].modenum = MODE_WINDOWED;
    modelist[0].dib = 1;
    modelist[0].fullscreen = 0;
    modelist[0].halfscreen = 0;
    modelist[0].bpp = 0;

    nummodes = 1;
}

/*
 * VID_InitFullDIB
 */
void VID_InitFullDIB (HINSTANCE hInstance) {
    DEVMODE       devmode;
    int           i, j, bpp, done, modenum, originalnummodes, existingmode, numlowresmodes;
    BOOL          stat;

    // enumerate >8 bpp modes
    originalnummodes = nummodes;
    modenum = 0;

    do {
        stat = EnumDisplaySettings (NULL, modenum, &devmode);

        if ((devmode.dmBitsPerPel >= 15) && (devmode.dmPelsWidth <= MAXWIDTH)
            && (devmode.dmPelsHeight <= MAXHEIGHT) && (nummodes < MAX_MODE_LIST)) {
            devmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

            if (ChangeDisplaySettings (&devmode, CDS_TEST | CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL) {
                modelist[nummodes].type = MS_FULLDIB;
                modelist[nummodes].width = devmode.dmPelsWidth;
                modelist[nummodes].height = devmode.dmPelsHeight;
                modelist[nummodes].modenum = 0;
                modelist[nummodes].halfscreen = 0;
                modelist[nummodes].dib = 1;
                modelist[nummodes].fullscreen = 1;
                modelist[nummodes].bpp = devmode.dmBitsPerPel;
                sprintf (modelist[nummodes].modedesc, "%dx%dx%d", devmode.dmPelsWidth, devmode.dmPelsHeight,
                         devmode.dmBitsPerPel);

                // if the width is more than twice the height, reduce it by
                // half because this
                // is probably a dual-screen monitor
                if (!COM_CheckParm ("-noadjustaspect")) {
                    if (modelist[nummodes].width > (modelist[nummodes].height << 1)) {
                        modelist[nummodes].width >>= 1;
                        modelist[nummodes].halfscreen = 1;
                        sprintf (modelist[nummodes].modedesc, "%dx%dx%d", modelist[nummodes].width,
                                 modelist[nummodes].height, modelist[nummodes].bpp);
                    }
                }

                for (i = originalnummodes, existingmode = 0; i < nummodes; i++) {
                    if ((modelist[nummodes].width == modelist[i].width)
                        && (modelist[nummodes].height == modelist[i].height)
                        && (modelist[nummodes].bpp == modelist[i].bpp)) {
                        existingmode = 1;
                        break;
                    }
                }

                if (!existingmode)
                    nummodes++;
            }
        }

        modenum++;
    }
    while (stat);

    // see if there are any low-res modes that aren't being reported
    numlowresmodes = sizeof (lowresmodes) / sizeof (lowresmodes[0]);
    bpp = 16;
    done = 0;

    do {
        for (j = 0; j < numlowresmodes && nummodes < MAX_MODE_LIST; j++) {
            devmode.dmBitsPerPel = bpp;
            devmode.dmPelsWidth = lowresmodes[j].width;
            devmode.dmPelsHeight = lowresmodes[j].height;
            devmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

            if (ChangeDisplaySettings (&devmode, CDS_TEST | CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL) {
                modelist[nummodes].type = MS_FULLDIB;
                modelist[nummodes].width = devmode.dmPelsWidth;
                modelist[nummodes].height = devmode.dmPelsHeight;
                modelist[nummodes].modenum = 0;
                modelist[nummodes].halfscreen = 0;
                modelist[nummodes].dib = 1;
                modelist[nummodes].fullscreen = 1;
                modelist[nummodes].bpp = devmode.dmBitsPerPel;
                sprintf (modelist[nummodes].modedesc, "%dx%dx%d", devmode.dmPelsWidth, devmode.dmPelsHeight,
                         devmode.dmBitsPerPel);

                for (i = originalnummodes, existingmode = 0; i < nummodes; i++) {
                    if ((modelist[nummodes].width == modelist[i].width)
                        && (modelist[nummodes].height == modelist[i].height)
                        && (modelist[nummodes].bpp == modelist[i].bpp)) {
                        existingmode = 1;
                        break;
                    }
                }

                if (!existingmode)
                    nummodes++;
            }
        }

        switch (bpp) {
            case 16:
                bpp = 32;
                break;

            case 32:
                bpp = 24;
                break;

            case 24:
                done = 1;
                break;
        }
    }
    while (!done);

    if (nummodes == originalnummodes)
        Con_Printf ("No fullscreen DIB modes found\n");
}

/*
 * VID_Init
 */
void VID_Init (unsigned char *palette) {
    int           i, basenummodes, width, height, bpp, findbpp, done;
    HDC           hdc;
    DEVMODE       devmode;

    memset (&devmode, 0, sizeof (devmode));

    Cvar_Register (&vid_mode);
    Cvar_Register (&_vid_default_mode);
    Cvar_Register (&_vid_default_mode_win);
    Cvar_Register (&vid_config_x);
    Cvar_Register (&vid_config_y);
    Cvar_Register (&_windowed_mouse);
    Cvar_Register (&vid_displayfrequency);
    Cvar_Register (&vid_hwgammacontrol);

    Cmd_AddCommand ("vid_nummodes", VID_NumModes_f);
    Cmd_AddCommand ("vid_describecurrentmode", VID_DescribeCurrentMode_f);
    Cmd_AddCommand ("vid_describemode", VID_DescribeMode_f);
    Cmd_AddCommand ("vid_describemodes", VID_DescribeModes_f);

    hIcon = LoadIcon (global_hInstance, MAKEINTRESOURCE (IDI_ICON2));

    VID_InitDIB (global_hInstance);
    basenummodes = nummodes;

    VID_InitFullDIB (global_hInstance);

    if (COM_CheckParm ("-window")) {
        hdc = GetDC (NULL);

        if (GetDeviceCaps (hdc, RASTERCAPS) & RC_PALETTE)
            Sys_Error ("Can't run in non-RGB mode");

        ReleaseDC (NULL, hdc);

        windowed = true;

        vid_default = MODE_WINDOWED;
    } else {
        if (nummodes == 1)
            Sys_Error ("No RGB fullscreen modes available");

        windowed = false;

        if ((i = COM_CheckParm ("-mode")) && i + 1 < com_argc) {
            vid_default = Q_atoi (com_argv[i + 1]);
        } else if (COM_CheckParm ("-current")) {
            modelist[MODE_FULLSCREEN_DEFAULT].width = GetSystemMetrics (SM_CXSCREEN);
            modelist[MODE_FULLSCREEN_DEFAULT].height = GetSystemMetrics (SM_CYSCREEN);
            vid_default = MODE_FULLSCREEN_DEFAULT;
            leavecurrentmode = 1;
        } else {
            if ((i = COM_CheckParm ("-width")) && i + 1 < com_argc)
                width = Q_atoi (com_argv[i + 1]);
            else
                width = 640;

            if ((i = COM_CheckParm ("-bpp")) && i + 1 < com_argc) {
                bpp = Q_atoi (com_argv[i + 1]);
                findbpp = 0;
            } else {
                bpp = 15;
                findbpp = 1;
            }

            if ((i = COM_CheckParm ("-height")) && i + 1 < com_argc)
                height = Q_atoi (com_argv[i + 1]);

            // if they want to force it, add the specified mode to the list
            if (COM_CheckParm ("-force") && nummodes < MAX_MODE_LIST) {
                modelist[nummodes].type = MS_FULLDIB;
                modelist[nummodes].width = width;
                modelist[nummodes].height = height;
                modelist[nummodes].modenum = 0;
                modelist[nummodes].halfscreen = 0;
                modelist[nummodes].dib = 1;
                modelist[nummodes].fullscreen = 1;
                modelist[nummodes].bpp = bpp;
                sprintf (modelist[nummodes].modedesc, "%dx%dx%d", devmode.dmPelsWidth, devmode.dmPelsHeight,
                         devmode.dmBitsPerPel);

                for (i = nummodes; i < nummodes; i++) {
                    if ((modelist[nummodes].width == modelist[i].width)
                        && (modelist[nummodes].height == modelist[i].height)
                        && (modelist[nummodes].bpp == modelist[i].bpp))
                        break;
                }

                if (i == nummodes)
                    nummodes++;
            }

            done = 0;

            do {
                height = 0;
                if ((i = COM_CheckParm ("-height")) && i + 1 < com_argc)
                    height = Q_atoi (com_argv[i + 1]);
                else
                    height = 0;

                for (i = 1, vid_default = 0; i < nummodes; i++) {
                    if (modelist[i].width == width && (!height || modelist[i].height == height)
                        && modelist[i].bpp == bpp) {
                        vid_default = i;
                        done = 1;
                        break;
                    }
                }

                if (findbpp && !done) {
                    switch (bpp) {
                        case 15:
                            bpp = 16;
                            break;

                        case 16:
                            bpp = 32;
                            break;

                        case 32:
                            bpp = 24;
                            break;

                        case 24:
                            done = 1;
                            break;
                    }
                } else {
                    done = 1;
                }
            }
            while (!done);

            if (!vid_default)
                Sys_Error ("Specified video mode not available");
        }
    }

    vid_initialized = true;

    if ((i = COM_CheckParm ("-conwidth")) && i + 1 < com_argc)
        vid.conwidth = Q_atoi (com_argv[i + 1]);
    else
        vid.conwidth = 640;

    vid.conwidth &= 0xfff8;            // make it a multiple of eight

    if (vid.conwidth < 320)
        vid.conwidth = 320;

    // pick a conheight that matches with correct aspect
    vid.conheight = vid.conwidth * 3 / 4;

    if ((i = COM_CheckParm ("-conheight")) && i + 1 < com_argc)
        vid.conheight = Q_atoi (com_argv[i + 1]);
    if (vid.conheight < 200)
        vid.conheight = 200;

    vid.maxwarpwidth = WARP_WIDTH;
    vid.maxwarpheight = WARP_HEIGHT;
    vid.colormap = host_colormap;
    vid.fullbright = 256 - LittleLong (*((int *) vid.colormap + 2048));

    Check_Gamma (palette);
    VID_SetPalette (palette);

    VID_SetMode (vid_default, palette);

    maindc = GetDC (mainwindow);
    if (!bSetupPixelFormat (maindc))
        Sys_Error ("bSetupPixelFormat failed");

    InitHWGamma ();

    if (!(baseRC = wglCreateContext (maindc)))
        Sys_Error
            ("Could not initialize GL (wglCreateContext failed).\n\nMake sure you in are 65535 color mode, and try running -window.");
    if (!wglMakeCurrent (maindc, baseRC))
        Sys_Error ("wglMakeCurrent failed");

    GL_Init ();
    CheckVsyncControlExtensions ();

    vid_menudrawfn = VID_MenuDraw;
    vid_menukeyfn = VID_MenuKey;

    strcpy (badmode.modedesc, "Bad mode");
    vid_canalttab = true;

    if (COM_CheckParm ("-fullsbar"))
        fullsbardraw = true;
}

// 
// Video menu stuff
// 

#define VID_ROW_SIZE		3

extern void   M_Menu_Options_f (void);
extern void   M_Print (int cx, int cy, char *str);
extern void   M_PrintWhite (int cx, int cy, char *str);
extern void   M_DrawCharacter (int cx, int line, int num);
extern void   M_DrawTransPic (int x, int y, mpic_t * pic);
extern void   M_DrawPic (int x, int y, mpic_t * pic);

static int    vid_line, vid_wmodes;

typedef struct {
    int           modenum;
    char         *desc;
    int           iscur;
} modedesc_t;

#define MAX_COLUMN_SIZE		9
#define MODE_AREA_HEIGHT	(MAX_COLUMN_SIZE + 2)
#define MAX_MODEDESCS		(MAX_COLUMN_SIZE * 3)

static modedesc_t modedescs[MAX_MODEDESCS];

/*
 * VID_MenuDraw
 */
void VID_MenuDraw (void) {
    mpic_t       *p;
    char         *ptr;
    int           lnummodes, i, k, column, row;
    vmode_t      *pv;

    p = Draw_CachePic ("gfx/vidmodes.lmp");
    M_DrawPic ((320 - p->width) / 2, 4, p);

    vid_wmodes = 0;
    lnummodes = VID_NumModes ();

    for (i = 1; i < lnummodes && vid_wmodes < MAX_MODEDESCS; i++) {
        ptr = VID_GetModeDescription (i);
        pv = VID_GetModePtr (i);

        k = vid_wmodes;

        modedescs[k].modenum = i;
        modedescs[k].desc = ptr;
        modedescs[k].iscur = 0;

        if (i == vid_modenum)
            modedescs[k].iscur = 1;

        vid_wmodes++;
    }

    if (vid_wmodes > 0) {
        M_Print (2 * 8, 36 + 0 * 8, "Fullscreen Modes (WIDTHxHEIGHTxBPP)");

        column = 8;
        row = 36 + 2 * 8;

        for (i = 0; i < vid_wmodes; i++) {
            if (modedescs[i].iscur)
                M_PrintWhite (column, row, modedescs[i].desc);
            else
                M_Print (column, row, modedescs[i].desc);

            column += 13 * 8;

            if ((i % VID_ROW_SIZE) == (VID_ROW_SIZE - 1)) {
                column = 8;
                row += 8;
            }
        }
    }

    M_Print (3 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8 * 2, "Video modes must be set from the");
    M_Print (3 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8 * 3, "command line with -width <width>");
    M_Print (3 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8 * 4, "and -bpp <bits-per-pixel>");
    M_Print (3 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8 * 6, "Select windowed mode with -window");
}

/*
 * VID_MenuKey
 */
void VID_MenuKey (int key) {
    switch (key) {
        case K_ESCAPE:
            S_LocalSound ("misc/menu1.wav");
            M_Menu_Options_f ();
            break;

        default:
            break;
    }
}
