#define DIRECTINPUT_VERSION 0x300	// We want to support DX 3.0, and
#define _WIN32_WINNT 0x0400			// we want to support the mouse wheel

#define WIN32_LEAN_AND_MEAN
#define __BYTEBOOL__
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#include <dinput.h>

#include "doomtype.h"
#include "doomdef.h"
#include "doomstat.h"
#include "m_argv.h"
#include "i_input.h"
#include "v_video.h"
#include "hardware.h"

#include "d_main.h"
#include "c_console.h"
#include "c_cvars.h"
#include "i_system.h"
#include "i_video.h"

#include "s_sound.h"

#define DINPUT_BUFFERSIZE	32

extern HINSTANCE g_hInst;

static void KeyRead (void);
static BOOL DI_Init2 (void);
static void MouseRead_DI (void);
static void MouseRead_Win32 (void);
static void GrabMouse_Win32 (void);
static void UngrabMouse_Win32 (void);
static BOOL I_GetDIMouse (void);
static void I_GetWin32Mouse (void);
static void CenterMouse_Win32 (void);
static void WheelMoved (void);
static void DI_Acquire (LPDIRECTINPUTDEVICE mouse);
static void DI_Unacquire (LPDIRECTINPUTDEVICE mouse);
static void SetCursorState (int visible);

static BOOL mousepaused;
static BOOL MakeMouseEvents;
BOOL WindowActive;

extern BOOL menuactive;
extern BOOL vidactive;
extern HWND Window;

EXTERN_CVAR (fullscreen)


// [RH] As of 1.14, ZDoom no longer needs to be linked with dinput.lib.
//		We now go straight to the DLL ourselves.

#ifndef __CYGNUS__
typedef HRESULT (WINAPI *DIRECTINPUTCREATE_FUNCTION) (HINSTANCE, DWORD, LPDIRECTINPUT*, LPUNKNOWN);
#else
typedef HRESULT WINAPI (*DIRECTINPUTCREATE_FUNCTION) (HINSTANCE, DWORD, LPDIRECTINPUT*, LPUNKNOWN);
#endif

static HMODULE DirectInputInstance;

void InitKeyboardObjectData (void);

typedef enum { win32, dinput } mousemode_t;
static mousemode_t mousemode;

extern BOOL paused;
static BOOL havefocus = FALSE;
static BOOL noidle = FALSE;
static int WheelMove;

// Used by the console for making keys repeat
int KeyRepeatDelay;
int KeyRepeatRate;

static LPDIRECTINPUT			g_pdi;
static LPDIRECTINPUTDEVICE		g_pKey;
static LPDIRECTINPUTDEVICE		g_pMouse;

//Other globals
static int GDx,GDy;

extern constate_e ConsoleState;

CVAR (i_remapkeypad, "1", CVAR_ARCHIVE)
CVAR (use_mouse, "1", CVAR_ARCHIVE)

CVAR (use_joystick, "0", CVAR_ARCHIVE)
CVAR (joy_speedmultiplier, "1", CVAR_ARCHIVE)
CVAR (joy_xsensitivity, "1", CVAR_ARCHIVE)
CVAR (joy_ysensitivity, "-1", CVAR_ARCHIVE)
CVAR (joy_xthreshold, "0.15", CVAR_ARCHIVE)
CVAR (joy_ythreshold, "0.15", CVAR_ARCHIVE)

BEGIN_CUSTOM_CVAR (in_mouse, "0", CVAR_ARCHIVE)
{
	if (var.value < 0)
	{
		var.Set (0.0f);
	}
	else if (var.value > 2)
	{
		var.Set (2.0f);
	}
	else if (!g_pdi)
	{
		return;
	}
	else
	{
		int new_mousemode;

		if (var.value == 1 || (var.value == 0 && OSPlatform == os_WinNT))
			new_mousemode = win32;
		else
			new_mousemode = dinput;

		if (new_mousemode != mousemode)
		{
			if (new_mousemode == win32)
				I_GetWin32Mouse ();
			else
				if (!I_GetDIMouse ())
					I_GetWin32Mouse ();
			if (!fullscreen.value && mousepaused)
				I_PauseMouse ();
		}
	}
}
END_CUSTOM_CVAR (in_mouse)

