#include "stdafx.h"
#include "refCInputKeyboard.h"
#include "refDeviceConstantConverter.h"
#include "refCInput管理.h"

namespace FDK
{
	CInputKeyboard::CInputKeyboard( IntPtr hWnd, IDirectInput8 *pDI )
	{
		HRESULT hr = S_OK;

		this->Disposed = gcnew CDisposed();
		this->pDevice = NULL;
		this->e入力デバイス種別 = E入力デバイス種別::キーボード;
		this->guid = nullptr;
		this->ID = 0;
		this->strデバイス名 = "Keyboard";

		try
		{
			// 配列確保。

			this->bKeyPullUp = gcnew array<bool>( 256 );
			this->bKeyPushDown = gcnew array<bool>( 256 );
			this->bKeyState = gcnew array<bool>( 256 );


			// キー状態配列を初期化する。

			for( int i = 0; i < this->bKeyState->Length; i++ )
				this->bKeyState[ i ] = false;


			// キーボードデバイスを生成する。

			#pragma region [ *** ]
			//-----------------
			IDirectInputDevice8 *pdev;
			tFAILEDなら例外発生( L"キーボードデバイスの生成",
				hr = pDI->CreateDevice( GUID_SysKeyboard, &pdev, NULL ) );
			this->pDevice = pdev;

			tFAILEDなら例外発生( L"キーボードデバイスの協調レベルの設定",
				hr = this->pDevice->SetCooperativeLevel( (HWND) (void*) hWnd, DISCL_NOWINKEY | DISCL_FOREGROUND | DISCL_NONEXCLUSIVE ) );

			tFAILEDなら例外発生( L"キーボードデバイスのフォーマットの設定",
				hr = this->pDevice->SetDataFormat( &c_dfDIKeyboard ) );

			DIPROPDWORD dipwd;
			dipwd.diph.dwSize = sizeof( DIPROPDWORD );
			dipwd.diph.dwHeaderSize = sizeof( DIPROPHEADER );
			dipwd.diph.dwObj = 0;
			dipwd.diph.dwHow = DIPH_DEVICE;
			dipwd.dwData = 32;

			tFAILEDなら例外発生( L"キーボードデバイスのバッファサイズの設定",
				hr = this->pDevice->SetProperty( DIPROP_BUFFERSIZE, &dipwd.diph ) );
			//-----------------
			#pragma endregion


			// タイマーを生成する。

			this->timer = gcnew CTimer( CTimer::E種別::MultiMedia );
		}
		catch( Exception^ e原因 )
		{
			#pragma region [ 失敗 ]
			//-----------------
			SAFE_REF_DELETE( this->bKeyPullUp );
			SAFE_REF_DELETE( this->bKeyPushDown );
			SAFE_REF_DELETE( this->bKeyState );
			SAFE_REF_DELETE( this->timer );
			SAFE_COM_RELEASE( this->pDevice );

			throw gcnew Exception( L"キーボードデバイスの生成に失敗しました。", e原因 );
			//-----------------
			#pragma endregion
		}
	}
	CInputKeyboard::~CInputKeyboard()
	{
		if( this->Disposed->bDispose済みまたは処理中である )
			return;		// 例外は発出しない

		if( !this->Disposed->t開始を宣言する() )
			return;		// 例外は発出しない

		SAFE_REF_DELETE( this->bKeyPullUp );
		SAFE_REF_DELETE( this->bKeyPushDown );
		SAFE_REF_DELETE( this->bKeyState );
		SAFE_REF_DELETE( this->timer );
		SAFE_REF_DELETE( this->_guid );				// bDisposed チェックをしているメンバを呼び出さないように注意。
		SAFE_REF_DELETE( this->_strデバイス名 );	//

		if( nullptr != this->_list入力イベント )
		{
			for each( CInputEvent^ ev in this->_list入力イベント )
				delete ev;		// Dispose

			this->_list入力イベント->Clear();
			SAFE_REF_DELETE( this->_list入力イベント );
		}

		// ファイナライザを呼び出して Unmanaged リソースを解放したのち、ファイナライザの呼び出しを抑制する。
		this->!CInputKeyboard();

		//GC::SuppressFinalize( this );   --> コンパイラが勝手にやる
	}
	CInputKeyboard::!CInputKeyboard()
	{
		// Unmanaged リソースを解放する。ディスポーザが呼び出されていればファイナライザは呼び出されないようコンパイラが調整する。
		SAFE_COM_RELEASE( this->pDevice );
	}

