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

using namespace Mix::HID;

Mouse* Mouse::CreateInstance( HWND hWnd )
{
	return MIX_LIB_NEW_T( Mix::Memory::SECTION_GENERAL, Mouse, hWnd );
}

Mouse::Mouse( HWND hWnd ) :
m_hWnd( hWnd ),
m_ButtonStateBitmap( 0 ),
m_NextWheelDelta( 0 ),
m_WheelDelta( 0 ),
m_bInside( False ),
m_bProcEnabled( True )
{
	m_ButtonStateTable[0][0] = RI_MOUSE_BUTTON_1_UP;
	m_ButtonStateTable[0][1] = RI_MOUSE_BUTTON_1_DOWN;
	m_ButtonStateTable[1][0] = RI_MOUSE_BUTTON_2_UP;
	m_ButtonStateTable[1][1] = RI_MOUSE_BUTTON_2_DOWN;
	m_ButtonStateTable[2][0] = RI_MOUSE_BUTTON_3_UP;
	m_ButtonStateTable[2][1] = RI_MOUSE_BUTTON_3_DOWN;
	m_ButtonStateTable[3][0] = RI_MOUSE_BUTTON_4_UP;
	m_ButtonStateTable[3][1] = RI_MOUSE_BUTTON_4_DOWN;
	m_ButtonStateTable[4][0] = RI_MOUSE_BUTTON_5_UP;
	m_ButtonStateTable[4][1] = RI_MOUSE_BUTTON_5_DOWN;

	m_ButtonStateList[0] = 0;
	m_ButtonStateList[1] = 0;
	m_ButtonStateList[2] = 0;
	m_ButtonStateList[3] = 0;
	m_ButtonStateList[4] = 0;

	ResetPos();
}

Mouse::~Mouse( void )
{
}

void Mouse::ResetPos( void )
{
	RECT scrClientRect;
	POINT screenPos;
	POINT clientPos;
	POINT tmp;

	//////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// スクリーン上のクライアント矩形
	//////////////////////////////////////////////////////////////////////////////////////////////////////////////

	::GetClientRect( m_hWnd, &scrClientRect );

	tmp.x = scrClientRect.left;
	tmp.y = scrClientRect.top;
	::ClientToScreen( m_hWnd, &tmp );
	scrClientRect.left = tmp.x;
	scrClientRect.top = tmp.y;

	tmp.x = scrClientRect.right;
	tmp.y = scrClientRect.bottom;
	::ClientToScreen( m_hWnd, &tmp );
	scrClientRect.right = tmp.x;
	scrClientRect.bottom = tmp.y;

	//////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// スクリーン上のマウスカーソルの位置
	//////////////////////////////////////////////////////////////////////////////////////////////////////////////

	::GetCursorPos( &screenPos );

	//////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// マウスカーソルの ON OFF
	//////////////////////////////////////////////////////////////////////////////////////////////////////////////

	if( ( scrClientRect.left  >  screenPos.x ) || ( scrClientRect.top    >  screenPos.y ) ||
		( scrClientRect.right <= screenPos.x ) || ( scrClientRect.bottom <= screenPos.y ) )
	{
		if( m_bInside == True )
		{
			m_bInside = False;
			SetCursorEnabled( TRUE );
		}
	}
	else
	{
		if( m_bInside == False )
		{
			m_bInside = True;
			SetCursorEnabled( FALSE );
		}
	}

	//////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// パラメータの更新
	//////////////////////////////////////////////////////////////////////////////////////////////////////////////

	clientPos = screenPos;
	::ScreenToClient( m_hWnd, &clientPos );

	m_RelativePos.x = clientPos.x;
	m_RelativePos.y = clientPos.y;

	if( m_bInside == True )
	{
		m_Pos.x = clientPos.x;
		m_Pos.y = clientPos.y;
	}

	m_ScreenPos.x = screenPos.x;
	m_ScreenPos.y = screenPos.y;

	m_NextVelocity = Mix::Point::Zero();
	m_Velocity = Mix::Point::Zero();
}

void Mouse::SetCursorEnabled( Boolean state )
{
	CURSORINFO info;

	Mix::Memory::Zero( &info, sizeof( CURSORINFO ) );
	info.cbSize = sizeof( CURSORINFO );

	if( ::GetCursorInfo( &info ) == TRUE )
	{
		if( ( state == True ) && ( MIX_TESTBIT( info.flags, CURSOR_SHOWING ) != CURSOR_SHOWING ) )
		{
			::ShowCursor( TRUE );
		}
		else if( ( state == False ) && ( MIX_TESTBIT( info.flags, CURSOR_SHOWING ) == CURSOR_SHOWING ) )
		{
			::ShowCursor( False );
		}
	}
}