// Convert DIK_* code to ASCII using Qwerty keymap
static const byte Convert []={
  //  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', '-', '=',   8,   9, // 0
	'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']',  13,   0, 'a', 's', // 1
	'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',  39, '`',   0,'\\', 'z', 'x', 'c', 'v', // 2
	'b', 'n', 'm', ',', '.', '/',   0, '*',   0, ' ',   0,   0,   0,   0,   0,   0, // 3
	  0,   0,   0,   0,   0,   0,   0, '7', '8', '9', '-', '4', '5', '6', '+', '1', // 4
	'2', '3', '0', '.',   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   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

	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, '=',   0,   0, // 8
	  0, '@', ':', '_',   0,   0,   0,   0,   0,   0,   0,   0,  13,   0,   0,   0, // 9
	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // A
	  0,   0,   0, ',',   0, '/',   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // B
	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // C
	  0,   0,   0,   0,   0,   0,   0,   0

};

// Convert DIK_* code to ASCII using user keymap (built at run-time)
// New on 19.7.1998 - Now has 8 tables for each possible combination
// of CTRL, ALT, and SHIFT.
static byte Convert2[256][8];

#define SHIFT_SHIFT 0
#define CTRL_SHIFT 1
#define ALT_SHIFT 2

static BOOL altdown, shiftdown, ctrldown;

LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	event_t event;

	event.data1 = event.data2 = event.data3 = 0;

	switch(message)
	{
		case WM_DESTROY:
			SetPriorityClass (GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
			//PostQuitMessage (0);
			exit (0);
			break;

		case WM_HOTKEY:
			break;

		case WM_PAINT:
			return DefWindowProc (hWnd, message, wParam, lParam);

		case WM_SIZE:
			if(Video)
			{
				if(LOWORD(lParam) != Video->m_DisplayWidth
					|| HIWORD(lParam) != Video->m_DisplayHeight)
				{
					setmodeneeded = 1;
					NewWidth = LOWORD(lParam); 
					NewHeight = LOWORD(lParam); 
					NewBits = 8;
				}
			}
			break;

		case WM_KILLFOCUS:
			if (g_pKey) g_pKey->Unacquire ();
			if (g_pMouse)
			{
				DI_Unacquire (g_pMouse);
				UngrabMouse_Win32 ();
			}
			if (altdown) {
				altdown = FALSE;
				event.type = ev_keyup;
				event.data1 = DIK_LALT;
				D_PostEvent (&event);
			}
			if (shiftdown) {
				shiftdown = FALSE;
				event.type = ev_keyup;
				event.data1 = DIK_LSHIFT;
				D_PostEvent (&event);
			}
			if (ctrldown) {
				ctrldown = FALSE;
				event.type = ev_keyup;
				event.data1 = DIK_LCONTROL;
				D_PostEvent (&event);
			}
			havefocus = FALSE;
			if (!paused)
				S_PauseSound ();
			if (!noidle)
			{
			//  DL - no crap
			//	SetPriorityClass (GetCurrentProcess(), IDLE_PRIORITY_CLASS);
			}
			break;

		case WM_SETFOCUS:
// DL - no crap like this please
/*#ifdef _DEBUG
//  DL - no crap
//			SetPriorityClass (GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
#else
//  DL - no crap
//			SetPriorityClass (GetCurrentProcess(), HIGH_PRIORITY_CLASS);
#endif*/
			if (g_pKey)
				DI_Acquire (g_pKey);
			havefocus = TRUE;
			if (g_pMouse && (fullscreen.value || !mousepaused))
			{
				GrabMouse_Win32 ();
				I_ResumeMouse ();
			}
			if (!paused)
				S_ResumeSound ();
			break;

		case WM_ACTIVATE:
			if (LOWORD(wParam)) {
				WindowActive = TRUE;
				if (mousemode == win32 && MakeMouseEvents &&
					(!mousepaused || fullscreen.value)) {
					GrabMouse_Win32 ();
				}
			} else {
				WindowActive = FALSE;
				if (mousemode == win32) {
					UngrabMouse_Win32 ();
				}
			}
			break;

		// Being forced to separate my keyboard input handler into
		// two pieces like this really stinks. (IMHO)
		case WM_KEYDOWN:
		case WM_KEYUP:
			if (message == WM_KEYUP) {
				event.type = ev_keyup;
			} else {
				if (lParam & 0x40000000)
					return 0;
				else
					event.type = ev_keydown;
			}

			switch (wParam) {
				case VK_PAUSE:
					event.data1 = KEY_PAUSE;
					break;
				case VK_TAB:
					event.data1 = DIK_TAB;
					event.data2 = '\t';
					event.data3 = Convert2[DIK_TAB][(shiftdown << SHIFT_SHIFT) |
													(ctrldown << CTRL_SHIFT) |
													(altdown << ALT_SHIFT)];
					break;
				case VK_NUMLOCK:
					event.data1 = DIK_NUMLOCK;
					break;
				case VK_SHIFT:
					event.data1 = KEY_LSHIFT;
					shiftdown = (event.type == ev_keydown);
					break;
				case VK_CONTROL:
					event.data1 = KEY_LCTRL;
					ctrldown = (event.type == ev_keydown);
					break;
			}
			if (event.data1)
				D_PostEvent (&event);
			break;

		case WM_SYSKEYDOWN:
			SendMessage (hWnd, WM_KEYDOWN, wParam, lParam);
			break;

		case WM_SYSKEYUP:
			SendMessage (hWnd, WM_KEYUP, wParam, lParam);
			break;

		case WM_SYSCOMMAND:
			{
				WPARAM cmdType = wParam & 0xfff0;

				// Prevent activation of the window menu with Alt-Space
				if (cmdType != SC_KEYMENU)
					return DefWindowProc (hWnd, message, wParam, lParam);
			}
			break;

		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_RBUTTONDOWN:
		case WM_RBUTTONUP:
		case WM_MBUTTONDOWN:
		case WM_MBUTTONUP:
			if (MakeMouseEvents && mousemode == win32) {
				event.type = ((message - WM_LBUTTONDOWN) % 3) ? ev_keyup : ev_keydown;
				event.data1 = KEY_MOUSE1 + (message - WM_LBUTTONDOWN) / 3;
				event.data2 = event.data3 = 0;
				D_PostEvent (&event);
			}
			break;

		case WM_MOUSEWHEEL:
			if (MakeMouseEvents && mousemode == win32)
			{
				WheelMove += (short) HIWORD(wParam);
				WheelMoved ();
			}
			break;

		case WM_GETMINMAXINFO:
			if (screen)
			{
				LPMINMAXINFO mmi = (LPMINMAXINFO)lParam;
				mmi->ptMinTrackSize.x = screen->width + GetSystemMetrics (SM_CXSIZEFRAME) * 2;
				mmi->ptMinTrackSize.y = screen->height + GetSystemMetrics (SM_CYSIZEFRAME) * 2 +
									    GetSystemMetrics (SM_CYCAPTION);
				return 0;
			}
			break;

		default:
			return DefWindowProc (hWnd, message, wParam, lParam);
	}

	return 0;
}

