#include "global.h"
#include "InputHandler_DirectInput.h"

#include "RageUtil.h"
#include "RageLog.h"
#include "archutils/Win32/AppInstance.h"
#include "archutils/Win32/DirectXHelpers.h"
#include "archutils/Win32/ErrorStrings.h"
#include "archutils/Win32/GraphicsWindow.h"
#include "archutils/Win32/RegistryAccess.h"
#include "InputFilter.h"
#include "PrefsManager.h"
#include "Foreach.h"

#include "InputHandler_DirectInputHelper.h"

REGISTER_INPUT_HANDLER_CLASS2( DirectInput, DInput );

static vector<DIDevice> Devices;

// Number of joysticks found:
static int g_iNumJoysticks;

static BOOL CALLBACK EnumDevicesCallback( const DIDEVICEINSTANCE *pdidInstance, void *pContext )
{
	DIDevice device;

	LOG->Info( "DInput: Enumerating device - Type: 0x%08X Instance Name: \"%s\" Product Name: \"%s\"", pdidInstance->dwDevType, pdidInstance->tszInstanceName, pdidInstance->tszProductName );

	switch( GET_DIDEVICE_TYPE(pdidInstance->dwDevType) )
	{
		case DI8DEVTYPE_JOYSTICK:
		case DI8DEVTYPE_GAMEPAD:
		case DI8DEVTYPE_SUPPLEMENTAL:
		{
			device.type = device.JOYSTICK;
			break;
		}

		case DI8DEVTYPE_KEYBOARD: device.type = device.KEYBOARD; break;
		case DI8DEVTYPE_MOUSE: device.type = device.MOUSE; break;
		default: LOG->Info( "DInput: Unrecognized device ignored." ); return DIENUM_CONTINUE;
	}

	device.JoystickInst = *pdidInstance;

	switch( device.type )
	{
	case device.JOYSTICK:
		if( g_iNumJoysticks == NUM_JOYSTICKS )
			return DIENUM_CONTINUE;

		device.dev = enum_add2( DEVICE_JOY1, g_iNumJoysticks );
		g_iNumJoysticks++;
		break;

	case device.KEYBOARD:
		device.dev = DEVICE_KEYBOARD;
		break;

	case device.MOUSE:
		device.dev = DEVICE_MOUSE;
		break;
	}

	Devices.push_back(device);

	return DIENUM_CONTINUE;
}