void Mouse::ProcessInput( const RAWINPUT* pInput )
{
	if( m_bProcEnabled == False )
	{
		//処理が無効にされている
		return;
	}

	const RAWMOUSE* pm = &( pInput->data.mouse );	//マウス入力情報
	Boolean bSupport = True; //サポートしている場合は True

	if( ( pm->usFlags & MOUSE_VIRTUAL_DESKTOP ) == MOUSE_VIRTUAL_DESKTOP )
	{
		if( ( pm->usFlags & MOUSE_MOVE_ABSOLUTE ) == MOUSE_MOVE_ABSOLUTE )
		{
			//仮想デスクトップ上の絶対座標( タブレットなど )
			ProcPos_VA( pm );
		}
		else
		{
			//仮想デスクトップ上の相対座標( 未サポート )
			bSupport = False;
		}
	}
	else
	{
		if( ( pm->usFlags & MOUSE_MOVE_ABSOLUTE ) == MOUSE_MOVE_ABSOLUTE )
		{
			//デスクトップ上の絶対座標( 未サポート )
			bSupport = False;
		}
		else
		{
			//デスクトップ上の相対座標( マウスなど )
			ProcPos_DR( pm );
		}
	}

	if( bSupport == True )
	{
		ProcBtn( pm );
	}
}