static void BuildCvt2Table (void)
{
	byte vk2scan[256];
	int i;

	memset (vk2scan, 0, 256);
	for (i = 0; i < 256; i++)
		vk2scan[MapVirtualKey (i, 1)] = i;

	vk2scan[0] = 0;

	for (i = 0; i < 256; i++) {
		int code = VkKeyScan ((TCHAR)i);
		Convert2[vk2scan[code & 0xff]][(code >> 8) & 7] =  i;
	}
}

/****** Stuff from Andy Bay's myjoy.c ******/

static struct {
	int X,Y,Z,R,U,V;
}					JoyBias;
static int 			JoyActive;
static int			JoyDevice;
static JOYINFOEX	JoyStats;
static JOYCAPS 		JoyCaps;

void JoyFixBias (void)
{
	JoyBias.X = (JoyCaps.wXmin + JoyCaps.wXmax) >> 1;
	JoyBias.Y = (JoyCaps.wYmin + JoyCaps.wYmax) >> 1;
//	JoyBias.Z = (JoyCaps.wZmin + JoyCaps.wZmax) >> 1;
//	JoyBias.R = (JoyCaps.wRmin + JoyCaps.wRmax) >> 1;
//	JoyBias.U = (JoyCaps.wUmin + JoyCaps.wUmax) >> 1;
//	JoyBias.V = (JoyCaps.wVmin + JoyCaps.wVmax) >> 1;
}

