/**
 * @file
 * @brief 入力（キーボード・マウス・ジョイスティック）の実装にかかわるクラス、定数定義などを行う.
 *
 * @author S.F.
 * @version $Id:
 *
 * Copyright (C) 2000-2002 Satoshi Fujiwara. All Rights Reserved.
 */

#pragma warning( disable : 4786 )	//STLの警告外し

// メモリーリーク検出用
#include "sfdebug.h"

#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT  0x0400
#include "math.h"
#include <list>
#include <queue>
#include <memory>
#include <map>

#include "windows.h"
#include "windowsx.h"
#include "d3d8.h"
#include "d3dx8.h"
#include "dmusici.h"
#include "dinput.h"
#include "dxerr8sf.h"

// PROJECT INCLUDES
//

#include "exception.h"
#include "resource.h"
#include "System.h"
#include "Console.h"
#include "Obj.h"
#include "singleton.h"
#include "AbstractSprite.h"
#include "ConsoleImpl.h"	
#include "SoundImpl.h"


#include "InputImpl.h"
#include "Main.h"
#include "SystemWin32.h"
#include "SystemImpl.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// 構築/消滅
//////////////////////////////////////////////////////////////////////

using namespace sf::system::input;

// デフォルトコンストラクタ ---------------------------------------------------
InputImpl::InputImpl()
{

}

// コンストラクタ -------------------------------------------------------------
InputImpl::InputImpl(HWND hwnd)
{
	mHWND = hwnd;
//	initialize();		
}

// デストラクタ ---------------------------------------------------------------
InputImpl::~InputImpl()
{
	uninitialize();
}// ~InputImpl()

// 初期化 ---------------------------------------------------------------------
void InputImpl::initialize(void)
{

	mpDI = NULL;
	mpJoystick = NULL;
	mpKeyboard = NULL;
	mbEnabled = false;
	
	mbLeft = false;
	mbRight = false;
	mbUp = false;
	mbDown = false;

	mbButtonA = false;
	mbButtonB = false;
	mbButtonC = false;
	mbButtonD = false;

	mbStart = false;
	mbExit = false;

	mbBeforeLeft = false;
	mbBeforeRight = false;
	mbBeforeUp = false;
	mbBeforeDown = false;

	mbBeforeButtonA = false;
	mbBeforeButtonB = false;
	mbBeforeButtonC = false;
	mbBeforeButtonD = false;

	mbBeforeStart = false;
	mbBeforeExit = false;
//	uninitialize();
	mbEnabled = false;
	mbMouseCursor = false;

	HRESULT hr;


    // DirectInput オブジェクトの生成
	if( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, 
                                         IID_IDirectInput8, (VOID**)&mpDI, NULL ) ) )
	{	std::string tmp("DirectInput8Create() Error : ");
		tmp += DXGetErrorString8(hr);
		tmp += " ";
		throw FatalErrorException(tmp,__FILE__,__LINE__);
	}

	// Joystickの初期化 
	try {
		initJoystick();
	} catch (RecoverbleErrorException e)
	{
		if(mpJoystick != NULL){
			mpJoystick->Release();
			mpJoystick = NULL;
		}
	} catch (...){
		throw;
	}


	// KeyBoardの初期化

	try {
		initKeyboard();
	} catch (RecoverbleErrorException e) {
		if(mpKeyboard != NULL)
		{
			mpKeyboard->Release();
			mpKeyboard = NULL;
		}

	} catch (...){
		throw;
	}

	// 入力デバイスが存在しない場合は、エラーで終了する。

	if(NULL == mpJoystick && NULL == mpKeyboard)
	{
		throw FatalErrorException("InputImpl Device Not Found\r\n",__FILE__,__LINE__);
	};

	addWindowMessageMap();

	mbEnabled = true;
}// intialize()