void Mouse::ProcPos_VA( const RAWMOUSE* pm )
{
	Float32 rw = static_cast<Float32>( pm->lLastX ) / 65535.0f;
	Float32 rh = static_cast<Float32>( pm->lLastY ) / 65535.0f;
	RECT clientRect;
	POINT screenSize;
	POINT screenPos;
	POINT clientPos;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// クライアント矩形( ローカル )
	////////////////////////////////////////////////////////////////////////////////////////////////////

	::GetClientRect( m_hWnd, &clientRect );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// スクリーンサイズ
	////////////////////////////////////////////////////////////////////////////////////////////////////

	screenSize.x = ::GetSystemMetrics( SM_CXSCREEN );
	screenSize.y = ::GetSystemMetrics( SM_CYSCREEN );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// マウスカーソルの位置
	////////////////////////////////////////////////////////////////////////////////////////////////////

	// スクリーン //

	screenPos.x = static_cast<Int32>( static_cast<Float32>( screenSize.x ) * rw );
	screenPos.y = static_cast<Int32>( static_cast<Float32>( screenSize.y ) * rh );

	// クライアント //

	clientPos = screenPos;
	::ScreenToClient( m_hWnd, &clientPos );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	//　Windows のマウスカーソルの制御
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( ( clientRect.left  >  clientPos.x ) || ( clientRect.top    >  clientPos.y ) ||
		( clientRect.right <= clientPos.x ) || ( clientRect.bottom <= clientPos.y ) )
	{
		//外側
		if( m_bInside == True )
		{
			//内側→外側
			m_bInside = False;
			SetCursorEnabled( True );
		}
	}
	else
	{
		//内側
		::SetCursorPos( screenPos.x, screenPos.y );

		if( m_bInside == False )
		{
			//外側→内側
			m_bInside = True;
			SetCursorEnabled( False );
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// パラメータの更新
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_ScreenPos.x = screenPos.x;
	m_ScreenPos.y = screenPos.y;

	m_RelativePos.x = clientPos.x;
	m_RelativePos.y = clientPos.y;

	if( m_bInside == True )
	{
		m_NextVelocity += ( m_RelativePos - m_Pos );
		m_Pos = m_RelativePos;
	}
	else
	{
		m_NextVelocity = Mix::Vector2::Zero();
	}
}

void Mouse::ProcPos_DR( const RAWMOUSE* pm )
{
	RECT clientRect;
	RECT scrClientRect;
	POINT screenSize;
	POINT clientPos;
	POINT temp;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// クライアント矩形( ローカル )
	////////////////////////////////////////////////////////////////////////////////////////////////////

	::GetClientRect( m_hWnd, &clientRect );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// クライアント矩形( スクリーン )
	////////////////////////////////////////////////////////////////////////////////////////////////////

	temp.x = clientRect.left; 
	temp.y = clientRect.top;
	::ClientToScreen( m_hWnd, &temp );
	scrClientRect.left = temp.x;
	scrClientRect.top = temp.y;

	temp.x = clientRect.right;
	temp.y = clientRect.bottom;
	::ClientToScreen( m_hWnd, &temp );
	scrClientRect.right = temp.x;
	scrClientRect.bottom = temp.y;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// スクリーンサイズ
	////////////////////////////////////////////////////////////////////////////////////////////////////

	screenSize.x = ::GetSystemMetrics( SM_CXSCREEN );
	screenSize.y = ::GetSystemMetrics( SM_CYSCREEN );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// マウスカーソルの位置( スクリーン )
	////////////////////////////////////////////////////////////////////////////////////////////////////

	// スクリーン //

	m_ScreenPos.x += pm->lLastX;
	m_ScreenPos.y += pm->lLastY;

	if( ( scrClientRect.left > m_ScreenPos.x ) || ( scrClientRect.right <= m_ScreenPos.x ) ||
		( scrClientRect.top > m_ScreenPos.y ) || ( scrClientRect.bottom <= m_ScreenPos.y ) )
	{
		::GetCursorPos( &temp );

		m_ScreenPos.x = temp.x;
		m_ScreenPos.y = temp.y;
	}

	// クライアント //

	clientPos.x = m_ScreenPos.x;
	clientPos.y = m_ScreenPos.y;

	::ScreenToClient( m_hWnd, &clientPos );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// Windows のマウスカーソルの制御
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( ( clientRect.left  >  clientPos.x ) || ( clientRect.top    >  clientPos.y ) ||
		( clientRect.right <= clientPos.x ) || ( clientRect.bottom <= clientPos.y ) )
	{
		//外側
		if( m_bInside == True )
		{
			//内側→外側
			m_bInside = False;
			SetCursorEnabled( True );
		}
	}
	else
	{
		//内側
		::SetCursorPos( m_ScreenPos.x, m_ScreenPos.y );

		if( m_bInside == False )
		{
			//外側→内側
			m_bInside = True;
			SetCursorEnabled( False );
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// パラメータの更新
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_RelativePos.x = clientPos.x;
	m_RelativePos.y = clientPos.y;

	if( m_bInside == True )
	{
		m_NextVelocity += ( m_RelativePos - m_Pos );
		m_Pos = m_RelativePos;
	}
	else
	{
		m_NextVelocity = Mix::Vector2::Zero();
	}
}

void Mouse::ProcBtn( const RAWMOUSE* pm )
{
	UInt32 i;
	UInt8 mask;

	for( i = 0; i < Mouse::MAX_BUTTON; i++ )
	{
		mask = ( 1 << i );

		if( MIX_TESTBIT( pm->usButtonFlags, m_ButtonStateTable[i][0] ) == m_ButtonStateTable[i][0] )
		{
			if( MIX_TESTBIT( m_ButtonStateBitmap, mask ) == mask )
			{
				//アップ
				m_ButtonStateBitmap ^= mask;
			}
		}
		else if( MIX_TESTBIT( pm->usButtonFlags, m_ButtonStateTable[i][1] ) == m_ButtonStateTable[i][1] )
		{
			if( ( m_bInside == True ) &&
				( MIX_TESTBIT( m_ButtonStateBitmap, mask ) == 0 ) )
			{
				//ダウン
				m_ButtonStateBitmap |= mask;
			}
		}
	}

	//ホイール
	if( ( pm->usButtonFlags & RI_MOUSE_WHEEL ) == RI_MOUSE_WHEEL )
	{
		if( pm->usButtonData & 0xFF00 )
		{
			m_NextWheelDelta = ( 0xFFFF0000 | static_cast<Int32>( pm->usButtonData ) );
		}
		else
		{
			m_NextWheelDelta = static_cast<Int32>( pm->usButtonData );
		}
	}
}

void Mouse::MessageProc( UInt32 msg, WPARAM wParam, LPARAM lParam )
{
	switch( msg )
	{
	//ウィンドウを移動中
	case WM_MOVING:
		m_bProcEnabled = False;
		break;

	//ウィンドウのサイズを変更中
	case WM_SIZING:
		m_bProcEnabled = False;
		break;

	//ウィンドウのサイズ変更が終了
	case WM_EXITSIZEMOVE:
		ResetPos();
		m_bProcEnabled = True;
		break;

	//メニューを表示した
	case WM_INITMENU:
		SetCursorEnabled( True );
		m_bProcEnabled = False;
		break;

	//メニューの選択を終了した
	case WM_EXITMENULOOP:
		SetCursorEnabled( False );
		ResetPos();
		m_bProcEnabled = True;
		break;

	//ウィンドウが復帰した
	case WM_SYSCOMMAND:
		if( ( wParam & 0xFFF0 ) == SC_RESTORE )
		{
			SetCursorEnabled( False );
			ResetPos();
			m_bProcEnabled = True;
		}
		break;

	//ウィンドウの状態が変化した
	case WM_ACTIVATE:
		//Windows Vista or 7 だと WM_ACTIVATEAPP では GetClientRect が正常に動作しない
		//XPは大丈夫
		if( ( wParam == WA_ACTIVE ) ||
			( wParam == WA_CLICKACTIVE ) )
		{
			//アクティブ
			ResetPos();
			m_bProcEnabled = True;
			SetCursorEnabled( False );
		}
		else
		{
			//非アクティブ
			for( UInt32 i = 0; i < MAX_BUTTON; i++ )
			{
				UInt32 mask = ( 1 < i );
				if( MIX_TESTBIT( m_ButtonStateBitmap, mask ) == mask )
				{
					m_ButtonStateBitmap ^= mask;
				}
			}

			m_bProcEnabled = False;
			SetCursorEnabled( True );
		}
		break;
	};
}

void Mouse::Update( void )
{
	UInt32 i;
	UInt32 mask;

	m_Velocity = m_NextVelocity;
	m_NextVelocity = Mix::Point::Zero();

	m_WheelDelta = m_NextWheelDelta;
	m_NextWheelDelta = 0;

	for( i = 0; i < Mouse::MAX_BUTTON; i++ )
	{
		mask = ( 1 << i );

		//リリース、プレスを解除する
		MIX_RESETBIT( m_ButtonStateList[i], Mix::HID::RELEASED );
		MIX_RESETBIT( m_ButtonStateList[i], Mix::HID::PRESSED );

		if( MIX_TESTBIT( m_ButtonStateBitmap, mask ) == 0 )
		{
			//アップ
			if( MIX_TESTBIT( m_ButtonStateList[i], Mix::HID::DOWN ) == Mix::HID::DOWN )
			{
				MIX_RESETBIT( m_ButtonStateList[i], Mix::HID::DOWN );
				MIX_RESETBIT( m_ButtonStateList[i], Mix::HID::PRESSED );

				MIX_SETBIT( m_ButtonStateList[i], Mix::HID::RELEASED );

//				MIX_TRACELINE( L"Button[%d] : Up", i );
			}
		}
		else
		{
			//ダウン
			if( m_ButtonStateList[i] == 0 )
			{
				MIX_RESETBIT( m_ButtonStateList[i], Mix::HID::RELEASED );

				MIX_SETBIT( m_ButtonStateList[i], Mix::HID::PRESSED );
				MIX_SETBIT( m_ButtonStateList[i], Mix::HID::DOWN );

//				MIX_TRACELINE( L"Button[%d] : Down", i );
			}
		}
	}
}

UInt32 Mouse::GetButtonState( UInt32 button )
{
	if( MAX_BUTTON <= button )
	{
		return 0;
	}

	return m_ButtonStateList[button];
}

Int32 Mouse::GetWheelDelta( void )
{
	return m_WheelDelta;
}

void Mouse::SetPos( Int32 x, Int32 y )
{
	SetPos( Mix::Point( x, y ) );
}

void Mouse::SetPos( const Mix::Point& pos )
{
	RECT clientRect;
	POINT clientPos;

	//クライアント矩形
	::GetClientRect( m_hWnd, &clientRect );

	//クライアント座標
	clientPos.x = pos.x;
	clientPos.y = pos.y;

	//クライアント座標のクリッピング
	if( clientRect.left > clientPos.x )
	{
		clientPos.x = clientRect.left;
	}
	else if( clientRect.right <= clientPos.x )
	{
		clientPos.x = ( clientRect.right - 1 );
	}

	if( clientRect.top > clientPos.y )
	{
		clientPos.y = clientRect.top;
	}
	else if( clientRect.bottom <= clientPos.y )
	{
		clientPos.y = ( clientRect.bottom - 1 );
	}

	//相対座標
	m_RelativePos.x = clientPos.x;
	m_RelativePos.y = clientPos.y;

	//座標
	m_Pos = m_RelativePos;

	//スクリーン座標
	::ClientToScreen( m_hWnd, &clientPos );
	m_ScreenPos.x = clientPos.x;
	m_ScreenPos.y = clientPos.y;

	//速度
	m_NextVelocity = Mix::Point::Zero();
	m_Velocity = Mix::Point::Zero();

	//マウスカーソルの位置
	::SetCursorPos( m_ScreenPos.x, m_ScreenPos.y );

	//マウスカーソルのON,OFF
	if( m_bInside == False )
	{
		SetCursorEnabled( False );
		m_bInside = True;
	}
}

const Mix::Point& Mouse::GetVelocity( void ) const
{
	return m_Velocity;
}

const Mix::Point& Mouse::GetPos( void ) const
{
	return m_Pos;
}

const Mix::Point& Mouse::GetScreenPos( void ) const
{
	return m_ScreenPos;
}