void DI_JoyCheck (void)
{
	event_t joyevent;
	fixed_t xscale, yscale;
	int xdead, ydead;

	if (JoyActive) {
		JoyStats.dwFlags = JOY_RETURNALL;
		if (joyGetPosEx (JoyDevice, &JoyStats)) {
			JoyActive = 0;
			return;
		}
		joyevent.type = ev_joystick;
		joyevent.data1 = 0;
		joyevent.data2 = JoyStats.dwXpos - JoyBias.X;
		joyevent.data3 = JoyStats.dwYpos - JoyBias.Y;

		xdead = (int)((float)JoyBias.X * joy_xthreshold.value);
		ydead = (int)((float)JoyBias.Y * joy_ythreshold.value);
		xscale = (int)(16777216 / ((float)JoyBias.X * (1 - joy_xthreshold.value)) * joy_xsensitivity.value * joy_speedmultiplier.value);
		yscale = (int)(16777216 / ((float)JoyBias.Y * (1 - joy_ythreshold.value)) * joy_ysensitivity.value * joy_speedmultiplier.value);

		if (abs (joyevent.data2) < xdead)
			joyevent.data2 = 0;
		else if (joyevent.data2 > 0) {
			joyevent.data2 = FixedMul (joyevent.data2 - xdead, xscale);
		} else if (joyevent.data2 < 0) {
			joyevent.data2 = FixedMul (joyevent.data2 + xdead, xscale);
		}
		if (joyevent.data2 > 256)
			joyevent.data2 = 256;
		else if (joyevent.data2 < -256)
			joyevent.data2 = -256;

		if (abs (joyevent.data3) < ydead)
			joyevent.data3 = 0;
		else if (joyevent.data3 > 0) {
			joyevent.data3 = FixedMul (joyevent.data3 - ydead, yscale);
		} else if (joyevent.data3 < 0) {
			joyevent.data3 = FixedMul (joyevent.data3 + ydead, yscale);
		}
		if (joyevent.data3 > 256)
			joyevent.data3 = 256;
		else if (joyevent.data3 < -256)
			joyevent.data3 = -256;
/*
		joyevent.type = ev_joystick;
		joyevent.data1 = 0;
		joyevent.data2 = JoyStats.dwXpos - JoyBias.X;

		//Doom thinks it is digital.  ugh.
		if (joyevent.data2 > JoyBias.X / 2){
			joyevent.data2 = 1;
		}
		else if(joyevent.data2 < -JoyBias.X / 2){
			joyevent.data2 = -1;
		}
		else
			joyevent.data2 = 0;

		joyevent.data3 = JoyStats.dwYpos - JoyBias.Y;
		if (joyevent.data3 > JoyBias.Y / 2) {
			joyevent.data3 = 1;
		}
		else if (joyevent.data3 < -JoyBias.Y / 2) {
			joyevent.data3 = -1;
		}
		else
			joyevent.data3 = 0;
*/
		D_PostEvent (&joyevent);

		{	/* Send out button up/down events */
			static DWORD oldButtons = 0;
			int i;
			DWORD buttons, mask;

			buttons = JoyStats.dwButtons;
			mask = buttons ^ oldButtons;

			joyevent.data2 = joyevent.data3 = 0;
			for (i = 0; i < 32; i++, buttons >>= 1, mask >>= 1) {
				if (mask & 1) {
					joyevent.data1 = KEY_JOY1 + i;
					if (buttons & 1)
						joyevent.type = ev_keydown;
					else
						joyevent.type = ev_keyup;
					D_PostEvent (&joyevent);
				}
			}
			oldButtons = JoyStats.dwButtons;
		}
	}
}

BOOL DI_InitJoy (void)
{
	int i;

	JoyActive = joyGetNumDevs ();
	JoyStats.dwSize = sizeof(JOYINFOEX);
	JoyDevice = -1;

	for (i = JOYSTICKID1; i <= JOYSTICKID2; i++) {
		if (joyGetDevCaps (i, &JoyCaps, sizeof(JOYCAPS)) != JOYERR_NOERROR)
			continue;

		JoyStats.dwFlags = JOY_RETURNALL;
		if (joyGetPosEx (i, &JoyStats) != JOYERR_NOERROR)
			continue;

		JoyDevice = i;
		break;
	}

	if (JoyDevice == -1)
		JoyActive = 0;
	else
		JoyFixBias();

	return TRUE;
}

static void DI_Acquire (LPDIRECTINPUTDEVICE mouse)
{
	mouse->Acquire ();
	if (!mousepaused)
		SetCursorState (FALSE);
	else
		SetCursorState (TRUE);
}

static void DI_Unacquire (LPDIRECTINPUTDEVICE mouse)
{
	mouse->Unacquire ();
	SetCursorState (TRUE);
}

void I_PauseMouse (void)
{
	if (fullscreen.value)
		return;

	mousepaused = TRUE;
	if (g_pMouse) {
		DI_Unacquire (g_pMouse);
	} else {
		UngrabMouse_Win32 ();
	}
}

