#include "Mix/Private/HID/RawInputManager.h"
#include "Mix/Private/HID/Keyboard.h"
#include "Mix/Private/HID/Mouse.h"

#include <list>

using namespace Mix::HID;

//KEYBOARD UsagePage 0x01 Usage 0x06
//MOUSE    UsagePage 0x01 Usage 0x02
//GAMEPAD  UsagePage 0x01 Usage 0x05
//JOYSTICK UsagePage 0x01 Usage 0x04

const wchar_t* RawInputManager::FAILED_INITIALIZE = L"インプットマネージャの初期化に失敗";

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::HID::RawInputManager
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

RawInputManager::RawInputManager( void ) :
m_hWnd( NULL ),
m_pBuffer( NULL ),
m_BufferSize( 0 ),
m_pKeyboard( NULL ),
m_pMouse( NULL )
{
}

RawInputManager::~RawInputManager( void )
{
}

Boolean RawInputManager::Start( HWND hWnd, UInt32 flags, Mix::UserFile* pSysReport )
{
	RAWINPUTDEVICE registDeviceArray[2];
	UInt32 registDeviceCount;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 設定
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_hWnd = hWnd;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 初期化
	////////////////////////////////////////////////////////////////////////////////////////////////////

	registDeviceCount = 0;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// システムレポート
	////////////////////////////////////////////////////////////////////////////////////////////////////

	{
		RAWINPUTDEVICELIST* devices = NULL;
		RID_DEVICE_INFO devInfo;
		UInt32 numDevice = 0;
		UInt32 devSize = 0;
		Mix::StringW temp;

		std::list<Mix::StringW> keyboardList;
		std::list<Mix::StringW> mouseList;

		//デバイスリストを作成
		::GetRawInputDeviceList( NULL, &numDevice, sizeof( RAWINPUTDEVICELIST ) );
		devices = reinterpret_cast<RAWINPUTDEVICELIST*>( ::HeapAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof( RAWINPUTDEVICELIST ) * numDevice ) );
		if( devices == NULL )
		{
			return False;
		}
		::GetRawInputDeviceList( devices, &numDevice, sizeof( RAWINPUTDEVICELIST ) );

		//各デバイスを準備
		for( UInt32 i = 0; i < numDevice; i++ )
		{
			wchar_t id[MAX_PATH] = L"";
			Mix::StringW productName = L"";

			//デバイス情報を取得
			devInfo.cbSize = sizeof( RID_DEVICE_INFO );
			devSize = devInfo.cbSize;
			if( GetRawInputDeviceInfo( devices[i].hDevice, RIDI_DEVICEINFO, &devInfo, &devSize ) <= 0 )
			{
				continue;
			}

			//キーネームを取得
			::ZeroMemory( id, sizeof( id ) );
			devSize = sizeof( id );
			if( GetRawInputDeviceInfo( devices[i].hDevice, RIDI_DEVICENAME, id, &devSize ) <= 0 )
			{
				continue;
			}

			switch( devices[i].dwType )
			{
			case RIM_TYPEKEYBOARD:
				//キーボード( GUID=884b96c3-56ef-11d1-bc8c-00a0c91405dd Class="Keyboard" )
				//devInfo.keyboard.dwType dwSubType
				//0x00000051 ?????????? 仮想デバイス
				//0x00000004 0x00000000 101キーボード
				if( GetDeviceDesc( L"Keyboard", id, productName ) == True )
				{
					temp.Sprintf( L"  [%s]\n    ファンクションキーの数 : %d\n    キーの総数 : %d", productName.GetConstPtr(), devInfo.keyboard.dwNumberOfFunctionKeys, devInfo.keyboard.dwNumberOfKeysTotal );
					keyboardList.push_back( temp );
				}
				break;

			case RIM_TYPEMOUSE:
				//マウス( GUID=378de44c-56ef-11d1-bc8c-00a0c91405dd Class="Mouse" )
				//devInfo.mouse.dwID
				//0x00000002 標準システムデバイス( ターミナルサーバーマウスドライバ )
				//0x00000020 PS/2互換マウス
				//0x00000100 HIDに準拠したマウス
				if( GetDeviceDesc( L"Mouse", id, productName ) == True )
				{
					temp.Sprintf( L"  [%s]\n    ボタンの総数 : %d", productName.GetConstPtr(), devInfo.mouse.dwNumberOfButtons );
					mouseList.push_back( temp );
				}
				break;
			}
		}

		//デバイスリストを解放
		::HeapFree( ::GetProcessHeap(), 0, devices );

		pSysReport->WriteLine( L"[キーボード一覧]" );
		if( keyboardList.size() > 0 )
		{
			for( std::list<Mix::StringW>::iterator it = keyboardList.begin(); it != keyboardList.end(); ++it )
			{
				pSysReport->WriteLine( L"%s", ( *it ).GetConstPtr() );
			}
		}
		else
		{
			pSysReport->WriteLine( L"  ありません" );
		}
		pSysReport->WriteLine( L"" );

		pSysReport->WriteLine( L"[マウス一覧]" );
		if( mouseList.size() > 0 )
		{
			for( std::list<Mix::StringW>::iterator it = mouseList.begin(); it != mouseList.end(); ++it )
			{
				pSysReport->WriteLine( L"%s", ( *it ).GetConstPtr() );
			}
		}
		else
		{
			pSysReport->WriteLine( L"  ありません" );
		}
		pSysReport->WriteLine( L"" );
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// キーボードを作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( MIX_TESTBIT( flags, Mix::EC_KEYBOARD ) == Mix::EC_KEYBOARD )
	{
		registDeviceArray[registDeviceCount].usUsagePage = 0x01;
		registDeviceArray[registDeviceCount].usUsage = 0x06;
		registDeviceArray[registDeviceCount].dwFlags = 0;
		registDeviceArray[registDeviceCount].hwndTarget = hWnd;

		m_pKeyboard = Mix::HID::Keyboard::CreateInstance( m_hWnd );
		if( m_pKeyboard == NULL )
		{
			MIX_LOG_ERROR( L"%s : %s", FAILED_INITIALIZE, Mix::STR_OUTOFMEMORY );
			return False;
		}

		registDeviceCount++;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// マウスを作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( MIX_TESTBIT( flags, Mix::EC_MOUSE ) == Mix::EC_MOUSE )
	{
		registDeviceArray[registDeviceCount].usUsagePage = 0x01;
		registDeviceArray[registDeviceCount].usUsage = 0x02;
		registDeviceArray[registDeviceCount].dwFlags = 0;
		registDeviceArray[registDeviceCount].hwndTarget = hWnd;

		m_pMouse = Mix::HID::Mouse::CreateInstance( m_hWnd );
		if( m_pMouse == NULL )
		{
			MIX_LOG_ERROR( L"%s : %s", FAILED_INITIALIZE, Mix::STR_OUTOFMEMORY );
			return False;
		}

		registDeviceCount++;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 各デバイスを使用できるようにする
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( registDeviceCount > 0 )
	{
		if( ::RegisterRawInputDevices( &( registDeviceArray[0] ), registDeviceCount, sizeof( RAWINPUTDEVICE ) ) == FALSE )
		{
			MIX_LOG_ERROR( L"%s : RegisterRawInputDevices %s", FAILED_INITIALIZE, Mix::STR_RETERROR );
			return False;
		}
	}

	return True;
}

void RawInputManager::Terminate( void )
{
	//バッファを解放
	if( m_pBuffer != NULL )
	{
		MIX_LIB_FREE( m_pBuffer );
		m_pBuffer = NULL;
		m_BufferSize = 0;
	}

	//キーボード解放
	MIX_RELEASE( m_pKeyboard );

	//マウス解放
	MIX_RELEASE( m_pMouse );
}

void RawInputManager::Update( void )
{
	if( m_pMouse != NULL )
	{
		m_pMouse->Update();
	}

	if( m_pKeyboard != NULL )
	{
		m_pKeyboard->Update();
	}
}

void RawInputManager::MessageProc( UInt32 msg, WPARAM wParam, LPARAM lParam )
{
	if( msg == WM_INPUT )
	{
		UInt32 dataSize = 0;
		RAWINPUT* pInput = NULL;
		HRAWINPUT hInput = reinterpret_cast<HRAWINPUT>( lParam );

		//入力データのサイズを取得
		::GetRawInputData( hInput, RID_INPUT, NULL, &dataSize, sizeof( RAWINPUTHEADER ) );
		if( dataSize == 0 )
		{
			return;
		}

		//入力データのメモリを確保
		if( m_BufferSize == 0 )
		{
			m_pBuffer = static_cast<UInt8*>( MIX_LIB_MALLOC( Mix::Memory::SECTION_GENERAL, dataSize ) );
			if( m_pBuffer != NULL )
			{
				m_BufferSize = dataSize;
			}
		}
		else if( dataSize > m_BufferSize )
		{
			UInt8* temp = reinterpret_cast<UInt8*>( MIX_LIB_REALLOC( m_pBuffer, dataSize ) );
			if( temp != NULL )
			{
				m_pBuffer = temp;
				m_BufferSize = dataSize;
			}
			else
			{
				MIX_LIB_FREE( m_pBuffer );
				m_pBuffer = NULL;
				m_BufferSize = 0;
			}
		}
		if( m_pBuffer == NULL )
		{
			return;
		}

		//入力データを取得
		if( ::GetRawInputData( hInput, RID_INPUT, m_pBuffer, &m_BufferSize, sizeof( RAWINPUTHEADER ) ) != dataSize )
		{
			return;
		}
		pInput = reinterpret_cast<RAWINPUT*>( &( m_pBuffer[0] ) );

		//入力データを処理する
		if( ( m_pMouse != NULL ) &&
			( pInput->header.dwType == RIM_TYPEMOUSE ) )
		{
			m_pMouse->ProcessInput( pInput );
		}
		else if(	( m_pKeyboard != NULL ) &&
					( pInput->header.dwType == RIM_TYPEKEYBOARD ) )
		{
			m_pKeyboard->ProcessInput( pInput );
		}
	}
	else
	{
		if( m_pMouse != NULL )
		{
			m_pMouse->MessageProc( msg, wParam, lParam );
		}

		if(	m_pKeyboard != NULL )
		{
			m_pKeyboard->MessageProc( msg, wParam, lParam );
		}
	}
}

Boolean RawInputManager::GetKeyboard( Mix::HID::IKeyboard** ppKeyboard )
{
	if( m_pKeyboard == NULL )
	{
		return False;
	}

	( *ppKeyboard ) = m_pKeyboard;
	( *ppKeyboard )->AddRef();

	return True;
}

Boolean RawInputManager::GetMouse( Mix::HID::IMouse** ppMouse )
{
	if( m_pMouse == NULL )
	{
		return False;
	}

	( *ppMouse ) = m_pMouse;
	( *ppMouse )->AddRef();

	return True;
}

Boolean RawInputManager::GetDeviceDesc( const TCHAR* pclassName, const TCHAR* pKeyName, Mix::StringW& name )
{
	Int32 i = 0;
	UInt32 step = 0;
	Boolean bEnd = False;
	HKEY hKey = NULL;
	DWORD dwSize = 0;
	Boolean bRet = False;
	const wchar_t* pk = &( pKeyName[4] );
	wchar_t pathArray[3][MAX_PATH];
	wchar_t regKey[MAX_PATH];
	wchar_t className[MAX_PATH];
	wchar_t temp[2014];
	Int32 tempLast;

	::ZeroMemory( pathArray, sizeof( pathArray ) );
	::ZeroMemory( className, sizeof( className ) );

	//クラス、サブクラス、プロトコルを取得
	while( step < 3 )
	{
		i = 0;
		while( pk[0] != L'#' )
		{
			pathArray[step][i++] = *pk++;
		}
		step++;
		pk++;
	}

	//レジストリキーを作成
	::wsprintf( regKey, L"SYSTEM\\CurrentControlSet\\Enum\\%s\\%s\\%s", pathArray[0], pathArray[1], pathArray[2] );

	//レジストリを開く
	if( ::RegOpenKeyEx( HKEY_LOCAL_MACHINE, regKey, 0, KEY_READ, &hKey ) == ERROR_SUCCESS )
	{
		//クラス名を取得し比較
		dwSize = ( sizeof( className ) >> 1 );
		if( ( ::RegQueryValueEx( hKey, L"Class", NULL, NULL, reinterpret_cast<LPBYTE>( &( className[0] ) ), &dwSize ) == ERROR_SUCCESS ) &&
			( ::wcscmp( pclassName, className ) == 0 ) )
		{
			//デバイス名を取得
			dwSize = sizeof( temp );
			if( ::RegQueryValueEx( hKey, L"DeviceDesc", NULL, NULL, reinterpret_cast<LPBYTE>( &( temp[0] ) ), &dwSize ) == ERROR_SUCCESS )
			{
				//%はsprintf時にまずいことになるので排除
				//ついでに@も
				for( i = 0; temp[i] != L'\0'; i++ )
				{
					if( ( temp[i] == L'@' ) ||
						( temp[i] == L'%' ) )
					{
						temp[i] = L'_';
					}
				}

				//Windows Vista 7 だと余分なものが付いてくるの排除
				//最後のセミコロンのあとがデバイス名のようです

				tempLast = static_cast<Int32>( ::wcslen( temp ) - 1 );

				for( i = tempLast; i >= 0; i-- )
				{
					if( temp[i] == L';' )
					{
						break;
					}
				}

				name = &( temp[i + 1] );

				bRet = True;
			}
		}

		::RegCloseKey( hKey );
	}

	return bRet;
}

