#include "global.h"
#include "InputHandler_DirectInputHelper.h"
#include "RageUtil.h"
#include "RageLog.h"
#include "archutils/Win32/DirectXHelpers.h"
#include "archutils/Win32/ErrorStrings.h"
#include "archutils/Win32/GraphicsWindow.h"

#if defined(_MSC_VER)
#pragma comment(lib, "dinput8.lib")
#if defined(_WINDOWS)
#pragma comment(lib, "dxguid.lib")
#endif
#endif
LPDIRECTINPUT8 g_dinput = NULL;

static int ConvertScancodeToKey( int scancode );
static BOOL CALLBACK DIJoystick_EnumDevObjectsProc(LPCDIDEVICEOBJECTINSTANCE dev, LPVOID data);
static BOOL CALLBACK DIMouse_EnumDevObjectsProc(LPCDIDEVICEOBJECTINSTANCE dev, LPVOID data);

DIDevice::DIDevice()
{
	buttons = axes = hats = 0;
	dev = InputDevice_Invalid;
	buffered = true;
	memset(&JoystickInst, 0, sizeof(JoystickInst));
	Device = NULL;
}

bool DIDevice::Open()
{
	m_sName = ConvertACPToUTF8( JoystickInst.tszProductName );

	LOG->Trace( "Opening device '%s'", m_sName.c_str() );
	buffered = true;

	LPDIRECTINPUTDEVICE8 tmpdevice;

	// load joystick
	HRESULT hr = g_dinput->CreateDevice( JoystickInst.guidInstance, &tmpdevice, NULL );
	if ( hr != DI_OK )
	{
		LOG->Info( hr_ssprintf(hr, "OpenDevice: IDirectInput_CreateDevice") );
		return false;
	}
	hr = tmpdevice->QueryInterface( IID_IDirectInputDevice8, (LPVOID *) &Device );
	tmpdevice->Release();
	if ( hr != DI_OK )
	{
		LOG->Info( hr_ssprintf(hr, "OpenDevice(%s): IDirectInputDevice::QueryInterface", m_sName.c_str()) );
		return false;
	}

	int coop = DISCL_NONEXCLUSIVE | DISCL_BACKGROUND;
	if( type == KEYBOARD )
		coop = DISCL_NONEXCLUSIVE | DISCL_FOREGROUND;

	hr = Device->SetCooperativeLevel( GraphicsWindow::GetHwnd(), coop );
	if ( hr != DI_OK )
	{
		LOG->Info( hr_ssprintf(hr, "OpenDevice(%s): IDirectInputDevice2::SetCooperativeLevel", m_sName.c_str()) );
		return false;
	}

	switch(type)
	{
		case KEYBOARD:
			hr = Device->SetDataFormat( &c_dfDIKeyboard );
			break;
		case JOYSTICK:
			hr = Device->SetDataFormat( &c_dfDIJoystick );
			break;
		case MOUSE:
			hr = Device->SetDataFormat( &c_dfDIMouse );
			break;
	}
	if ( hr != DI_OK )
	{
		LOG->Info( hr_ssprintf(hr, "OpenDevice(%s): IDirectInputDevice2::SetDataFormat", m_sName.c_str()) );
		return false;
	}

	switch( type )
	{
		case JOYSTICK:
			Device->EnumObjects( DIJoystick_EnumDevObjectsProc, this, DIDFT_BUTTON | DIDFT_AXIS | DIDFT_POV);
			break;
		case KEYBOARD:
			// Always 256-button.
			for( int b = 0; b < 256; ++b )
			{
				input_t in;
				in.type = in.KEY;

				in.num = ConvertScancodeToKey(b);
				in.ofs = b;
				buttons++;
				Inputs.push_back(in);
			}
			break;
		case MOUSE:
			Device->EnumObjects( DIMouse_EnumDevObjectsProc, this, DIDFT_BUTTON | DIDFT_AXIS | DIDFT_ANYINSTANCE );
			break;
	}

	{
		DIPROPDWORD dipdw;
		memset(&dipdw, 0, sizeof(dipdw));
		dipdw.diph.dwSize = sizeof(dipdw);
		dipdw.diph.dwHeaderSize = sizeof(dipdw.diph);
		dipdw.diph.dwObj = 0;
		dipdw.diph.dwHow = DIPH_DEVICE;
		dipdw.dwData = INPUT_QSIZE;
		hr = Device->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph );
		if ( hr == DI_POLLEDDEVICE )
		{
			/* This device doesn't support buffering, so we're forced
			 * to use less reliable polling. */
			buffered = false;
		}
		else if ( hr != DI_OK )
		{
			LOG->Info( hr_ssprintf(hr, "OpenDevice(%s): IDirectInputDevice2::SetProperty", m_sName.c_str()) );
			return false;
		}
	}

	return true;
}