void I_ResumeMouse (void)
{
	if (!fullscreen.value && gamestate == GS_FULLCONSOLE)
		return;

	mousepaused = FALSE;
	if (!g_pMouse) {
		GrabMouse_Win32 ();
	} else {
		DI_Acquire (g_pMouse);
	}
}

/****** Stuff from Andy Bay's mymouse.c ******

/****************************************************************************
 *
 *		DIInit
 *
 *		Initialize the DirectInput variables.
 *
 ****************************************************************************/

// [RH] Obtain the mouse using standard Win32 calls. Should always work.
static void I_GetWin32Mouse (void)
{
	mousemode = win32;

	if (g_pMouse) {
		DI_Unacquire (g_pMouse);
		g_pMouse->Release ();
		g_pMouse = NULL;
	}
	GrabMouse_Win32 ();
}

// [RH] Used to obtain DirectInput access to the mouse.
//		(Preferred for Win95, but buggy under NT.)
static BOOL I_GetDIMouse (void)
{
	HRESULT hr;
	DIPROPDWORD dipdw =
		{
			{
				sizeof(DIPROPDWORD),		// diph.dwSize
				sizeof(DIPROPHEADER),		// diph.dwHeaderSize
				0,							// diph.dwObj
				DIPH_DEVICE,				// diph.dwHow
			},
			DINPUT_BUFFERSIZE,				// dwData
		};

	if (mousemode == dinput)
		return FALSE;

	mousemode = win32;	// Assume failure
	UngrabMouse_Win32 ();

	if (in_mouse.value == 1 || (in_mouse.value == 0 && OSPlatform == os_WinNT))
		return FALSE;

	// Obtain an interface to the system mouse device.
	hr = g_pdi->CreateDevice (GUID_SysMouse, &g_pMouse, NULL);

	if (FAILED(hr))
		return FALSE;

	// Set the data format to "mouse format".
	hr = g_pMouse->SetDataFormat (&c_dfDIMouse);

	if (FAILED(hr)) {
		g_pMouse->Release ();
		g_pMouse = NULL;
		return FALSE;
	}

	// Set the cooperative level.
	hr = g_pMouse->SetCooperativeLevel ((HWND)Window,
									   DISCL_EXCLUSIVE | DISCL_FOREGROUND);

	if (FAILED(hr)) {
		g_pMouse->Release ();
		g_pMouse = NULL;
		return FALSE;
	}


	// Set the buffer size to DINPUT_BUFFERSIZE elements.
	// The buffer size is a DWORD property associated with the device.
	hr = g_pMouse->SetProperty (DIPROP_BUFFERSIZE, &dipdw.diph);

	if (FAILED(hr)) {
		Printf (PRINT_HIGH, "Could not set mouse buffer size");
		g_pMouse->Release ();
		g_pMouse = NULL;
		return FALSE;
	}

	DI_Acquire (g_pMouse);

	mousemode = dinput;
	return TRUE;
}

BOOL I_InitInput (void *hwnd)
{
	DIRECTINPUTCREATE_FUNCTION DirectInputCreateFunction;
	HRESULT hr;

	atterm (I_ShutdownInput);

	noidle = Args.CheckParm ("-noidle");

	// [RH] Removed dependence on existance of dinput.lib when linking.
	DirectInputInstance = (HMODULE)LoadLibrary ("dinput.dll");
	if (!DirectInputInstance)
		I_FatalError ("Sorry, you need Microsoft's DirectX 3 or higher installed.\n\n"
					  "Go grab it at http://www.microsoft.com/directx\n");

	DirectInputCreateFunction = (DIRECTINPUTCREATE_FUNCTION)GetProcAddress (DirectInputInstance, "DirectInputCreateA");
	if (!DirectInputCreateFunction)
		I_FatalError ("Could not get address of DirectInputCreateA");

	// Register with DirectInput and get an IDirectInput to play with.
	hr = DirectInputCreateFunction (g_hInst, DIRECTINPUT_VERSION, &g_pdi, NULL);

	if (FAILED(hr))
		I_FatalError ("Could not obtain DirectInput interface");

	if (!I_GetDIMouse ())
		I_GetWin32Mouse ();

	DI_Init2();

	return TRUE;
}