static void CheckForDirectInputDebugMode()
{
	int iVal;
	if( RegistryAccess::GetRegValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\DirectInput", "emulation", iVal) )
	{
		if( iVal & 0x8 )
			LOG->Warn("DirectInput keyboard debug mode appears to be enabled. This reduces\n"
					  "input timing accuracy significantly. Disabling this is strongly recommended." );
	}
}

static BOOL CALLBACK CountDevicesCallback( const DIDEVICEINSTANCE *pdidInstance, void *pContext )
{
	(*(int*)pContext)++;
	return DIENUM_CONTINUE;
}

static int GetNumHidDevices()
{
	int i = 0;
	RegistryAccess::GetRegValue( "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\HidUsb\\Enum", "Count", i, false );	// don't warn on error
	return i;
}

static int GetNumJoysticksSlow()
{
	int iCount = 0;
	HRESULT hr = g_dinput->EnumDevices( DI8DEVCLASS_GAMECTRL, CountDevicesCallback, &iCount, DIEDFL_ATTACHEDONLY );
	if( hr != DI_OK )
	{
		LOG->Warn( hr_ssprintf(hr, "g_dinput->EnumDevices") );
	}
	return iCount;
}

InputHandler_DInput::InputHandler_DInput()
{
	LOG->Trace( "InputHandler_DInput::InputHandler_DInput()" );

	CheckForDirectInputDebugMode();

	m_bShutdown = false;
	g_iNumJoysticks = 0;

	AppInstance inst;
	HRESULT hr = DirectInput8Create(inst.Get(), DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID *) &g_dinput, NULL);
	if( hr != DI_OK )
		RageException::Throw( hr_ssprintf(hr, "InputHandler_DInput: DirectInputCreate") );

	LOG->Trace( "InputHandler_DInput: IDirectInput::EnumDevices(DIDEVTYPE_KEYBOARD)" );
	hr = g_dinput->EnumDevices( DI8DEVCLASS_KEYBOARD, EnumDevicesCallback, NULL, DIEDFL_ATTACHEDONLY );
	if( hr != DI_OK )
		RageException::Throw( hr_ssprintf(hr, "InputHandler_DInput: IDirectInput::EnumDevices") );

	LOG->Trace( "InputHandler_DInput: IDirectInput::EnumDevices(DIDEVTYPE_JOYSTICK)" );
	hr = g_dinput->EnumDevices( DI8DEVCLASS_GAMECTRL, EnumDevicesCallback, NULL, DIEDFL_ATTACHEDONLY );
	if( hr != DI_OK )
		RageException::Throw( hr_ssprintf(hr, "InputHandler_DInput: IDirectInput::EnumDevices") );

	// mouse
	LOG->Trace( "InputHandler_DInput: IDirectInput::EnumDevices(DIDEVTYPE_MOUSE)" );
	hr = g_dinput->EnumDevices( DI8DEVCLASS_POINTER, EnumDevicesCallback, NULL, DIEDFL_ATTACHEDONLY );
	if( hr != DI_OK )
		RageException::Throw( hr_ssprintf(hr, "InputHandler_DInput: IDirectInput::EnumDevices") );

	for( unsigned i = 0; i < Devices.size(); ++i )
	{
		if( Devices[i].Open() )
			continue;

		Devices.erase( Devices.begin() + i );
		i--;
		continue;
	}

	LOG->Info( "Found %u DirectInput devices:", Devices.size() );
	for( unsigned i = 0; i < Devices.size(); ++i )
	{
		LOG->Info( "   %d: '%s' axes: %d, hats: %d, buttons: %d (%s)",
			i,
			Devices[i].m_sName.c_str(),
			Devices[i].axes,
			Devices[i].hats,
			Devices[i].buttons,
			Devices[i].buffered? "buffered": "unbuffered" );
	}

	m_iLastSeenNumHidDevices = GetNumHidDevices();
	m_iNumTimesLeftToPollForJoysticksChanged = 0;
	m_iLastSeenNumJoysticks = GetNumJoysticksSlow();

	StartThread();
}

void InputHandler_DInput::StartThread()
{
	ASSERT( !m_InputThread.IsCreated() );
	if( PREFSMAN->m_bThreadedInput )
	{
		m_InputThread.SetName( "DirectInput thread" );
		m_InputThread.Create( InputThread_Start, this );
	}
}

void InputHandler_DInput::ShutdownThread()
{
	m_bShutdown = true;
	if( m_InputThread.IsCreated() )
	{
		LOG->Trace( "Shutting down DirectInput thread ..." );
		m_InputThread.Wait();
		LOG->Trace( "DirectInput thread shut down." );
	}
	m_bShutdown = false;
}

InputHandler_DInput::~InputHandler_DInput()
{
	ShutdownThread();

	for( unsigned i = 0; i < Devices.size(); ++i )
		Devices[i].Close();

	Devices.clear();
	g_dinput->Release();
	g_dinput = NULL;
}

void InputHandler_DInput::WindowReset()
{
	// We need to reopen keyboards.
	ShutdownThread();

	for( unsigned i = 0; i < Devices.size(); ++i )
	{
		if( Devices[i].type != Devices[i].KEYBOARD )
			continue;

		Devices[i].Close();

		// We lose buffered inputs here, so we need to clear all pressed keys.
		INPUTFILTER->ResetDevice( Devices[i].dev );

		bool ret = Devices[i].Open();

		// Reopening it should succeed.
		ASSERT( ret );
	}

	StartThread();
}

#define HAT_UP_MASK 1
#define HAT_DOWN_MASK 2
#define HAT_LEFT_MASK 4
#define HAT_RIGHT_MASK 8