// 終了 -----------------------------------------------------------------------
void InputImpl::uninitialize(void)
{
	KeyboardListenerList::iterator it = mKeyboardListenerList.begin();
	
	while(it != mKeyboardListenerList.end())
	{
		if(*it != NULL){
			(*it)->keyboardTalkerIsDeleted();
//			++it;
		}
		it = mKeyboardListenerList.erase(it);
	}

	it = mKeyboardListenerQ.begin();
	
	while(it != mKeyboardListenerQ.end())
	{
		if(*it != NULL){
			(*it)->keyboardTalkerIsDeleted();

		}
		it = mKeyboardListenerQ.erase(it);
	}

	MouseListenerList::iterator itm = mMouseListenerList.begin();

	while(itm != mMouseListenerList.end())
	{
		if(*itm != NULL){
			(*itm)->mouseTalkerIsDeleted();
//			++itm;
		}
		itm = mMouseListenerList.erase(itm);
	}

	itm = mMouseListenerQ.begin();

	while(itm != mMouseListenerQ.end())
	{
		if(*itm != NULL){
			(*itm)->mouseTalkerIsDeleted();
//			++itm;
		}
		itm = mMouseListenerQ.erase(itm);
	}

	if(mpJoystick){
		mpJoystick->Release();
		mpJoystick = NULL;
	}

	if(mpKeyboard){
		mpKeyboard->Release();
		mpKeyboard = NULL;
	}
	
	if(mpDI){
		mpDI->Release();
		mpDI = NULL;
	}

}// uninitialize()

// ジョイスティックデバイス列挙用コールバック ---------------------------------
BOOL InputImpl::enumJoysticksCallback(const DIDEVICEINSTANCE *pdidInstance)
{
    HRESULT hr;

    // Obtain an interface to the enumerated joystick.
    hr = mpDI->CreateDevice( pdidInstance->guidInstance, &mpJoystick, NULL );

    // If it failed, then we can't use this joystick. (Maybe the user unplugged
    // it while we were in the middle of enumerating it.)
    if( FAILED(hr) ) 
        return DIENUM_CONTINUE;

    // Stop enumeration. Note: we're just taking the first joystick we get. You
    // could store all the enumerated joysticks and let the user pick.
    return DIENUM_STOP;
}


// ジョイスティックの初期化 ---------------------------------------------------
void InputImpl::initJoystick(void)
{

	HRESULT hr = E_FAIL;

	// DirectInputデバイスの列挙
    if( FAILED(hr = mpDI->EnumDevices( DI8DEVCLASS_GAMECTRL, 
                                         enumJoysticksCallback,
                                         (VOID*)this, DIEDFL_ATTACHEDONLY ) ) )
	{	
		throw RecoverbleErrorException(DXGetErrorString8(hr),__FILE__,__LINE__);
	}

    // Make sure we got a joystick
    if( NULL == mpJoystick )
	{	throw RecoverbleErrorException(" NULL == mpJoystick",__FILE__,__LINE__);}

    if( FAILED(mpJoystick->SetDataFormat( &c_dfDIJoystick2 ) ) )
	{	
		throw RecoverbleErrorException(DXGetErrorString8(hr),__FILE__,__LINE__);
	}

    // Set the cooperative level to let DInputImpl know how this device should
    // interact with the system and with other DInputImpl applications.
    if( FAILED(mpJoystick->SetCooperativeLevel( mHWND, DISCL_EXCLUSIVE | 
                                                             DISCL_FOREGROUND ) ) )
	{	
		throw RecoverbleErrorException(DXGetErrorString8(hr),__FILE__,__LINE__);}

    // Determine how many axis the joystick has (so we don't error out setting
    // properties for unavailable axis)
    mDIJoystickDevCaps.dwSize = sizeof(DIDEVCAPS);
    if ( FAILED(mpJoystick->GetCapabilities(&mDIJoystickDevCaps) ) )
 	{	
		throw RecoverbleErrorException(DXGetErrorString8(hr),__FILE__,__LINE__);
	
	}

	
    // Enumerate the axes of the joyctick and set the range of each axis. Note:
    // we could just use the defaults, but we're just trying to show an example
    // of enumerating device objects (axes, buttons, etc.).
    if ( FAILED(mpJoystick->EnumObjects( enumAxesCallback, 
                                                (VOID*)this, DIDFT_AXIS ) ) )
 	{	
		throw RecoverbleErrorException(DXGetErrorString8(hr),__FILE__,__LINE__);
	
	}
}// InitJoystick()