// Free all input resources
void STACK_ARGS I_ShutdownInput (void)
{
	if (g_pKey) {
		g_pKey->Unacquire ();
		g_pKey->Release ();
		g_pKey = NULL;
	}
	if (g_pMouse) {
		DI_Unacquire (g_pMouse);
		g_pMouse->Release ();
		g_pMouse = NULL;
	}
	UngrabMouse_Win32 ();
	if (g_pdi) {
		g_pdi->Release ();
		g_pdi = NULL;
	}
	// [RH] Close dinput.dll
	if (DirectInputInstance) {
		FreeLibrary (DirectInputInstance);
		DirectInputInstance = NULL;
	}
}

static LONG PrevX, PrevY;

static void CenterMouse_Win32 (void)
 {
	RECT rect;

	GetWindowRect (Window, &rect);
	PrevX = (rect.left + rect.right) >> 1;
	PrevY = (rect.top + rect.bottom) >> 1;
	SetCursorPos (PrevX, PrevY);
}

static void SetCursorState (int visible)
 {
	int count;
	BOOL direction = visible;
	
	do {
		count = ShowCursor (direction);
		if (visible && count > 0)
			direction = FALSE;
		else if (!visible && count < -1)
			direction = TRUE;
	} while (count != visible - 1);
}

static void GrabMouse_Win32 (void)
 {
	RECT rect;

	ClipCursor (NULL);		// helps with Win95?
	GetWindowRect (Window, &rect);
	ClipCursor (&rect);
	SetCursorState (FALSE);
	CenterMouse_Win32 ();
	MakeMouseEvents = TRUE;
}

static void UngrabMouse_Win32 (void)
 {
	ClipCursor (NULL);
	SetCursorState (TRUE);
	MakeMouseEvents = FALSE;
}

static void WheelMoved (void)
{
	event_t event;
	int dir;

	event.data2 = event.data3 = 0;
	if (WheelMove < 0) {
		dir = WHEEL_DELTA;
		event.data1 = KEY_MWHEELDOWN;
	} else {
		dir = -WHEEL_DELTA;
		event.data1 = KEY_MWHEELUP;
	}
	while (abs (WheelMove) >= WHEEL_DELTA) {
		event.type = ev_keydown;
		D_PostEvent (&event);
		event.type = ev_keyup;
		D_PostEvent (&event);
		WheelMove += dir;
	}
}

static void MouseRead_Win32 (void) {
	POINT pt;
	event_t ev;

	if (!WindowActive || !MakeMouseEvents || !GetCursorPos (&pt))
		return;

	ev.data2 = (pt.x - PrevX) * 3;
	ev.data3 = (PrevY - pt.y) << 1;

	CenterMouse_Win32 ();

	if (ev.data2 || ev.data3) {
		ev.type = ev_mouse;
		ev.data1 = 0;
		D_PostEvent (&ev);
	}
}

static void MouseRead_DI (void) {
	static int lastx = 0, lasty = 0;
	DIDEVICEOBJECTDATA od;
	DWORD dwElements;
	HRESULT hr;
	int count = 0;

	event_t event;
	GDx=0;
	GDy=0;

	if (!g_pMouse)
		return;

	event.data2 = event.data3 = 0;
	while (1) {
		dwElements = 1;
		hr = g_pMouse->GetDeviceData (sizeof(DIDEVICEOBJECTDATA), &od,
							 &dwElements, 0);
		if (hr == DIERR_INPUTLOST) {
			DI_Acquire (g_pMouse);
			hr = g_pMouse->GetDeviceData (sizeof(DIDEVICEOBJECTDATA), &od,
								 &dwElements, 0);
		}

		/* Unable to read data or no data available */
		if (FAILED(hr) || !dwElements)
			break;

		count++;

		/* Look at the element to see what happened */
		switch (od.dwOfs) {

		/* DIMOFS_X: Mouse horizontal motion */
			case DIMOFS_X:
				GDx += od.dwData;
				break;
		/* DIMOFS_Y: Mouse vertical motion */
			case DIMOFS_Y:
				GDy += od.dwData;
				break;

		/* DIMOFS_Z: Mouse wheel motion */
			case DIMOFS_Z:
				WheelMove += od.dwData;
				WheelMoved ();
				break;

		/* [RH] Mouse button events now mimic keydown/up events */
			case DIMOFS_BUTTON0:
				if(od.dwData & 0x80) {
					event.type = ev_keydown;
				} else {
					event.type = ev_keyup;
				}
				event.data1 = KEY_MOUSE1;
				D_PostEvent (&event);
				break;
			case DIMOFS_BUTTON1:
				if(od.dwData & 0x80) {
					event.type = ev_keydown;
				} else {
					event.type = ev_keyup;
				}
				event.data1 = KEY_MOUSE2;
				D_PostEvent (&event);
				break;
			case DIMOFS_BUTTON2:
				if(od.dwData & 0x80) {
					event.type = ev_keydown;
				} else {
					event.type = ev_keyup;
				}
				event.data1 = KEY_MOUSE3;
				D_PostEvent (&event);
				break;
			case DIMOFS_BUTTON3:
				if(od.dwData & 0x80) {
					event.type = ev_keydown;
				} else {
					event.type = ev_keyup;
				}
				event.data1 = KEY_MOUSE4;
				D_PostEvent (&event);
				break;
		}
	}

	if (count) {
		event.type = ev_mouse;
		event.data1 = 0;
		event.data2 = GDx<<2;
		event.data3 = -GDy;
		D_PostEvent (&event);
	}
}