static int TranslatePOV(DWORD value)
{
	const int HAT_VALS[] =
	{
		HAT_UP_MASK,
		HAT_UP_MASK   | HAT_RIGHT_MASK,
		HAT_RIGHT_MASK,
		HAT_DOWN_MASK | HAT_RIGHT_MASK,
		HAT_DOWN_MASK,
		HAT_DOWN_MASK | HAT_LEFT_MASK,
		HAT_LEFT_MASK,
		HAT_UP_MASK   | HAT_LEFT_MASK
	};

	if( LOWORD(value) == 0xFFFF )
		return 0;

	// Round the value up:
	value += 4500 / 2;
	value %= 36000;
	value /= 4500;

	if( value >= 8 )
		return 0; // shouldn't happen

	return HAT_VALS[value];
}

static HRESULT GetDeviceState( LPDIRECTINPUTDEVICE8 dev, int size, void *ptr )
{
	HRESULT hr = dev->GetDeviceState( size, ptr );
	if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
	{
		hr = dev->Acquire();
		if( hr != DI_OK )
		{
			LOG->Trace( hr_ssprintf(hr, "?") );
			return hr;
		}

		hr = dev->GetDeviceState( size, ptr );
	}

	return hr;
}

/* This doesn't take a timestamp; instead, we let InputHandler::ButtonPressed
 * figure it out. Be sure to call InputHandler::Update() between each poll. */