// ジョイスティックの設定コールバック　-------------------------------------------------
BOOL InputImpl::enumAxesCallback( const DIDEVICEOBJECTINSTANCE* pdidoi)
{

    DIPROPRANGE diprg; 
    diprg.diph.dwSize       = sizeof(DIPROPRANGE); 
    diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
    diprg.diph.dwHow        = DIPH_BYID; 
    diprg.diph.dwObj        = pdidoi->dwType; // Specify the enumerated axis
    diprg.lMin              = -1000; 
    diprg.lMax              = +1000; 
    
	// Set the range for the axis
	if( FAILED( mpJoystick->SetProperty( DIPROP_RANGE, &diprg.diph ) ) )
	{
		return DIENUM_STOP;
	}

    return DIENUM_CONTINUE;
}// enumAxesCallback 

// キーボードデバイスの初期化 -------------------------------------------------
void InputImpl::initKeyboard(void)
{
    HRESULT hr = E_FAIL;

	// キーボードデバイスの作成
	if( FAILED( hr = mpDI->CreateDevice( GUID_SysKeyboard, &mpKeyboard, NULL ) ) )
	{	//system::instance()->outputError(DXGetErrorString8(hr));
		throw RecoverbleErrorException(DXGetErrorString8(hr),__FILE__,__LINE__);
	}
    
	if( FAILED( hr = mpKeyboard->SetDataFormat( &c_dfDIKeyboard ) ) )
	{
		//system::instance()->outputError(DXGetErrorString8(hr));
		throw RecoverbleErrorException(DXGetErrorString8(hr),__FILE__,__LINE__);
	}
    
    // 協調レベルの設定 
    if(FAILED(mpKeyboard->SetCooperativeLevel( mHWND, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND )))
    {
		throw RecoverbleErrorException(DXGetErrorString8(hr),__FILE__,__LINE__);
    }

    // Acquire the newly created device
    mpKeyboard->Acquire();
}// initKeyboard

// キーボード・ジョイスティックの現在の状態を取得 -----------------------------
void InputImpl::update(void)
{
	HRESULT hr = E_FAIL;
	bool b_joystick = false;
	bool b_keyboard = false;
	
	// ジョイスティックの現在状態を取得 //
	if(mpJoystick){
		mpJoystick->Poll();
		hr = mpJoystick->GetDeviceState(sizeof(DIJOYSTATE2),(LPVOID)&mDIJoyState);
		if(FAILED(hr)){
			mpJoystick->Acquire();
			while( hr == DIERR_INPUTLOST ) 
				hr = mpJoystick->Acquire();
		} else {
			b_joystick = true;
		}
		

	} 

	// キーボードの現在状態を取得 //
	if(mpKeyboard){
//	    ZeroMemory( &mDIKeys, sizeof(mDIKeys) );
		hr = mpKeyboard->GetDeviceState(sizeof(mDIKeys),(LPVOID)&mDIKeys);
		if(FAILED(hr)){
#ifdef _DEBUG
			OutputDebugString(DXGetErrorString8(hr));
#endif
			mpKeyboard->Acquire();
			while( hr == DIERR_INPUTLOST ) 
				hr = mpKeyboard->Acquire();
		} else {
			b_keyboard = true;
		}
	}

	//
	mbBeforeLeft = mbLeft;
	mbBeforeRight = mbRight;
	mbBeforeUp = mbUp;
	mbBeforeDown = mbDown;

	mbBeforeButtonA = mbButtonA;
	mbBeforeButtonB = mbButtonB;
	mbBeforeButtonC = mbButtonC;
	mbBeforeButtonD = mbButtonD;

	mbBeforeStart = mbStart;
	mbBeforeExit = mbExit;

	mbLeft = mbRight = mbUp = mbDown = false;
	mbButtonA = mbButtonB = mbButtonC = mbButtonD = false;
	mbStart = mbExit = false;
	

	if(b_joystick){
		
		if(mDIJoyState.lX > 300){
			mbRight = true;
		} else {
			if(mDIJoyState.lX < -300)
			{

				mbLeft = true;
			}
		}
			
		if(mDIJoyState.lY > 300)
			mbDown = true;
		else 
			if(mDIJoyState.lY < -300)
				mbUp = true;

		if(mDIJoyState.rgbButtons[0])
		{
			mbButtonA = true;
			mbStart = true;
		}

		if(mDIJoyState.rgbButtons[1])
			mbButtonB = true;
		
		if(mDIJoyState.rgbButtons[2])
			mbButtonC =  true;
		
		if(mDIJoyState.rgbButtons[3])
			mbButtonD =  true;
	}

	if(b_keyboard){
		
		if(mDIKeys[DIK_LEFT] & 0x80)
			mbLeft = true;

		if(mDIKeys[DIK_RIGHT] & 0x80)
			mbRight = true;

		if(mDIKeys[DIK_UP] & 0x80)
			mbUp = true;

		if(mDIKeys[DIK_DOWN] & 0x80)
			mbDown =  true;

		if(mDIKeys[DIK_Z] & 0x80)
			mbButtonA = true;
		
		if(mDIKeys[DIK_X] & 0x80)
			mbButtonB = true;
		
		if(mDIKeys[DIK_C] & 0x80)
			mbButtonC = true;
		
		if(mDIKeys[DIK_V] & 0x80)
			mbButtonD = true;
		
		if(mDIKeys[DIK_SPACE] & 0x80)
			mbStart = true;

		if(mDIKeys[DIK_ESCAPE] & 0x80)
			mbExit = true;

	}

	// イベントリスナーQ→イベントリスナーリストへ //
	MouseListenerList::iterator it = mMouseListenerQ.begin();
	
	while(it != mMouseListenerQ.end())
	{
		if(*it != NULL){
			mMouseListenerList.push_back(*it);
		}
		it = mMouseListenerQ.erase(it);

	}

	KeyboardListenerList::iterator itk = mKeyboardListenerQ.begin();

	while(itk != mKeyboardListenerQ.end())
	{
		if(*itk != NULL){
			mKeyboardListenerList.push_back(*itk);
		}
		itk = mKeyboardListenerQ.erase(itk);
	}
}// update() 