// Initialize the keyboard
static BOOL DI_Init2 (void)
{
	int hr;
	DWORD repeatStuff;
	DIPROPDWORD dipdw =
		{
			{
				sizeof(DIPROPDWORD),		// diph.dwSize
				sizeof(DIPROPHEADER),		// diph.dwHeaderSize
				0,							// diph.dwObj
				DIPH_DEVICE,				// diph.dwHow
			},
			DINPUT_BUFFERSIZE,				// dwData
		};

	BuildCvt2Table ();

	// [RH] The timing values for these SPI_* parameters are described in
	//		MS Knowledge Base article Q102978.
	if (SystemParametersInfo (SPI_GETKEYBOARDDELAY, 0, &repeatStuff, 0)) {
		// 0 = 250 ms, 3 = 1000 ms
		KeyRepeatDelay = ((repeatStuff * 250 + 250) * TICRATE) / 1000;
	} else {
		KeyRepeatDelay = (250 * TICRATE) / 1000;
	}
	DPrintf ("KeyRepeatDelay = %u tics\n", KeyRepeatDelay);

	if (SystemParametersInfo (SPI_GETKEYBOARDSPEED, 0, &repeatStuff, 0)) {
		// 0 = 2/sec, 31 = 30/sec
		KeyRepeatRate = TICRATE / (2 + repeatStuff);
	} else {
		KeyRepeatRate = TICRATE / 15;
	}
	DPrintf ("KeyRepeatRate = %u tics\n", KeyRepeatRate);

	// Obtain an interface to the system key device.
	hr = g_pdi->CreateDevice (GUID_SysKeyboard, &g_pKey, NULL);

	if (FAILED(hr)) {
		I_FatalError ("Could not create keyboard device");
	}

	// [RH] Prepare c_dfDIKeyboard for use.
	InitKeyboardObjectData ();

	// Set the data format to "keyboard format".
	hr = g_pKey->SetDataFormat (&c_dfDIKeyboard);

	if (FAILED(hr)) {
		I_FatalError ("Could not set keyboard data format");
	}


	// Set the cooperative level.
	hr = g_pKey->SetCooperativeLevel (Window,
									  DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

	if (FAILED(hr)) {
		I_FatalError("Could not set keyboard cooperative level");
	}

	// Set the buffer size to DINPUT_BUFFERSIZE elements.
	// The buffer size is a DWORD property associated with the device.
	hr = g_pKey->SetProperty (DIPROP_BUFFERSIZE, &dipdw.diph);

	if (FAILED(hr)) {
		I_FatalError ("Could not set keyboard buffer size");
	}

	g_pKey->Acquire ();

	DI_InitJoy ();
	return TRUE;
}

static void KeyRead (void) {
	HRESULT  hr;
	event_t event;

	DIDEVICEOBJECTDATA data[DINPUT_BUFFERSIZE];
	DWORD dwElements, elem;

	do {
		dwElements = DINPUT_BUFFERSIZE;

		hr = g_pKey->GetDeviceData (sizeof(DIDEVICEOBJECTDATA), data, &dwElements, 0);
		if (hr == DIERR_INPUTLOST) {
			hr = g_pKey->Acquire ();
			hr = g_pKey->GetDeviceData (sizeof(DIDEVICEOBJECTDATA), data, &dwElements, 0);
		}

		if (SUCCEEDED (hr) && dwElements) {
			int key;

			for (elem = 0; elem < dwElements; elem++) {
				key = data[elem].dwOfs;

				if (data[elem].dwData & 0x80) {
					event.type = ev_keydown;
				} else {
					event.type = ev_keyup;
				}

				switch (key)
				{
					case DIK_NUMPADENTER:	// These keys always translated
						key = DIK_RETURN;
						break;
					case DIK_RMENU:
						key = DIK_LMENU;
						break;
					case DIK_RCONTROL:		// These keys are handled by the message handler
					case DIK_LCONTROL:
					case DIK_RSHIFT:
					case DIK_LSHIFT:
					case DIK_TAB:
					case DIK_NUMLOCK:
						key = 0;
						break;
					default:
						if (!menuactive &&
							(ConsoleState == c_falling || ConsoleState == c_down))
						{
							switch (key)
							{
								case DIK_NUMPAD4:
									key = DIK_4;
									break;
								case DIK_NUMPAD6:
									key = DIK_6;
									break;
								case DIK_NUMPAD8:
									key = DIK_8;
									break;
								case DIK_NUMPAD2:
									key = DIK_2;
									break;
								case DIK_NUMPAD7:
									key = DIK_7;
									break;
								case DIK_NUMPAD9:
									key = DIK_9;
									break;
								case DIK_NUMPAD3:
									key = DIK_3;
									break;
								case DIK_NUMPAD1:
									key = DIK_1;
									break;
								case DIK_NUMPAD0:
									key = DIK_0;
									break;
								case DIK_NUMPAD5:
									key = DIK_5;
									break;
							}
						}
						else if (i_remapkeypad.value)
						{
							switch (key)
							{
								case DIK_NUMPAD4:
									key = DIK_LEFT;
									break;
								case DIK_NUMPAD6:
									key = DIK_RIGHT;
									break;
								case DIK_NUMPAD8:
									key = DIK_UP;
									break;
								case DIK_NUMPAD2:
									key = DIK_DOWN;
									break;
								case DIK_NUMPAD7:
									key = DIK_HOME;
									break;
								case DIK_NUMPAD9:
									key = DIK_PRIOR;
									break;
								case DIK_NUMPAD3:
									key = DIK_NEXT;
									break;
								case DIK_NUMPAD1:
									key = DIK_END;
									break;
								case DIK_NUMPAD0:
									key = DIK_INSERT;
									break;
								case DIK_DECIMAL:
									key = DIK_DELETE;
									break;
							}
						}
				}

				if (key) {
					event.data1 = key;
					if (ConsoleState == c_falling || ConsoleState == c_down) {
						switch (event.data1) {
							case DIK_DIVIDE:
								event.data2 = event.data3 = '/';
								break;
							case DIK_MULTIPLY:
								event.data2 = event.data3 = '*';
								break;
							case DIK_ADD:
								event.data2 = event.data3 = '+';
								break;
							case DIK_SUBTRACT:
								event.data2 = event.data3 = '-';
								break;
							case DIK_DECIMAL:
								event.data2 = event.data3 = '.';
								break;
							default:
								event.data2 = Convert[key];
								event.data3 = Convert2[key][(altdown << ALT_SHIFT) |
															(shiftdown << SHIFT_SHIFT) |
															(ctrldown << CTRL_SHIFT)];
						}
					} else {
						event.data2 = Convert[key];
						event.data3 = Convert2[key][(altdown << ALT_SHIFT) |
													(shiftdown << SHIFT_SHIFT) |
													(ctrldown << CTRL_SHIFT)];
					}
					D_PostEvent (&event);
					if (key == DIK_LALT)
						altdown = (event.type == ev_keydown);
				}
			}
		}
	} while (SUCCEEDED (hr) && dwElements);
}

void I_GetEvent(void)
{
	MSG mess;

//	while (1) {
		while (PeekMessage (&mess, NULL, 0, 0, PM_REMOVE))
		{
			if (mess.message == WM_QUIT)
				exit (mess.wParam);
			TranslateMessage (&mess);
			DispatchMessage (&mess);
		}
//		if (havefocus || netgame || gamestate != GS_LEVEL)
//			break;
//	}

	KeyRead ();

	if (use_mouse.value)
	{
		if (mousemode == dinput)
			MouseRead_DI ();
		else
			MouseRead_Win32 ();
	}
	if (use_joystick.value)
		DI_JoyCheck ();
}


//
// I_StartTic
//
void I_StartTic (void)
{
	I_GetEvent ();
}

//
// I_StartFrame
//
void I_StartFrame (void)
{
}