void InputHandler_DInput::UpdatePolled( DIDevice &device, const RageTimer &tm )
{
	switch( device.type )
	{
	default:
		FAIL_M(ssprintf("Unsupported DI device type: %i", device.type));
	case device.KEYBOARD:
		{
			unsigned char keys[256];

			HRESULT hr = GetDeviceState( device.Device, 256, keys );
			if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
				return;

			if( hr != DI_OK )
			{
				LOG->MapLog( "UpdatePolled", hr_ssprintf(hr, "Failures on polled keyboard update") );
				return;
			}

			for( int k = 0; k < 256; ++k )
			{
				const DeviceButton key = (DeviceButton) device.Inputs[k].num;
				ButtonPressed( DeviceInput(device.dev, key, !!(keys[k] & 0x80) ) );
			}
		}
		break;
	case device.JOYSTICK:
		{
			DIJOYSTATE state;

			HRESULT hr = GetDeviceState(device.Device, sizeof(state), &state);
			if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
				return;

			// Set each known axis, button and POV.
			for( unsigned i = 0; i < device.Inputs.size(); ++i )
			{
				const input_t &in = device.Inputs[i];
				const InputDevice dev = device.dev;

				switch(in.type)
				{
					case in.BUTTON:
					{
						DeviceInput di( dev, enum_add2(JOY_BUTTON_1, in.num), !!state.rgbButtons[in.ofs - DIJOFS_BUTTON0], tm );
						ButtonPressed( di );
						break;
					}

					case in.AXIS:
					{
						DeviceButton neg = DeviceButton_Invalid, pos = DeviceButton_Invalid;
						int val = 0;
						switch( in.ofs )
						{
							case DIJOFS_X:  neg = JOY_LEFT; pos = JOY_RIGHT;
											val = state.lX;
											break;
							case DIJOFS_Y:  neg = JOY_UP; pos = JOY_DOWN;
											val = state.lY;
											break;
							case DIJOFS_Z:  neg = JOY_Z_UP; pos = JOY_Z_DOWN;
											val = state.lZ;
											break;
							case DIJOFS_RX: neg = JOY_ROT_LEFT; pos = JOY_ROT_RIGHT;
											val = state.lRx;
											break;
							case DIJOFS_RY: neg = JOY_ROT_UP; pos = JOY_ROT_DOWN;
											val = state.lRy;
											break;
							case DIJOFS_RZ: neg = JOY_ROT_Z_UP; pos = JOY_ROT_Z_DOWN;
											val = state.lRz;
											break;
							case DIJOFS_SLIDER(0):
											neg = JOY_AUX_1; pos = JOY_AUX_2;
											val = state.rglSlider[0];
											break;
							case DIJOFS_SLIDER(1):
											neg = JOY_AUX_3; pos = JOY_AUX_4;
											val = state.rglSlider[1];
											break;
							default: LOG->MapLog( "unknown input", 
											"Controller '%s' is returning an unknown joystick offset, %i",
											device.m_sName.c_str(), in.ofs );
									 continue;
						}
						if( neg != DeviceButton_Invalid )
						{
							float l = SCALE( int(val), 0.0f, 100.0f, 0.0f, 1.0f );
							ButtonPressed( DeviceInput(dev, neg, max(-l,0), tm) );
							ButtonPressed( DeviceInput(dev, pos, max(+l,0), tm) );
						}

						break;
					}

					case in.HAT:
						if( in.num == 0 )
						{
							const int pos = TranslatePOV( state.rgdwPOV[in.ofs - DIJOFS_POV(0)] );
							ButtonPressed( DeviceInput(dev, JOY_HAT_UP, !!(pos & HAT_UP_MASK), tm) );
							ButtonPressed( DeviceInput(dev, JOY_HAT_DOWN, !!(pos & HAT_DOWN_MASK), tm) );
							ButtonPressed( DeviceInput(dev, JOY_HAT_LEFT, !!(pos & HAT_LEFT_MASK), tm) );
							ButtonPressed( DeviceInput(dev, JOY_HAT_RIGHT, !!(pos & HAT_RIGHT_MASK), tm) );
						}

						break;
				}
			}
		}
		break;
	case device.MOUSE:
		// Not 100% perfect (or tested much) yet. -aj
		DIMOUSESTATE state;

		HRESULT hr = GetDeviceState(device.Device, sizeof(state), &state);
		if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
			return;

		// reset mousewheel
		ButtonPressed( DeviceInput(device.dev, MOUSE_WHEELUP, 0, tm) );
		ButtonPressed( DeviceInput(device.dev, MOUSE_WHEELDOWN, 0, tm) );

		for( unsigned i = 0; i < device.Inputs.size(); ++i )
		{
			const input_t &in = device.Inputs[i];
			const InputDevice dev = device.dev;

			switch(in.type)
			{
				case in.BUTTON:
				{
					DeviceInput di( dev, enum_add2(MOUSE_LEFT, in.num), !!state.rgbButtons[in.ofs - DIMOFS_BUTTON0], tm );
					ButtonPressed( di );
					break;
				}
				case in.AXIS:
				{
					DeviceButton neg = DeviceButton_Invalid, pos = DeviceButton_Invalid;
					int val = 0;
					switch( in.ofs )
					{
						case DIMOFS_X:
							neg = MOUSE_X_LEFT; pos = MOUSE_X_RIGHT;
							val = state.lX;
							break;
						case DIMOFS_Y:
							neg = MOUSE_Y_UP; pos = MOUSE_Y_DOWN;
							val = state.lY;
							break;
						case DIMOFS_Z:
							{
								neg = MOUSE_WHEELDOWN; pos = MOUSE_WHEELUP;
								val = state.lZ;
								//LOG->Trace("MouseWheel polled: %i",val);
								INPUTFILTER->UpdateMouseWheel(val);
								if( val == 0 )
								{
									// release all
									ButtonPressed( DeviceInput(dev, pos, 0, tm) );
									ButtonPressed( DeviceInput(dev, neg, 0, tm) );
								}
								else if( int(val) > 0 )
								{
									// positive values: WheelUp
									ButtonPressed( DeviceInput(dev, pos, 1, tm) );
									ButtonPressed( DeviceInput(dev, neg, 0, tm) );
								}
								else if( int(val) < 0 )
								{
									// negative values: WheelDown
									ButtonPressed( DeviceInput(dev, neg, 1, tm) );
									ButtonPressed( DeviceInput(dev, pos, 0, tm) );
								}
							}
							break;
						default: LOG->MapLog( "unknown input", 
											"Mouse '%s' is returning an unknown mouse offset, %i",
											device.m_sName.c_str(), in.ofs );
									 	continue;
					}
					break;
				}
			}
		}
		break;
	}
}