void DIDevice::Close()
{
	// Don't try to close a device that isn't open.
	ASSERT( Device != NULL );

	Device->Unacquire();
	Device->Release();

	Device = NULL;
	buttons = axes = hats = 0;
	Inputs.clear();
}

static BOOL CALLBACK DIJoystick_EnumDevObjectsProc(LPCDIDEVICEOBJECTINSTANCE dev, LPVOID data)
{
	DIDevice *device = (DIDevice *) data;
	HRESULT hr;

	input_t in;
	const int SupportedMask = DIDFT_BUTTON | DIDFT_POV | DIDFT_AXIS;
	if(!(dev->dwType & SupportedMask))
		return DIENUM_CONTINUE; // unsupported

	if(dev->dwType & DIDFT_BUTTON) {
		if( device->buttons == 24 )
			return DIENUM_CONTINUE; // too many buttons

		in.ofs = DIJOFS_BUTTON(device->buttons);
		in.type = in.BUTTON;
		in.num = device->buttons;
	} else if(dev->dwType & DIDFT_POV) {
		in.ofs = DIJOFS_POV(device->hats);
		in.type = in.HAT;
		in.num = device->hats;
	} else { // dev->dwType & DIDFT_AXIS
		DIPROPRANGE diprg;
		DIPROPDWORD dilong;

		// Figure out which axis we're looking at and store the correct DIJOFS_* value.
		if( dev->guidType == GUID_XAxis )
		{
			in.ofs = DIJOFS_X;
		}
		else if( dev->guidType == GUID_YAxis )
		{
			in.ofs = DIJOFS_Y;
		}
		else if( dev->guidType == GUID_ZAxis )
		{
			in.ofs = DIJOFS_Z;
		}
		else if( dev->guidType == GUID_RxAxis )
		{
			in.ofs = DIJOFS_RX;
		}
		else if( dev->guidType == GUID_RyAxis )
		{
			in.ofs = DIJOFS_RY;
		}
		else if( dev->guidType == GUID_RzAxis )
		{
			in.ofs = DIJOFS_RZ;
		}
		else
		{
			// No idea what this is so ignore it.
			return DIENUM_CONTINUE;
		}

		in.type = in.AXIS;
		in.num = device->axes;

		diprg.diph.dwSize		= sizeof(diprg);
		diprg.diph.dwHeaderSize	= sizeof(diprg.diph);
		diprg.diph.dwObj		= dev->dwType;
		diprg.diph.dwHow		= DIPH_BYID;
		diprg.lMin				= -100;
		diprg.lMax				= 100;

		hr = device->Device->SetProperty( DIPROP_RANGE, &diprg.diph );
		if ( hr != DI_OK )
			return DIENUM_CONTINUE; // don't use this axis

		// Set dead zone to 0.
		dilong.diph.dwSize		= sizeof(dilong);
		dilong.diph.dwHeaderSize	= sizeof(dilong.diph);
		dilong.diph.dwObj		= dev->dwType;
		dilong.diph.dwHow		= DIPH_BYID;
		dilong.dwData = 0;
		hr = device->Device->SetProperty( DIPROP_DEADZONE, &dilong.diph );
		if ( hr != DI_OK )
			return DIENUM_CONTINUE; // don't use this axis
	}

	/* For some devices it's possible to find multiple objects of the same type. When trying to
	 * figure out which device caused an event it doesn't matter which one it finds so there's
	 * no need to include a duplicate if one has already been added.
	 */
	if( std::find_if( device->Inputs.begin(), device->Inputs.end(), input_t::Compare( in.ofs ) ) == device->Inputs.end() )
	{
		device->Inputs.push_back(in);

		// Increment the number of buttons/axes/hats only after it has been added.
		switch ( in.type )
		{
			case input_t::BUTTON:
				++device->buttons;
				break;

			case input_t::AXIS:
				++device->axes;
				break;

			case input_t::HAT:
				++device->hats;
				break;
		}
	}

	return DIENUM_CONTINUE;
}

