/*
Copyright (C) 1997-2001 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.

*/

#include "win_local.h"
#ifdef _WIN32_WCE
#include <aygshell.h>
#endif /* _WIN32_WCE */

qboolean		win_initialized;

HWND			hWndMain;

PRIVATE DWORD	lastMsgTime;
static qboolean	s_alttab_disabled;

PRIVATE HHOOK kbdHook;

static cvar_t	*vid_fullscreen;
static cvar_t	*vid_xpos;
static cvar_t	*vid_ypos;
static cvar_t	*sys_winnt;
static cvar_t	*win_hwnd;
static cvar_t	*win_noalttab;
static cvar_t	*win_disablewinkey;

void GLimp_AppActivate( qboolean active );

// ============================================================================

#ifdef _WIN32_WCE
BOOL AdjustWindowRect( LPRECT lpRect, DWORD dwStyle, BOOL bMenu ) {
	return AdjustWindowRectEx( lpRect, dwStyle, bMenu, 0 );
}
#endif


/*
=================
Win_CreateWindow
=================
*/
HWND Win_CreateWindow( int width, int height, qboolean fullscreen ) {
#ifdef _WIN32_WCE
	WNDCLASS		wc;
#else
	WNDCLASSEX		wc;
#endif
	RECT			r;
	HWND			hWnd;
	int				stylebits;
	int				x, y, w, h;
	int				exstyle;
	RECT			screen;

	/* Register the frame class */
	memset( &wc, 0, sizeof( wc ) );
#ifndef _WIN32_WCE
	wc.cbSize = sizeof( wc );
#endif
	wc.lpfnWndProc   = ( WNDPROC )Win_MainWndProc;
	wc.hInstance     = hGlobalInstance;
#ifdef _WIN32_WCE
#else
	wc.hIcon = LoadImage( hGlobalInstance, MAKEINTRESOURCE( IDI_APP ),
		IMAGE_ICON, 32, 32, LR_CREATEDIBSECTION );
	wc.hIconSm = LoadImage( hGlobalInstance, MAKEINTRESOURCE( IDI_APP ),
		IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION );
#endif
	wc.hCursor       = LoadCursor ( NULL, IDC_ARROW );
	wc.hbrBackground = ( void * )COLOR_GRAYTEXT;
	wc.lpszClassName = WINDOW_CLASS_NAME;

#ifdef _WIN32_WCE
	if( !RegisterClass( &wc ) ) {
#else
	if( !RegisterClassEx( &wc ) ) {
#endif
		Com_Error( ERR_FATAL, "Couldn't register window class" );
	}

	if( fullscreen ) {
		exstyle = WS_EX_TOPMOST;
		stylebits = WS_POPUP|WS_VISIBLE;
	} else {
		exstyle = 0;
		stylebits = WINDOW_STYLE;
	}

	r.left = 0;
	r.top = 0;
	r.right = width;
	r.bottom = height;

	AdjustWindowRect( &r, stylebits, FALSE );

	w = r.right - r.left;
	h = r.bottom - r.top;

	if( fullscreen ) {
		/* postion on primary monitor */
		x = 0;
		y = 0;
	} else {
		/* get virtual screen dimensions */
		if( GetSystemMetrics( SM_CMONITORS ) > 1 ) {
			screen.left = GetSystemMetrics( SM_XVIRTUALSCREEN );
			screen.top = GetSystemMetrics( SM_YVIRTUALSCREEN );
			screen.right = screen.left + GetSystemMetrics( SM_CXVIRTUALSCREEN );
			screen.bottom = screen.top + GetSystemMetrics( SM_CYVIRTUALSCREEN );
		} else {
			screen.left = 0;
			screen.top = 0;
			screen.right = GetSystemMetrics( SM_CXSCREEN );
			screen.bottom = GetSystemMetrics( SM_CYSCREEN );
		}

		x = Cvar_VariableInteger( "vid_xpos" );
		y = Cvar_VariableInteger( "vid_ypos" );

		/* clip to virtual screen */
		if( x + w > screen.right ) {
			x = screen.right - w;
		}
		if( y + h > screen.bottom ) {
			y = screen.bottom - h;
		}
		if( x < screen.left ) {
			x = screen.left;
		}
		if( y < screen.top ) {
			y = screen.top;
		}

	}

	hWnd = CreateWindowEx(
		 exstyle, 
		 WINDOW_CLASS_NAME,
#ifdef _WIN32_WCE
		 __TEXT( "q2pro" ),
#else
		 Cvar_VariableString( "vid_window" ),
#endif
		 stylebits,
		 x, y, w, h,
		 NULL,
		 NULL,
		 hGlobalInstance,
		 NULL );

	if( !hWnd ) {
		Com_Error( ERR_FATAL, "Couldn't create window" );
	}
	
	ShowWindow( hWnd, SW_SHOW );
	SetForegroundWindow( hWnd );
	SetFocus( hWnd );

	return hWnd;
}

/*
=================
Win_DestroyWindow
=================
*/
void Win_DestroyWindow( HWND hWnd ) {
	ShowWindow( hWnd, SW_SHOWNORMAL );	// prevents leaving empty slots in the taskbar
	if( !DestroyWindow( hWnd ) ) {
		Com_Error( ERR_FATAL, "Couldn't destroy window" );
	}
	if( !UnregisterClass( WINDOW_CLASS_NAME, hGlobalInstance ) ) {
		Com_Error( ERR_FATAL, "Couldn't unregister window class" );
	}
}


/*
=================
Win_DisableAltTab
=================
*/
static void Win_DisableAltTab( void ) {
#ifndef _WIN32_WCE
	if( s_alttab_disabled )
		return;

	if( !sys_winnt->integer ) {
		BOOL old;

		SystemParametersInfo( SPI_SETSCREENSAVERRUNNING, 1, &old, 0 );
	} else {
		RegisterHotKey( 0, 0, MOD_ALT, VK_TAB );
		RegisterHotKey( 0, 1, MOD_ALT, VK_RETURN );
	}
	s_alttab_disabled = qtrue;
#endif /* !_WIN32_WCE */
}

/*
=================
Win_EnableAltTab
=================
*/
static void Win_EnableAltTab( void ) {
#ifndef _WIN32_WCE
	if( s_alttab_disabled ) {
		if( !sys_winnt->integer ) {
			BOOL old;

			SystemParametersInfo( SPI_SETSCREENSAVERRUNNING, 0, &old, 0 );
		} else {
			UnregisterHotKey( 0, 0 );
			UnregisterHotKey( 0, 1 );
		}

		s_alttab_disabled = qfalse;
	}
#endif /* !_WIN32_WCE */
}
/*
============
Win_NotAltTab_OnChange
============
*/
static void Win_NotAltTab_OnChange( cvar_t *self, void *arg ) {
	if( self->integer ) {
		Win_DisableAltTab();
	} else {
		Win_EnableAltTab();
	}
}

/*
============
Win_VidPos_OnChange
============
*/
static void Win_VidPos_OnChange( cvar_t *self, void *arg ) {
	if( vid_fullscreen->integer ) {
		return;
	}

	SetWindowPos( hWndMain, HWND_TOPMOST,
		vid_xpos->integer, vid_ypos->integer, 0, 0, SWP_NOSIZE );
}

/*
=================
Win_AppActivate

=================
*/
static void Win_AppActivate( WPARAM wParam ) {
	qboolean active, minimized;

	// KJB: Watch this for problems in fullscreen modes with Alt-tabbing.
	minimized = active = qfalse;
	if( HIWORD( wParam ) ) {
		// we don't want to act like we're active if we're minimized
		minimized = qtrue;
	} else if( LOWORD( wParam ) ) {
		active = qtrue;
	}

	CL_AppActivate( active );

	if( win_noalttab->integer ) {
		if( !active ) {
			Win_EnableAltTab();
		} else {
			Win_DisableAltTab();
		}
	}

	if( vid_fullscreen->integer ) {
		if( active ) {
			ShowWindow( hWndMain, SW_RESTORE );
		} else {
			ShowWindow( hWndMain, SW_MINIMIZE );
		}
	}

#ifndef _WIN32_WCE
	GLimp_AppActivate( active );
#endif

	if( active ) {
		SetForegroundWindow( hWndMain );
	}
}

static LRESULT CALLBACK LowLevelKeyboardProc( int nCode, WPARAM wParam, LPARAM lParam ) {
	PKBDLLHOOKSTRUCT kb = ( PKBDLLHOOKSTRUCT )lParam;
	uint32 key;

	if( nCode != HC_ACTION ) {
		goto ignore;
	}

	switch( kb->vkCode ) {
	case VK_LWIN:
		key = K_LWINKEY;
		break;
	case VK_RWIN:
		key = K_RWINKEY;
		break;
	default:
		goto ignore;
	}

	switch( wParam ) {
	case WM_KEYDOWN:
		Key_Event( key, qtrue, kb->time );
		return TRUE;
	case WM_KEYUP:
		Key_Event( key, qfalse, kb->time );
		return TRUE;
	default:
		break;
	}

ignore:
	return CallNextHookEx( NULL, nCode, wParam, lParam );
}

static void DisableWinKey_OnChange( cvar_t *self, void *arg ) {
	if( self->integer ) {
		if( !sys_winnt->integer ) {
			Com_Printf( "Low-level keyboard hook requires Windows NT\n" );
			Cvar_Set( "win_disablewinkey", "0" );
			return;
		}
		kbdHook = SetWindowsHookEx( WH_KEYBOARD_LL, LowLevelKeyboardProc, hGlobalInstance, 0 );
		if( !kbdHook ) {
			Com_EPrintf( "Couldn't set low-level keyboard hook, error %#lX\n", GetLastError() );
			Cvar_Set( "win_disablewinkey", "0" );
		}
	} else {
		if( kbdHook ) {
			UnhookWindowsHookEx( kbdHook );
			kbdHook = NULL;
		}
	}
}

#ifdef _WIN32_WCE

static byte        scantokey[256] = {
	0,      '\x001', '\x002', '\x003', '\x004', '\x005', '\x006', '\x007', K_BACKSPACE, '\x009',
	'\x00a', '\x00b', '\x00c', K_ENTER, '\x00e', '\x00f', K_SHIFT, '\x011', '\x012', '\x013', 
	'\x014', '\x015', '\x016', '\x017', '\x018', '\x019', '\x01a', '\x01b', '\x01c', '\x01d', 
	'\x01e', '\x01f', '\x020', '\x021', '\x022', '\x023', '\x024', K_LEFTARROW, K_UPARROW, K_RIGHTARROW, 
	K_DOWNARROW, '\x029', '\x02a', '\x02b', '\x02c', '\x02d', '\x02e', '\x02f', ')', '!',
	'@', '#', '$', '%', '^', '&', '*', '(', '\x03a', '\x03b', 
	'\x03c', '\x03d', '\x03e', '\x03f', '\x040', '\x041', '\x042', '\x043', '\x044', '\x045', 
	'\x046', '\x047', '\x048', '\x049', '\x04a', '\x04b', '\x04c', '\x04d', '\x04e', '\x04f', 
	'\x050', '\x051', '\x052', '\x053', '\x054', '\x055', '\x056', '\x057', '\x058', '\x059', 	
	'\x05a', 0/*'\x05b'*/, '\x05c', '\x05d', '\x05e', '\x05f', '\x060', '\x061', '\x062', '\x063', 	
	'\x064', '\x065', '\x066', '\x067', '\x068', '\x069', '\x06a', '\x06b', '\x06c', '\x06d', 	
	'\x06e', '\x06f', '\x070', '\x071', '\x072', '\x073', '\x074', '\x075', '\x076', '\x077', 	
	'\x078', '\x079', '\x07a', '\x07b', '\x07c', '\x07d', '\x07e', '\x07f', '\x080', '\x081', 
	'\x082', '\x083', '\x084', '\x085', K_JOY1,  '\x087', '\x088', '\x089', '\x08a', '\x08b', 	
	'\x08c', '\x08d', '\x08e', '\x08f', '\x090', '\x091', '\x092', '\x093', '\x094', '\x095', 	
	'\x096', '\x097', '\x098', '\x099', '\x09a', '\x09b', '\x09c', '\x09d', '\x09e', '\x09f', 	
	'\x0a0', '\x0a1', '\x0a2', '\x0a3', '\x0a4', '\x0a5', '\x0a6', '\x0a7', '\x0a8', '\x0a9', 	
	'\x0aa', '\x0ab', '\x0ac', '\x0ad', '\x0ae', '\x0af', '\x0b0', '\x0b1', '\x0b2', '\x0b3', 	
	'\x0b4', '\x0b5', '\x0b6', '\x0b7', '\x0b8', '\x0b9', ':', '+', '<', '_', 
	'>', '?', '~', K_AUX1,  K_AUX2,  K_AUX3,  K_AUX4, K_AUX5, '\x0c6', '\x0c7', 
	'\x0c8', '\x0c9', '\x0ca', '\x0cb', '\x0cc', '\x0cd', '\x0ce', '\x0cf', '\x0d0', '\x0d1', 
	'\x0d2', '\x0d3', '\x0d4', '\x0d5', '\x0d6', '\x0d7', '\x0d8', '\x0d9', '\x0da', '{', 
	'|', '}', '"', '\x0df', '\x0e0', '\x0e1', '\x0e2', '\x0e3', '\x0e4', '\x0e5', 
	'\x0e6', '\x0e7', '\x0e8', '\x0e9', '\x0ea', '\x0eb', '\x0ec', '\x0ed', '\x0ee', '\x0ef', 
	'\x0f0', '\x0f1', '\x0f2', '\x0f3', '\x0f4', '\x0f5', '\x0f6', '\x0f7', '\x0f8', '\x0f9', 
	'\x0fa', '\x0fb', '\x0fc', '\x0fd', '\x0fe', '\x0ff'};

static int Win_MapKey( WPARAM wParam, LPARAM lParam ) {
	if( wParam < 0 || wParam > 255 ) return 0;
	return scantokey[wParam];
}

#else

static byte        scantokey[128] = { 
//  0           1       2			3				4			5				6			7 
//  8           9       A			B				C			D				E			F 
    0  ,		27,     '1',		'2',			'3',		'4',			'5',		'6', 
    '7',		'8',    '9',		'0',			'-',		'=',			K_BACKSPACE, 9,			// 0 
    'q',		'w',    'e',		'r',			't',		'y',			'u',		'i', 
    'o',		'p',	'[',		']',			13 ,		K_CTRL,			'a',		's',		// 1 
    'd',		'f',	'g',		'h',			'j',		'k',			'l',		';', 
    '\'' ,		'`',	K_SHIFT,	'\\',			'z',		'x',			'c',		'v',		// 2 
    'b',		'n',	'm',		',',			'.',		'/',			K_SHIFT,	'*', 
    K_ALT,		' ',	K_CAPSLOCK,	K_F1,			K_F2,		K_F3,			K_F4,		K_F5,		// 3 
    K_F6,		K_F7,	K_F8,		K_F9,			K_F10,		K_PAUSE,		K_SCROLLOCK, K_HOME, 
    K_UPARROW,	K_PGUP,	K_KP_MINUS,	K_LEFTARROW,	K_KP_5,		K_RIGHTARROW,	K_KP_PLUS,	K_END,		// 4 
    K_DOWNARROW,K_PGDN,	K_INS,		K_DEL,			0,			0,				0,			K_F11, 
    K_F12,		0,		0,			K_LWINKEY,		K_RWINKEY,  K_MENU,			0,			0,			// 5
    0,			0,		0,			0,				0,			0,				0,			0, 
    0,			0,		0,			0,				0,			0,				0,			0,			// 6 
    0,			0,		0,			0,				0,			0,				0,			0, 
    0,			0,		0,			0,				0,			0,				0,			0			// 7 
}; 



/*
=======
Win_KeyEvent

Map from windows to quake keynums
=======
*/
static void Win_KeyEvent( WPARAM wParam, LPARAM lParam, qboolean down ) {
	uint32 result;
	uint32 scancode = ( lParam >> 16 ) & 255;
	qboolean is_extended = qfalse;

	if( scancode > 127 ) {
		return;
    }

	if( lParam & ( 1 << 24 ) ) {
		is_extended = qtrue;
    }

	result = scantokey[scancode];
    if( !result ) {
		Com_DPrintf( "Win_KeyEvent: unknown scancode %u\n", scancode );
        return;
    }

	if( !is_extended ) {
		switch( result ) {
		case K_HOME:
			result = K_KP_HOME;
            break;
		case K_UPARROW:
			result = K_KP_UPARROW;
            break;
		case K_PGUP:
			result = K_KP_PGUP;
            break;
		case K_LEFTARROW:
			result = K_KP_LEFTARROW;
            break;
		case K_RIGHTARROW:
			result = K_KP_RIGHTARROW;
            break;
		case K_END:
			result = K_KP_END;
            break;
		case K_DOWNARROW:
			result = K_KP_DOWNARROW;
            break;
		case K_PGDN:
			result = K_KP_PGDN;
            break;
		case K_INS:
			result = K_KP_INS;
            break;
		case K_DEL:
			result = K_KP_DEL;
            break;
		}
	} else {
		switch( result ) {
		case 0x0D:
			result = K_KP_ENTER;
            break;
		case 0x2F:
			result = K_KP_SLASH;
            break;
		case 0xAF:
			result = K_KP_PLUS;
            break;
		}
	}

	Key_Event( result, down, lastMsgTime );
}

#endif

/*
====================
Win_MainWndProc

main window procedure
====================
*/
LONG WINAPI Win_MainWndProc ( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {
	switch( uMsg ) {
	case WM_MOUSEWHEEL: {
			UINT lines;
			int key;

// this chunk of code theoretically only works under NT4 and Win98
// since this message doesn't exist under Win95
			if( ( short )HIWORD( wParam ) > 0 ) {
				key = K_MWHEELUP;
			} else {
				key = K_MWHEELDOWN;
			}

			if( keys.GetDest() & KEY_CONSOLE ) {
				SystemParametersInfo( SPI_GETWHEELSCROLLLINES, 0, &lines, 0 );
				if( !lines ) {
					lines = 1;
				} else if( lines > 6 ) {
					lines = 6;
				}
			} else {
				lines = 1;
			}

			do {
				Key_Event( key, qtrue, lastMsgTime );
				Key_Event( key, qfalse, lastMsgTime );
			} while( --lines );
		}
		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:
	case WM_XBUTTONDOWN:
	case WM_XBUTTONUP: {
			int	temp = 0;

			if( wParam & MK_LBUTTON )
				temp |= 1;

			if( wParam & MK_RBUTTON )
				temp |= 2;

			if( wParam & MK_MBUTTON )
				temp |= 4;

			if( wParam & MK_XBUTTON1 )
				temp |= 8;

			if( wParam & MK_XBUTTON2 )
				temp |= 16;

			Win_SendMouseButtonEvents( temp, lastMsgTime );
		}
		break;
	case WM_HOTKEY:
		return FALSE;

	case WM_CREATE:
		hWndMain = hWnd;
		Cvar_SetIntegerHex( "win_hwnd", ( uint32 )hWnd );
		break;

	case WM_CLOSE:
		PostQuitMessage( 0 );
		return FALSE;

	case WM_DESTROY:
		hWndMain = NULL;
		Cvar_SetInteger( "win_hwnd", 0 );
		break;

	case WM_ACTIVATE:
		Win_AppActivate( wParam );
		break;

	case WM_SIZE:
		if( wParam == SIZE_MAXIMIZED ) {
			if( !vid_fullscreen->integer ) {
				Cvar_SetInteger( "vid_fullscreen", 1 );
				Cbuf_AddText( "vid_restart\n" );
			}
		}
		return FALSE;

	case WM_MOVE: {
		short		xPos, yPos;
		RECT r;
		int		style;

		if( vid_fullscreen->integer ) {
			break;
		}

		xPos = ( signed short )LOWORD( lParam );    // horizontal position 
		yPos = ( signed short )HIWORD( lParam );    // vertical position 

		r.left   = 0;
		r.top    = 0;
		r.right  = 1;
		r.bottom = 1;

		style = GetWindowLong( hWnd, GWL_STYLE );
		AdjustWindowRect( &r, style, FALSE );

		Cvar_SetInteger( "vid_xpos", xPos + r.left );
		Cvar_SetInteger( "vid_ypos", yPos + r.top );
	}
	break;

#ifdef _WIN32_WCE
	case WM_SETTINGCHANGE:
		if( wParam == SPI_SETSIPINFO ) {
			SIPINFO si;

			/* TODO */
			memset( &si, 0, sizeof( si ) );
			si.cbSize = sizeof( si );
			SHSipInfo( SPI_GETSIPINFO, 0, &si, 0 );
			
			if( si.fdwFlags & SIPF_ON ) {
				Con_SetMaxHeight( 1.0f - 80.0f / 296 );
			} else {
				Con_SetMaxHeight( 1.0f );
			}
		}
		break;

#else /* _WIN32_WCE */
	case WM_SYSCOMMAND:
		if( wParam == SC_SCREENSAVE ) {
			return FALSE;
		}
        break;
#endif /* !_WIN32_WCE */

	case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
		Win_KeyEvent( wParam, lParam, qtrue );
		break;

	case WM_CHAR:
#ifdef UNICODE
		{
			char tinystr[2];
			wchar_t wstr[2];
			wstr[0] = wParam;
			wstr[1] = 0;
			WideCharToMultiByte( CP_ACP, 0, wstr, -1, tinystr, 2, NULL, NULL );
			Key_CharEvent( tinystr[0] );
		}
#elif (defined USE_CHAR_EVENTS)
		Key_CharEvent( wParam );
#endif
		break;

	case WM_SYSKEYUP:
	case WM_KEYUP:
		Win_KeyEvent( wParam, lParam, qfalse );
		break;

	default:	
        break;
    }

	// pass all unhandled messages to DefWindowProc
    return DefWindowProc( hWnd, uMsg, wParam, lParam );
}

/*
============
Win_PumpEvents
============
*/
void Video_PumpEvents( void ) {
	MSG        msg;
	
	while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) {
		if( msg.message == WM_QUIT ) {
			Com_Quit();
			break;
		}
		lastMsgTime = msg.time;
      	TranslateMessage( &msg );
      	DispatchMessage( &msg );
	}
}

/*
============
Win_Init
============
*/
void Win_Init( void ) {
	vid_fullscreen = Cvar_Get( "vid_fullscreen", "", CVAR_USER_CREATED );
	vid_xpos = Cvar_Get( "vid_xpos", "0", CVAR_ARCHIVE );
	vid_xpos->changedFunc = Win_VidPos_OnChange;
	vid_ypos = Cvar_Get( "vid_ypos", "0", CVAR_ARCHIVE );
	vid_ypos->changedFunc = Win_VidPos_OnChange;
	sys_winnt = Cvar_Get( "sys_winnt", "0", CVAR_ROM|CVAR_USER_CREATED );
	win_hwnd = Cvar_Get( "win_hwnd", "0", CVAR_ROM );
	win_noalttab = Cvar_Get( "win_noalttab", "0", CVAR_ARCHIVE );
	win_noalttab->changedFunc = Win_NotAltTab_OnChange;
	win_disablewinkey = Cvar_Get( "win_disablewinkey", "0", CVAR_ARCHIVE );
	win_disablewinkey->changedFunc = DisableWinKey_OnChange;

	DisableWinKey_OnChange( win_disablewinkey, NULL );

	win_initialized = qtrue;
}

/*
============
Win_Shutdown
============
*/
void Win_Shutdown( void ) {
	if( kbdHook ) {
		UnhookWindowsHookEx( kbdHook );
		kbdHook = NULL;
	}

	win_initialized = qfalse;
}