void InputHandler_DInput::UpdateBuffered( DIDevice &device, const RageTimer &tm )
{
	DWORD numevents;
	DIDEVICEOBJECTDATA evtbuf[INPUT_QSIZE];

	numevents = INPUT_QSIZE;
	HRESULT hr = device.Device->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), evtbuf, &numevents, 0 );
	if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
	{
		INPUTFILTER->ResetDevice( device.dev );
		return;
	}

	if( hr != DI_OK )
	{
		LOG->Trace( hr_ssprintf(hr, "UpdateBuffered: IDirectInputDevice2_GetDeviceData") );
		return;
	}

	if( GetForegroundWindow() != GraphicsWindow::GetHwnd() )
	{
		// Discard input when not focused, and release all keys.
		INPUTFILTER->ResetDevice( device.dev );
		return;
	}

	// reset mousewheel
	DeviceInput diUp = DeviceInput(device.dev, MOUSE_WHEELUP, 0.0f, tm);
	ButtonPressed( diUp );
	DeviceInput diDown = DeviceInput(device.dev, MOUSE_WHEELDOWN, 0.0f, tm);
	ButtonPressed( diDown );

	for( int i = 0; i < (int) numevents; ++i )
	{
		for(unsigned j = 0; j < device.Inputs.size(); ++j)
		{
			const input_t &in = device.Inputs[j];
			const InputDevice dev = device.dev;

			if( evtbuf[i].dwOfs != in.ofs )
				continue;

			switch( in.type )
			{
				case in.KEY:
					ButtonPressed( DeviceInput(dev, (DeviceButton) in.num, !!(evtbuf[i].dwData & 0x80), tm) );
					break;

				case in.BUTTON:
					if(dev == DEVICE_MOUSE)
					{
						DeviceButton mouseInput = DeviceButton_Invalid;
						switch(in.ofs)
						{
							case DIMOFS_BUTTON0: mouseInput = MOUSE_LEFT; break;
							case DIMOFS_BUTTON1: mouseInput = MOUSE_RIGHT; break;
							case DIMOFS_BUTTON2: mouseInput = MOUSE_MIDDLE; break;
							default: LOG->MapLog( "unknown input", 
								 "Mouse '%s' is returning an unknown mouse offset [button], %i",
								 device.m_sName.c_str(), in.ofs );
								continue;
						}
						ButtonPressed( DeviceInput(dev, mouseInput, !!evtbuf[i].dwData, tm) );
					}
					else
						ButtonPressed( DeviceInput(dev, enum_add2(JOY_BUTTON_1, in.num), !!evtbuf[i].dwData, tm) );
					break;

				case in.AXIS:
				{
					DeviceButton up = DeviceButton_Invalid, down = DeviceButton_Invalid;
					if(dev == DEVICE_MOUSE)
					{
						float l = int(evtbuf[i].dwData);
						POINT cursorPos;
						GetCursorPos(&cursorPos);
						// convert screen coordinates to client
						ScreenToClient(GraphicsWindow::GetHwnd(), &cursorPos);

						switch(in.ofs)
						{
							case DIMOFS_X:
								INPUTFILTER->UpdateCursorLocation((float)cursorPos.x,(float)cursorPos.y);
								break;
							case DIMOFS_Y:
								INPUTFILTER->UpdateCursorLocation((float)cursorPos.x,(float)cursorPos.y);
								break;
							case DIMOFS_Z:
								// positive values: WheelUp
								// negative values: WheelDown
								INPUTFILTER->UpdateMouseWheel(l);
								{
									up = MOUSE_WHEELUP; down = MOUSE_WHEELDOWN;
									float fWheelDelta = l;
									//l = SCALE( int(evtbuf[i].dwData), -WHEEL_DELTA, WHEEL_DELTA, 1.0f, -1.0f );
									if( l > 0 )
									{
										DeviceInput diUp = DeviceInput(dev, up, 1.0f, tm);
										DeviceInput diDown = DeviceInput(dev, down, 0.0f, tm);
										while( fWheelDelta >= WHEEL_DELTA )
										{
											ButtonPressed( diUp );
											ButtonPressed( diDown );
											INPUTFILTER->UpdateMouseWheel(fWheelDelta);
											fWheelDelta -= WHEEL_DELTA;
										}
									}
									else if( l < 0 )
									{
										DeviceInput diDown = DeviceInput(dev, down, 1.0f, tm);
										DeviceInput diUp = DeviceInput(dev, up, 0.0f, tm);
										while( fWheelDelta <= -WHEEL_DELTA )
										{
											ButtonPressed( diDown );
											ButtonPressed( diUp );
											INPUTFILTER->UpdateMouseWheel(fWheelDelta);
											fWheelDelta += WHEEL_DELTA;
										}
									}
									else
									{
										DeviceInput diUp = DeviceInput(dev, up, 0.0f, tm);
										ButtonPressed( diUp );
										DeviceInput diDown = DeviceInput(dev, down, 0.0f, tm);
										ButtonPressed( diDown );
									}
								}
								break;
							default: LOG->MapLog( "unknown input", 
										 "Mouse '%s' is returning an unknown mouse offset [axis], %i",
										 device.m_sName.c_str(), in.ofs );
								continue;
						}
					}
					else
					{
						switch(in.ofs)
						{
							// joystick
							case DIJOFS_X:  up = JOY_LEFT; down = JOY_RIGHT; break;
							case DIJOFS_Y:  up = JOY_UP; down = JOY_DOWN; break;
							case DIJOFS_Z:  up = JOY_Z_UP; down = JOY_Z_DOWN; break;
							case DIJOFS_RX: up = JOY_ROT_UP; down = JOY_ROT_DOWN; break;
							case DIJOFS_RY: up = JOY_ROT_LEFT; down = JOY_ROT_RIGHT; break;
							case DIJOFS_RZ: up = JOY_ROT_Z_UP; down = JOY_ROT_Z_DOWN; break;
							case DIJOFS_SLIDER(0): up = JOY_AUX_1; down = JOY_AUX_2; break;
							case DIJOFS_SLIDER(1): up = JOY_AUX_3; down = JOY_AUX_4; break;
							default: LOG->MapLog( "unknown input", 
										 "Controller '%s' is returning an unknown joystick offset, %i",
										 device.m_sName.c_str(), in.ofs );
								continue;
						}
						float l = SCALE( int(evtbuf[i].dwData), 0.0f, 100.0f, 0.0f, 1.0f );
						ButtonPressed( DeviceInput(dev, up, max(-l,0), tm) );
						ButtonPressed( DeviceInput(dev, down, max(+l,0), tm) );
					}
					break;
				}

				case in.HAT:
				{
					const int pos = TranslatePOV( evtbuf[i].dwData );
					ButtonPressed( DeviceInput(dev, JOY_HAT_UP, !!(pos & HAT_UP_MASK), tm) );
					ButtonPressed( DeviceInput(dev, JOY_HAT_DOWN, !!(pos & HAT_DOWN_MASK), tm) );
					ButtonPressed( DeviceInput(dev, JOY_HAT_LEFT, !!(pos & HAT_LEFT_MASK), tm) );
					ButtonPressed( DeviceInput(dev, JOY_HAT_RIGHT, !!(pos & HAT_RIGHT_MASK), tm) );
				}
			}
		}
	}
}