LRESULT		InputImpl::onSetCursor(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
	if(system::instance()->isActive() && !mouseCursorVisibility())
	{
		SetCursor(NULL);//マウスカーソルを消す。
		return 0;
	}
	return DefWindowProc (hwnd, WM_SETCURSOR,wParam, lParam);
}// onSetCursor()

// マウスの左ボタンが押された －－－－－－－－－－－－－－－－－－－－－－－
LRESULT		InputImpl::onLButtonDown(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
	return callMouseListenerMethod(hwnd,wParam,lParam,input::mouse::Listener::leftButtonDown);
}// onLButtonDown()

// マウスの左ボタンが離された －－－－－－－－－－－－－－－－－－－－－－－
LRESULT		InputImpl::onLButtonUp(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
	return callMouseListenerMethod(hwnd,wParam,lParam,input::mouse::Listener::leftButtonUp);
}// onLButtonUp()

// マウスの左ボタンがダブルクリックされた　－－－－－－－－－－－－－－－－－－－－－－－
LRESULT		InputImpl::onLButtonDoubleClick(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
	return callMouseListenerMethod(hwnd,wParam,lParam,input::mouse::Listener::leftButtonDoubleClick);
}// onLButtonDoubleClick()

// マウスの右ボタンが離された －－－－－－－－－－－－－－－－－－－－－－－
LRESULT		InputImpl::onRButtonDown(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
	return callMouseListenerMethod(hwnd,wParam,lParam,input::mouse::Listener::rightButtonDown);
}// onRButtonDown()

// マウスの右ボタンが押された －－－－－－－－－－－－－－－－－－－－－－－
LRESULT		InputImpl::onRButtonUp(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
	return callMouseListenerMethod(hwnd,wParam,lParam,input::mouse::Listener::rightButtonUp);
}// onRButtonUp()

// マウスの右ボタンがダブルクリックされた　－－－－－－－－－－－－－－－－－－－－－－－
LRESULT		InputImpl::onRButtonDoubleClick(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
	return callMouseListenerMethod(hwnd,wParam,lParam,input::mouse::Listener::rightButtonDoubleClick);
}// onRButtonDoubleClick()

// マウスが動いた　－－－－－－－－－－－－－－－－－－－－－－－
LRESULT		InputImpl::onMouseMove(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
	return callMouseListenerMethod(hwnd,wParam,lParam,input::mouse::Listener::mouseMove);
}// onMouseMove()

// ホイールが動いた　－－－－－－－－－－－－－－－－－－－－－－－
LRESULT		InputImpl::onMouseWheel(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
	return callMouseListenerMethod(hwnd,wParam,lParam,input::mouse::Listener::mouseWheel);
}// onMouseWheel()

