#include "mof/private/DirectInput.hpp"
#include <vector>
#include "mof/InputReceiver.hpp"
#include "mof/InputDevice.hpp"
#include "mof/ConsoleIO.hpp"
#include "mof/private/DeviceInputReceiver.hpp"
#include "memory"

namespace {
	LPDIRECTINPUT8 pInput;
	LPDIRECTINPUTDEVICE8A pDevice;
	std::shared_ptr<mof::InputReceiver> pInputReceiver;//外部に渡すため
	mof::DeviceInputReceiver* pDeviceInputReceiver;//上記と同じインスタンス
}

//--- プロトタイプ宣言
BOOL CALLBACK enumJoyCallback(const DIDEVICEINSTANCE* pInstance , VOID* pContext);

/**
 * mof::InputDevice::initialize
 *
 */
void mof::InputDevice::initialize( ){
    HINSTANCE hInst = GetModuleHandle(NULL);
	HRESULT hr;
	hr = DirectInput8Create(hInst , DIRECTINPUT_VERSION ,IID_IDirectInput8 , (void**)&pInput , NULL);
	if(FAILED(hr)){
		switch(hr){
			case DIERR_BETADIRECTINPUTVERSION :
				DEBUG_PRINT("DIERR_BETADIRECTINPUTVERSION");
				break;
			case DIERR_INVALIDPARAM :
				DEBUG_PRINT("DIERR_INVALIDPARAM");
				break;
			case DIERR_OLDDIRECTINPUTVERSION : 
				DEBUG_PRINT("DIERR_OLDDIRECTINPUTVERSION");
				break;
			case DIERR_OUTOFMEMORY :
				DEBUG_PRINT("DIERR_OUTOFMEMORY");
				break;
		}
		throw std::runtime_error("Failed --- create direct input");
	}
	
	LPCDIDATAFORMAT dataFmt = &c_dfDIJoystick;//ジョイスティックが取得できたと仮定
	//---デバイスの設定
	//ゲームパッドデバイスを探す
	hr = pInput->EnumDevices(DI8DEVCLASS_GAMECTRL , enumJoyCallback , NULL , DIEDFL_ATTACHEDONLY);
	if(FAILED(hr) || pDevice == NULL){
		//ゲームパッドがなければキーボード
		hr = pInput->CreateDevice(GUID_SysKeyboard , &pDevice , NULL);
		if(FAILED(hr)){
				pInput->Release();
				pInput = NULL;
				throw std::runtime_error("Failed --- create device");
		}
		dataFmt = &c_dfDIKeyboard;//キーボードをデバイスとして取得
		DEBUG_PRINT("Select --- keyboard as input device");
	}
	else DEBUG_PRINT("Select --- joystick as input device");
	
	hr = pDevice->SetDataFormat(dataFmt);
	if(FAILED(hr)){
		pDevice->Release();
		pDevice = NULL;
		pInput->Release();
		pInput = NULL;
		throw std::runtime_error("Failed --- set data format");
	}


	DIPROPDWORD diprop;
	diprop.diph.dwSize = sizeof(diprop);
	diprop.diph.dwHeaderSize = sizeof(diprop.diph);
	diprop.diph.dwObj = 0;
	diprop.diph.dwHow = DIPH_DEVICE;
	diprop.dwData = 1000;
	pDevice->SetProperty(DIPROP_BUFFERSIZE , &diprop.diph);
	setActivation(true);
	
	pDeviceInputReceiver = new mof::DeviceInputReceiver();
	pInputReceiver = std::shared_ptr<mof::InputReceiver>(pDeviceInputReceiver);
}



/**
 * mof::InputDevice::finalize
 *
 */
void mof::InputDevice::finalize(void)
{
	mof::InputDevice::setActivation(false);
    if(pDevice != NULL)pDevice->Release();
	if(pInput != NULL)pInput->Release();
}


void mof::InputDevice::setActivation(bool active){
	if(pDevice == NULL)return;

	if(active == false)pDevice->Unacquire();
	else pDevice->Acquire();
}



BOOL CALLBACK enumJoyCallback(const DIDEVICEINSTANCE* pInstance , VOID* /*pContext*/ ){
	HRESULT hr;
	
	hr = pInput->CreateDevice(pInstance->guidInstance , &(pDevice) , NULL);
	if(FAILED(hr))return DIENUM_CONTINUE;

	DIDEVICEINSTANCE deviceInstance;
	deviceInstance.dwSize = sizeof(DIDEVCAPS);
	hr = pDevice->GetCapabilities((LPDIDEVCAPS)&deviceInstance);
	if(FAILED(hr)){
		pDevice->Release();
		pDevice = NULL;
		return DIENUM_CONTINUE;
	}

	*ConsoleOut::getInstance() << "Found InputDevice --- " << deviceInstance.tszInstanceName 
		<< ":" << deviceInstance.tszProductName << std::endl;
	return DIENUM_STOP;//何でもいいから採用
}

	
void mof::InputDevice::update(){
	
	HRESULT hr;
	while(true){
		DIDEVICEOBJECTDATA od;
		DWORD dwItems = 1;
		hr = pDevice->GetDeviceData(sizeof(DIDEVICEOBJECTDATA) , &od , &dwItems , 0);
		if(hr == DIERR_INPUTLOST)pDevice->Acquire();
		else if(FAILED(hr) || dwItems == 0)return;
		
		InputEvent iEvent(od.dwOfs , od.dwData);
		//inputSignal(iEvent);
		pDeviceInputReceiver->notifyInputEvent(iEvent);

		/*for(int i = 0 ; i < listenerList.size() ; ++i){
			int tmpSize = listenerList.size();
			listenerList.at(i)->notifyInputEvent(iEvent);
			if(tmpSize != listenerList.size())i--;//NotifyInputEventでRemoveされたかをチェック
		}*/
	
	}


}