void InputHandler_DInput::PollAndAcquireDevices( bool bBuffered )
{
	for( unsigned i = 0; i < Devices.size(); ++i )
	{
		if( Devices[i].buffered != bBuffered )
			continue;

		HRESULT hr = Devices[i].Device->Poll();
		if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
		{
			INPUTFILTER->ResetDevice( Devices[i].dev );

			/* This will fail with "access denied" on the keyboard if we don't
			 * have focus. */
			hr = Devices[i].Device->Acquire();
			if( hr != DI_OK )
				continue;

			Devices[i].Device->Poll();
		}
	}
}

void InputHandler_DInput::Update()
{
	// Handle polled devices. Handle buffered, too, if there's no input thread to do it.
	PollAndAcquireDevices( false );
	if( !m_InputThread.IsCreated() )
		PollAndAcquireDevices( true );

	for( unsigned i = 0; i < Devices.size(); ++i )
	{
		if( !Devices[i].buffered )
		{
			UpdatePolled( Devices[i], RageZeroTimer );
		}
		else if( !m_InputThread.IsCreated() )
		{
			// If we have an input thread, it'll handle buffered devices.
			UpdateBuffered( Devices[i], RageZeroTimer );
		}
	}

	InputHandler::UpdateTimer();
}

const float POLL_FOR_JOYSTICK_CHANGES_LENGTH_SECONDS = 15.0f;
const float POLL_FOR_JOYSTICK_CHANGES_EVERY_SECONDS = 0.25f;