// MouseListenerのメソッドを呼び出す －－－－－－－－－－－－－－－－－－－－－－
LRESULT InputImpl::callMouseListenerMethod(const HWND hwnd, const WPARAM wParam, const LPARAM lParam,void(mouse::Listener::* pMethod)(const int,const int,const DWORD))
{
	using namespace sf::system::input;

	if(mMouseListenerList.size() == 0)
		return 0;

	POINT pt;
	
	pt.x = LOWORD(lParam);
	pt.y = HIWORD(lParam);
	
	ClientToScreen(hwnd,&pt);

	DWORD mkey = 0;

	if(MK_CONTROL & wParam)
		mkey |= mouse::MKEY_CONTROL;
	
	if(MK_SHIFT & wParam)
		mkey |= mouse::MKEY_SHIFT;

	mkey |= (wParam & 0xffff0000);

	
	MouseListenerList::iterator it = mMouseListenerList.begin();
	
	while(it != mMouseListenerList.end())
	{
		if(*it == NULL){
			it = mMouseListenerList.erase(it);
		} else {
			if((*it)->isListeningMouse()){
				((*it)->*pMethod)(pt.x,pt.y,mkey);
			}
			++it;
		}
	}

	return 0;
}// callListener

// キー押下時の処理 -----------------------------------------------------------
LRESULT InputImpl::onKeyDown(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
	DWORD extKey = 0;

	// とりあえず、ESCキーが押されたら終了するようにしている
	switch(wParam)
	{
	case 'Q':
		PostMessage( hwnd, WM_CLOSE, 0, 0L );
		break;
	default:

		if(GetAsyncKeyState(VK_SHIFT))
			extKey |= keyboard::SHIFT;

		if(GetAsyncKeyState(VK_CONTROL))
			extKey |= keyboard::CONTROL;

		
		KeyboardListenerList::iterator it = mKeyboardListenerList.begin();
	
		while(it != mKeyboardListenerList.end())
		{
			if(*it == NULL){
				it = mKeyboardListenerList.erase(it);
			} else {
				if((*it)->isListeningKeyboard()){
						(*it)->keyDown(wParam,extKey);
				}
				++it;
			}
		}

	}
	return 0; // WM_KEYDOWN
}// onKeyDown

// キーが離された時の処理 -----------------------------------------------------------
LRESULT InputImpl::onKeyUp(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
	return 0;
}// onKeyUp

// ウィンドウメッセージハンドラをSystemImplに登録 －－－－－－－－
void InputImpl::addWindowMessageMap()
{
	SystemImpl *pSys = dynamic_cast<SystemImpl *>(system::instance());

	pSys->addWindowMessage(WM_SETCURSOR, new WindowMessageImpl<InputImpl>(this,&InputImpl::onSetCursor));
	pSys->addWindowMessage(WM_LBUTTONDOWN,new WindowMessageImpl<InputImpl>(this,&InputImpl::onLButtonDown));
	pSys->addWindowMessage(WM_LBUTTONUP,new WindowMessageImpl<InputImpl>(this,&InputImpl::onLButtonUp));
	pSys->addWindowMessage(WM_LBUTTONDBLCLK,new WindowMessageImpl<InputImpl>(this,&InputImpl::onLButtonDoubleClick));

	pSys->addWindowMessage(WM_RBUTTONDOWN,new WindowMessageImpl<InputImpl>(this,&InputImpl::onRButtonDown));
	pSys->addWindowMessage(WM_RBUTTONUP,new WindowMessageImpl<InputImpl>(this,&InputImpl::onRButtonUp));
	pSys->addWindowMessage(WM_RBUTTONDBLCLK,new WindowMessageImpl<InputImpl>(this,&InputImpl::onRButtonDoubleClick));

	pSys->addWindowMessage(WM_MOUSEMOVE,new WindowMessageImpl<InputImpl>(this,&InputImpl::onMouseMove));
	pSys->addWindowMessage(WM_MOUSEWHEEL,new WindowMessageImpl<InputImpl>(this,&InputImpl::onMouseWheel));

	pSys->addWindowMessage(WM_KEYDOWN,new WindowMessageImpl<InputImpl>(this,&InputImpl::onKeyDown));
	pSys->addWindowMessage(WM_KEYUP,new WindowMessageImpl<InputImpl>(this,&InputImpl::onKeyUp));

}