std::shared_ptr<mof::InputReceiver> mof::InputDevice::getInputReceiver(){
	return pInputReceiver;
}


/*
namespace mof{

	class DeviceInputReceiver;

	class InputDevice
	{
		

		std::vector<DeviceInputReceiver*> listenerList;
		
		void addReceiver(DeviceInputReceiver* pReceiver);
	public:
		InputDevice(void);
		~InputDevice(void);
		

		bool initialize( HINSTANCE hInst);
		static BOOL CALLBACK enumJoyCallback(const DIDEVICEINSTANCE* pInstance , VOID* pContext);

		void activate(bool);
		void updateInputData();
		InputReceiver* getInputReceiver();
		void removeReceiver(DeviceInputReceiver* pReceiver);
		//void clearListenerList();


	};

};

typedef std::vector<mof::DeviceInputReceiver*>::iterator LISTENERITR;

mof::InputDevice::InputDevice(void)
{
	 m_pInput = NULL;
	 m_pDevice = NULL;
}

mof::InputDevice::~InputDevice(void)
{
	activate(false);
	if(m_pDevice != NULL)m_pDevice->Release();
	if(m_pInput != NULL)m_pInput->Release();
}


bool mof::InputDevice::initialize( HINSTANCE hInst){
	HRESULT hr;
	hr = DirectInput8Create(hInst , DIRECTINPUT_VERSION ,IID_IDirectInput8 , (void**)&m_pInput , NULL);
	if(FAILED(hr))return false;
	
	LPCDIDATAFORMAT dataFmt = &c_dfDIJoystick;//ジョイスティックが取得できたと仮定
	//---デバイスの設定
	//ゲームパッドデバイスを探す
	hr = m_pInput->EnumDevices(DI8DEVCLASS_GAMECTRL , enumJoyCallback , NULL , DIEDFL_ATTACHEDONLY);
	if(FAILED(hr) || m_pDevice == NULL){
		//ゲームパッドがなければキーボード
		hr = m_pInput->CreateDevice(GUID_SysKeyboard , &m_pDevice , NULL);
		if(FAILED(hr)){
				m_pInput->Release();
				m_pInput = NULL;
				return false;
		}
		else dataFmt = &c_dfDIKeyboard;//キーボードをデバイスとして取得
	}

	
	hr = m_pDevice->SetDataFormat(dataFmt);
	if(FAILED(hr)){
		*ConsoleOut::getInstance() << "SetDataFormat --- ERROR" << std::endl;
		m_pDevice->Release();
		m_pDevice = NULL;
		m_pInput->Release();
		m_pInput = NULL;
		return false;
	}

	//m_pDevice->SetCooperativeLevel( hWnd , DISCL_FOREGROUND);

	DIPROPDWORD diprop;
	diprop.diph.dwSize = sizeof(diprop);
	diprop.diph.dwHeaderSize = sizeof(diprop.diph);
	diprop.diph.dwObj = 0;
	diprop.diph.dwHow = DIPH_DEVICE;
	diprop.dwData = 1000;
	m_pDevice->SetProperty(DIPROP_BUFFERSIZE , &diprop.diph);
	activate(true);
	return true;
}
	

void mof::InputDevice::activate(bool active){
	if(m_pDevice == NULL)return;

	if(active == false)m_pDevice->Unacquire();
	else m_pDevice->Acquire();
}

void mof::InputDevice::updateInputData(){
	
	HRESULT hr;
	while(true){
		DIDEVICEOBJECTDATA od;
		DWORD dwItems = 1;
		hr = m_pDevice->GetDeviceData(sizeof(DIDEVICEOBJECTDATA) , &od , &dwItems , 0);
		if(hr == DIERR_INPUTLOST)m_pDevice->Acquire();
		else if(FAILED(hr) || dwItems == 0)return;
		
		InputEvent iEvent(od.dwOfs , od.dwData);
		//inputSignal(iEvent);
		
		for(int i = 0 ; i < listenerList.size() ; ++i){
			int tmpSize = listenerList.size();
			listenerList.at(i)->notifyInputEvent(iEvent);
			if(tmpSize != listenerList.size())i--;//NotifyInputEventでRemoveされたかをチェック
		}
	
	}


}



mof::InputReceiver* mof::InputDevice::getInputReceiver(){
	mof::DeviceInputReceiver* pReceiver = new mof::DeviceInputReceiver();
	addReceiver(pReceiver);
	return pReceiver;

}


void mof::InputDevice::addReceiver(mof::DeviceInputReceiver* pReceiver){
	for(LISTENERITR itr = listenerList.begin() ; itr != listenerList.end() ; ++itr){
		if((*itr) == pReceiver)return;//既に登録済み
	}
	listenerList.push_back(pReceiver);
}

void mof::InputDevice::removeReceiver(mof::DeviceInputReceiver* pReceiver){
	for(LISTENERITR itr = listenerList.begin() ; itr != listenerList.end() ; ++itr){
		if((*itr) == pReceiver){
			listenerList.erase(itr);
			return;
		}
	}
	
}
*/