bool InputHandler_DInput::DevicesChanged()
{
	/* GetNumJoysticksSlow() blocks DirectInput for a while even if called from a
	 * different thread, so we can't poll with it. GetNumHidDevices() is fast,
	 * but sometimes the DirectInput joysticks haven't updated by  the time the
	 * HID registry value changes. So, poll using GetNumHidDevices(). When that
	 * changes, poll using GetNumJoysticksSlow() for a little while to give
	 * DirectInput time to catch up. On this XP machine (which? -aj), it takes
	 * 2-10 DirectInput polls (0.5-2.5 seconds) to catch a newly installed device
	 * after the registry value changes, and catches non-new plugged/unplugged
	 * devices on the first DirectInputPoll. Note that this "poll for N seconds"
	 * method will not work if the Add New Hardware wizard halts device
	 * installation to wait for a driver. Most of the joysticks people would want
	 * to use don't prompt for a driver though, and the wizard adds them pretty quickly. */

	int iOldNumHidDevices = m_iLastSeenNumHidDevices;
	m_iLastSeenNumHidDevices = GetNumHidDevices();
	if( iOldNumHidDevices != m_iLastSeenNumHidDevices )
	{
		LOG->Warn( "HID devices changes" );
		m_iNumTimesLeftToPollForJoysticksChanged = (int)(POLL_FOR_JOYSTICK_CHANGES_LENGTH_SECONDS / POLL_FOR_JOYSTICK_CHANGES_EVERY_SECONDS);
	}

	if( m_iNumTimesLeftToPollForJoysticksChanged > 0 )
	{
		static RageTimer timerPollJoysticks;
		if( timerPollJoysticks.Ago() >= POLL_FOR_JOYSTICK_CHANGES_EVERY_SECONDS )
		{
			m_iNumTimesLeftToPollForJoysticksChanged--;
			timerPollJoysticks.Touch();
			LOG->Warn( "polling for joystick changes" );

			int iOldNumJoysticks = m_iLastSeenNumJoysticks;
			m_iLastSeenNumJoysticks = GetNumJoysticksSlow();
			if( iOldNumJoysticks != m_iLastSeenNumJoysticks )
			{
				LOG->Warn( "joysticks changed" );
				m_iNumTimesLeftToPollForJoysticksChanged = 0;
				return true;
			}
		}
	}

	return false;
}

void InputHandler_DInput::InputThreadMain()
{
	if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST))
		LOG->Warn(werr_ssprintf(GetLastError(), "Failed to set DirectInput thread priority"));

	// Enable priority boosting.
	SetThreadPriorityBoost( GetCurrentThread(), FALSE );

	vector<DIDevice*> BufferedDevices;
	HANDLE Handle = CreateEvent( NULL, FALSE, FALSE, NULL );
	for( unsigned i = 0; i < Devices.size(); ++i )
	{
		if( !Devices[i].buffered )
			continue;

		BufferedDevices.push_back( &Devices[i] );

		Devices[i].Device->Unacquire();
		HRESULT hr = Devices[i].Device->SetEventNotification( Handle );
		if( FAILED(hr) )
			LOG->Warn( "IDirectInputDevice2_SetEventNotification failed on %i", i );
		Devices[i].Device->Acquire();
	}

	while( !m_bShutdown )
	{
		CHECKPOINT;
		if( BufferedDevices.size() )
		{
			// Update buffered devices.
			PollAndAcquireDevices( true );

			int ret = WaitForSingleObjectEx( Handle, 50, true );
			if( ret == -1 )
			{
				LOG->Trace( werr_ssprintf(GetLastError(), "WaitForSingleObjectEx failed") );
				continue;
			}

			/* Update devices even if no event was triggered, since this also
			 * checks for focus loss. */
			RageTimer now;
			for( unsigned i = 0; i < BufferedDevices.size(); ++i )
				UpdateBuffered( *BufferedDevices[i], now );
		}
		CHECKPOINT;

		// If we have no buffered devices, we didn't delay at WaitForMultipleObjectsEx.
		if( BufferedDevices.size() == 0 )
			usleep( 50000 );
		CHECKPOINT;
	}
	CHECKPOINT;

	for( unsigned i = 0; i < Devices.size(); ++i )
	{
		if( !Devices[i].buffered )
			continue;

		Devices[i].Device->Unacquire();
		Devices[i].Device->SetEventNotification( NULL );
	}

	CloseHandle(Handle);
}