	void CInputKeyboard::tポーリング( bool bWindowがアクティブ中, bool bバッファ入力を使用する )
	{
		this->Disposed->tDispose済みまたは実行中なら例外発生( "CInputKeyboard" );

		HRESULT hr = S_OK;


		// 状態バッファをクリア。

		for( int i = 0; i < 256; i++ )
		{
			this->bKeyPushDown[ i ] = false;
			this->bKeyPullUp[ i ] = false;
		}


		// 取得＆ポーリング＆入力イベントリストを作成。

		if( bWindowがアクティブ中 && 
			this->pDevice != NULL &&
			SUCCEEDED( this->pDevice->Acquire() ) && 
			SUCCEEDED( this->pDevice->Poll() ) )
		{
			this->list入力イベント = gcnew List<CInputEvent^>( 32 );

			if( bバッファ入力を使用する )
			{
				#pragma region [ (A) バッファ入力 ]
				//-----------------------------
				DIDEVICEOBJECTDATA bufferedData[32];
				DWORD dwItems = 32;
				hr = this->pDevice->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), bufferedData, &dwItems, 0 );
				if( FAILED( hr ) || dwItems == 0 )
					return;

				for( DWORD i = 0; i < dwItems; i++ )
				{
					if( bufferedData[ i ].dwData != 0 )
					{
						#pragma region [ a. 押された ]
						//-----------------
						Key key = DeviceConstantConverter::DIKtoKey( bufferedData[ i ].dwOfs );
						if( key == Key::Unknown )
							continue;

						CInputEvent^ ev = gcnew CInputEvent();
						ev->guid = this->guid;
						ev->ID = this->ID;
						ev->nKey = (int) key;
						ev->b押された = true;
						ev->nTimeStamp = bufferedData[ i ].dwTimeStamp;
						ev->nVelocity = CInput管理::n通常音量;
						this->list入力イベント->Add( ev );
						this->bKeyState[ (int) key ] = true;
						this->bKeyPushDown[ (int) key ] = true;
						//-----------------
						#pragma endregion
					}
					else
					{
						#pragma region [ b. 離された ]
						//-----------------
						Key key = DeviceConstantConverter::DIKtoKey( bufferedData[ i ].dwOfs );
						if( key == Key::Unknown )
							continue;

						CInputEvent^ ev = gcnew CInputEvent();
						ev->guid = this->guid;
						ev->ID = this->ID;
						ev->nKey = (int) key;
						ev->b押された = false;
						ev->nTimeStamp = bufferedData[ i ].dwTimeStamp;
						ev->nVelocity = 0;
						this->list入力イベント->Add( ev );
						this->bKeyState[ (int) key ] = false;
						this->bKeyPullUp[ (int) key ] = true;
						//-----------------
						#pragma endregion
					}
				}
				//-----------------------------
				#pragma endregion
			}
			else
			{
				#pragma region [ (B) 状態入力 ]
				//-----------------
				BYTE keys[ 256 ];
				hr = this->pDevice->GetDeviceState( 256, keys );
				if( FAILED( hr ) )
					return;

				for( int i = 0; i < 256; i++ )
				{
					if( keys[ i ] != 0 )
					{
						if( this->bKeyState[ i ] == false )
						{
							#pragma region [ a. 押されている ＆ 前回は離されていた ]
							//-----------------
							CInputEvent^ ev = gcnew CInputEvent();
							ev->guid = this->guid;
							ev->ID = this->ID;
							ev->nKey = i;
							ev->b押された = true;
							ev->nTimeStamp = this->timer->nシステム時刻ms;
							ev->nVelocity = CInput管理::n通常音量;
							this->list入力イベント->Add( ev );
									
							this->bKeyState[ i ] = true;
							this->bKeyPushDown[ i ] = true;
							//-----------------
							#pragma endregion
						}
					}
					else
					{
						if( this->bKeyState[ i ] == true )
						{
							#pragma region [ b. 離されている ＆ 前回は押されていた ]
							//-----------------
							CInputEvent^ ev = gcnew CInputEvent();
							ev->guid = this->guid;
							ev->ID = this->ID;
							ev->nKey = i;
							ev->b押された = false;
							ev->nTimeStamp = this->timer->nシステム時刻ms;
							ev->nVelocity = 0;
							this->list入力イベント->Add( ev );
					
							this->bKeyState[ i ] = false;
							this->bKeyPullUp[ i ] = true;
							//-----------------
							#pragma endregion
						}
					}
				}
				//-----------------
				#pragma endregion
			}
		}
	}
}