static int ConvertScancodeToKey( int scancode )
{
	switch(scancode)
	{
	case DIK_ESCAPE: return KEY_ESC;
	case DIK_1: return KEY_C1;
	case DIK_2: return KEY_C2;
	case DIK_3: return KEY_C3;
	case DIK_4: return KEY_C4;
	case DIK_5: return KEY_C5;
	case DIK_6: return KEY_C6;
	case DIK_7: return KEY_C7;
	case DIK_8: return KEY_C8;
	case DIK_9: return KEY_C9;
	case DIK_0: return KEY_C0;
	case DIK_MINUS: return KEY_HYPHEN;
	case DIK_EQUALS: return KEY_EQUAL;
	case DIK_BACK: return KEY_BACK;
	case DIK_TAB: return KEY_TAB;
	case DIK_Q: return KEY_Cq;
	case DIK_W: return KEY_Cw;
	case DIK_E: return KEY_Ce;
	case DIK_R: return KEY_Cr;
	case DIK_T: return KEY_Ct;
	case DIK_Y: return KEY_Cy;
	case DIK_U: return KEY_Cu;
	case DIK_I: return KEY_Ci;
	case DIK_O: return KEY_Co;
	case DIK_P: return KEY_Cp;
	case DIK_LBRACKET: return KEY_LBRACKET;
	case DIK_RBRACKET: return KEY_RBRACKET;
	case DIK_RETURN: return KEY_ENTER;
	case DIK_LCONTROL: return KEY_LCTRL;
	case DIK_A: return KEY_Ca;
	case DIK_S: return KEY_Cs;
	case DIK_D: return KEY_Cd;
	case DIK_F: return KEY_Cf;
	case DIK_G: return KEY_Cg;
	case DIK_H: return KEY_Ch;
	case DIK_J: return KEY_Cj;
	case DIK_K: return KEY_Ck;
	case DIK_L: return KEY_Cl;
	case DIK_SEMICOLON: return KEY_SEMICOLON;
	case DIK_APOSTROPHE: return KEY_SQUOTE;
	case DIK_GRAVE: return KEY_ACCENT;
	case DIK_LSHIFT: return KEY_LSHIFT;
	case DIK_BACKSLASH: return KEY_BACKSLASH;
	case DIK_OEM_102: return KEY_BACKSLASH;
	case DIK_Z: return KEY_Cz;
	case DIK_X: return KEY_Cx;
	case DIK_C: return KEY_Cc;
	case DIK_V: return KEY_Cv;
	case DIK_B: return KEY_Cb;
	case DIK_N: return KEY_Cn;
	case DIK_M: return KEY_Cm;
	case DIK_COMMA: return KEY_COMMA;
	case DIK_PERIOD: return KEY_PERIOD;
	case DIK_SLASH: return KEY_SLASH;
	case DIK_RSHIFT: return KEY_RSHIFT;
	case DIK_MULTIPLY: return KEY_KP_ASTERISK;
	case DIK_LMENU: return KEY_LALT;
	case DIK_SPACE: return KEY_SPACE;
	case DIK_CAPITAL: return KEY_CAPSLOCK;
	case DIK_F1: return KEY_F1;
	case DIK_F2: return KEY_F2;
	case DIK_F3: return KEY_F3;
	case DIK_F4: return KEY_F4;
	case DIK_F5: return KEY_F5;
	case DIK_F6: return KEY_F6;
	case DIK_F7: return KEY_F7;
	case DIK_F8: return KEY_F8;
	case DIK_F9: return KEY_F9;
	case DIK_F10: return KEY_F10;
	case DIK_NUMLOCK: return KEY_NUMLOCK;
	case DIK_SCROLL: return KEY_SCRLLOCK;
	case DIK_NUMPAD7: return KEY_KP_C7;
	case DIK_NUMPAD8: return KEY_KP_C8;
	case DIK_NUMPAD9: return KEY_KP_C9;
	case DIK_SUBTRACT: return KEY_KP_HYPHEN;
	case DIK_NUMPAD4: return KEY_KP_C4;
	case DIK_NUMPAD5: return KEY_KP_C5;
	case DIK_NUMPAD6: return KEY_KP_C6;
	case DIK_ADD: return KEY_KP_PLUS;
	case DIK_NUMPAD1: return KEY_KP_C1;
	case DIK_NUMPAD2: return KEY_KP_C2;
	case DIK_NUMPAD3: return KEY_KP_C3;
	case DIK_NUMPAD0: return KEY_KP_C0;
	case DIK_DECIMAL: return KEY_KP_PERIOD;
	case DIK_F11: return KEY_F11;
	case DIK_F12: return KEY_F12;

	case DIK_F13: return KEY_F13;
	case DIK_F14: return KEY_F14;
	case DIK_F15: return KEY_F15;

	case DIK_NUMPADEQUALS: return KEY_KP_EQUAL;
	case DIK_NUMPADENTER: return KEY_KP_ENTER;
	case DIK_RCONTROL: return KEY_RCTRL;
	case DIK_DIVIDE: return KEY_KP_SLASH;
	case DIK_SYSRQ: return KEY_PRTSC;
	case DIK_RMENU: return KEY_RALT;
	case DIK_PAUSE: return KEY_PAUSE;
	case DIK_HOME: return KEY_HOME;
	case DIK_UP: return KEY_UP;
	case DIK_PRIOR: return KEY_PGUP;
	case DIK_LEFT: return KEY_LEFT;
	case DIK_RIGHT: return KEY_RIGHT;
	case DIK_END: return KEY_END;
	case DIK_DOWN: return KEY_DOWN;
	case DIK_NEXT: return KEY_PGDN;
	case DIK_INSERT: return KEY_INSERT;
	case DIK_DELETE: return KEY_DEL;
	case DIK_LWIN: return KEY_LMETA;
	case DIK_RWIN: return KEY_RMETA;
	case DIK_APPS: return KEY_MENU;
	default: return '?';
	};
}

static BOOL CALLBACK DIMouse_EnumDevObjectsProc(LPCDIDEVICEOBJECTINSTANCE dev, LPVOID data)
{
	DIDevice *device = (DIDevice *) data;
	//HRESULT hr;

	input_t in;
	const int SupportedMask = DIDFT_BUTTON | DIDFT_AXIS | DIDFT_ANYINSTANCE;
	if(!(dev->dwType & SupportedMask))
		return DIENUM_CONTINUE; // unsupported

	in.ofs = dev->dwOfs;

	if(dev->dwType & DIDFT_BUTTON)
	{
		in.type = in.BUTTON;
		in.num = device->buttons;
		device->buttons++;
	}
	else if(dev->dwType & DIDFT_AXIS)
	{
		// todo: how to handle this correctly? -aj
		in.type = in.AXIS;
		in.num = device->axes;

		device->axes++;
	}

	device->Inputs.push_back(in);

	return DIENUM_CONTINUE;
}

/*
 * (c) 2003-2011 Glenn Maynard, AJ Kelly
 * 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.
 */