void InputHandler_DInput::GetDevicesAndDescriptions( vector<InputDeviceInfo>& vDevicesOut )
{
	for( unsigned i=0; i < Devices.size(); ++i )
		vDevicesOut.push_back( InputDeviceInfo(Devices[i].dev, Devices[i].m_sName) );
}

static wchar_t ScancodeAndKeysToChar( DWORD scancode, unsigned char keys[256] )
{
	static HKL layout = GetKeyboardLayout(0); // 0 == current thread
	UINT vk = MapVirtualKeyEx( scancode, 1, layout );

	static bool bInitialized = false;

	typedef int (WINAPI TOUNICODEEX)( IN UINT wVirtKey, IN UINT wScanCode, IN CONST BYTE *lpKeyState, OUT LPWSTR pwszBuff, IN int cchBuff, IN UINT wFlags, IN HKL dwhkl );
	static TOUNICODEEX *pToUnicodeEx;

	if( !bInitialized )
	{
		bInitialized = true;
		HMODULE hModule = GetModuleHandle( "user32.dll" );
		pToUnicodeEx = (TOUNICODEEX *) GetProcAddress( hModule, "ToUnicodeEx" );
	}

	unsigned short result[2]; // ToAscii writes a max of 2 chars
	ZERO( result );

	if( pToUnicodeEx != NULL )
	{
		int iNum = pToUnicodeEx( vk, scancode, keys, (LPWSTR)result, 2, 0, layout );
		if( iNum == 1 )
			return result[0];
	}
	else
	{
		int iNum = ToAsciiEx( vk, scancode, keys, result, 0, layout );
		// iNum == 2 will happen only for dead keys. See MSDN for ToAsciiEx.
		if( iNum == 1 )
		{
			RString s = RString()+(char)result[0];
			return ConvertCodepageToWString( s, CP_ACP )[0];
		}
	}

	return '\0';
}

wchar_t InputHandler_DInput::DeviceButtonToChar( DeviceButton button, bool bUseCurrentKeyModifiers )
{
	// ToAsciiEx maps these keys to a character. They shouldn't be mapped to any character.
	switch( button )
	{
	case KEY_ESC:
	case KEY_TAB:
	case KEY_ENTER:
	case KEY_BACK:
		return '\0';
	}

	FOREACH_CONST( DIDevice, Devices, d )
	{
		if( d->type != DIDevice::KEYBOARD )
			continue;

		FOREACH_CONST( input_t, d->Inputs, i )
		{
			if( button != i->num )
				continue;

			unsigned char keys[256];
			ZERO( keys );
			if( bUseCurrentKeyModifiers )
				GetKeyboardState(keys);
			// todo: handle Caps Lock -freem
			wchar_t c = ScancodeAndKeysToChar( i->ofs, keys );
			if( c )
				return c;
		}
	}

	return InputHandler::DeviceButtonToChar( button, bUseCurrentKeyModifiers );
}

/*
 * (c) 2003-2004 Glenn Maynard
 * All rights reserved.
 * 
 * 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, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * 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 OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
