//------------------------------------------------------------------------------
// Lamp : Open source game middleware
// Copyright (C) 2004  Junpei Ohtani ( Email : junpee@users.sourceforge.jp )
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//------------------------------------------------------------------------------

/** @file
 * Lamp入力実装
 * @author Junpee
 */

#include "LampBasic.h"
#include "Input/System/LampInput.h"
#include "Input/System/BufferedInput.h"
#include "Input/Keyboard/KeyboardKey.h"
#include "Input/Keyboard/Keyboard.h"
#include "Input/Keyboard/KeyboardDevice.h"
#include "Input/Mouse/Mouse.h"
#include "Input/Mouse/MouseDevice.h"
#include "Input/Joystick/Joystick.h"
#include "Input/Joystick/JoystickDevice.h"
#include "Core/InputOutput/BinaryFileWriter.h"
#include "Core/InputOutput/BinaryFileReader.h"

namespace Lamp{

// ウィンドウハンドル
HWND LampInput::windowHandle_ = NULL;
// DirectInput
DirectInput* LampInput::directInput_ = NULL;
// 入力モード
LampInput::InputMode LampInput::inputMode_ = LampInput::modePolling;
// バッファ入力
BufferedInput* LampInput::bufferedInput_ = NULL;

// ログライタ
BinaryWriter* LampInput::logWriter_ = NULL;
// バイナリファイルライタ
BinaryWriter* LampInput::binaryFileWriter_ = NULL;
// ログリーダ
BinaryReader* LampInput::logReader_ = NULL;
// バイナリファイルリーダ
BinaryReader* LampInput::binaryFileReader_ = NULL;

// キーボード
Keyboard* LampInput::keyboard_ = NULL;
// キーボードデバイス
KeyboardDevice* LampInput::keyboardDevice_ = NULL;
// マウス
Mouse* LampInput::mouse_ = NULL;
// マウスデバイス
MouseDevice* LampInput::mouseDevice_ = NULL;
// ジョイスティック
ArrayList<Joystick*> LampInput::joysticks_;
// ジョイスティックデバイス
ArrayList<JoystickDevice*> LampInput::joystickDevices_;

// 初期化フラグ
bool LampInput::isInitialized_ = false;
// ログ取得中フラグ
bool LampInput::isLogging_ = false;
// ログ再生中フラグ
bool LampInput::isLogPlaying_ = false;

//------------------------------------------------------------------------------
// 初期化、後始末
//------------------------------------------------------------------------------
// 初期化
bool LampInput::initialize(
	HINSTANCE instanceHandle, HWND windowHandle, InputMode inputMode){
	if(isInitialized_){ return true; }
	Assert(inputMode_ == modePolling);
	windowHandle_ = windowHandle;
	LampCore::initialize();
	KeyboardKey::initializeKeyString();
	// DirectInput作成
	if(DirectXFailed(DirectInputCreate(instanceHandle, DIRECTINPUT_VERSION,
		DirectInputInterfaceID, (void**)&directInput_, NULL))){
		ErrorOut("LampInput::initialize() "
			"DirectInputの作成に失敗しました");
		return false;
	}

	// キーボードデバイスの作成
	DirectInputDevice* device;
	if(DirectXFailed(directInput_->CreateDevice(
		GUID_SysKeyboard, &device, NULL))){
		ErrorOut("LampInput::initialize() "
			"キーボードデバイスの初期化に失敗しました");
		return false;
	}
	keyboardDevice_ = new KeyboardDevice();
	if(!keyboardDevice_->initialize(device, windowHandle_)){ return false; }
	keyboard_ = new Keyboard(keyboardDevice_);

	// マウスデバイスの作成
	if(DirectXFailed(directInput_->CreateDevice(
		GUID_SysMouse, &device, NULL))){
		ErrorOut("LampInput::initialize() "
			"マウスデバイスの初期化に失敗しました");
		return false;
	}
	mouseDevice_ = new MouseDevice();
	if(!mouseDevice_->initialize(device, windowHandle_)){ return false; }
	mouse_ = new Mouse(mouseDevice_);

	// ジョイスティックデバイスの作成
	if(DirectXFailed(directInput_->EnumDevices(
		DirectInputDeviceClass_GameController, joystickEnumeration,
		NULL, DIEDFL_ATTACHEDONLY))){
		ErrorOut("LampInput::initialize() "
			"ジョイスティックデバイスの列挙に失敗しました");
		return false;
	}
	// 初期化フラグ立てる
	isInitialized_ = true;
	setInputMode(inputMode);
	return true;
}
//------------------------------------------------------------------------------
// ジョイスティックの列挙コールバック
int __stdcall LampInput::joystickEnumeration(
	const DIDEVICEINSTANCE* instance, void* userData){
	// デバイス作成
	DirectInputDevice* device;
	if(DirectXFailed(directInput_->CreateDevice(
		instance->guidInstance, &device, NULL))){
		ErrorOut("LampInput::joystickEnumeration() "
			"ジョイスティックデバイスの作成に失敗しました");
		return DIENUM_STOP;
	}
	// ジョイスティック初期化
	JoystickDevice* joystickDevice = new JoystickDevice();
	if(!joystickDevice->initialize(device, windowHandle_)){
		ErrorOut("LampInput::joystickEnumeration() "
			"ジョイスティックデバイスの初期化に失敗しました");
		return DIENUM_STOP;
	}
	joystickDevices_.add(joystickDevice);
	joysticks_.add(new Joystick(joystickDevice));
	return DIENUM_CONTINUE;
}
//------------------------------------------------------------------------------
// 後始末
void LampInput::finalize(){
	// ログ再生を停止する
	if(isLogPlaying()){ stopLog(); }
	// ログ取得を停止する
	if(isLogging()){ endLogging(); }
	// バッファ入力の後始末
	if(inputMode_ == modeBuffering){ finalizeBufferInput(); }
	// デバイス解放
	for(int i = joysticks_.getCount() - 1; i >= 0; i--){ delete joysticks_[i]; }
	joysticks_.clear();
	for(int i = joystickDevices_.getCount() - 1; i >= 0; i--){
		delete joystickDevices_[i];
	}
	joystickDevices_.clear();
	SafeDelete(mouse_);
	SafeDelete(mouseDevice_);
	SafeDelete(keyboard_);
	SafeDelete(keyboardDevice_);
	// DirectInput解放
	SafeRelease(directInput_);
	// 初期化フラグクリア
	isInitialized_ = false;
}
//------------------------------------------------------------------------------
// クリア
void LampInput::clear(){
	Assert(isInitialized_);
	keyboard_->clear();
	mouse_->clear();
	int joystickCount = joysticks_.getCount();
	for(int i = 0; i < joystickCount; i++){ joysticks_[i]->clear(); }
}
//------------------------------------------------------------------------------
// バッファクリア
void LampInput::bufferClear(){
	Assert(isInitialized_);
	Assert(inputMode_ == modeBuffering);
	while(bufferedInput_->hasMoreInput()){ bufferedInput_->nextInput(); }
	// バッファをクリアしてから現在の入力をクリアする
	clear();
}
//------------------------------------------------------------------------------
// 入力モード
//------------------------------------------------------------------------------
// 入力モードの設定
void LampInput::setInputMode(InputMode inputMode){
	Assert(isInitialized_);
	Assert(!isLogPlaying());
	Assert(!isLogging());
	if(inputMode_ == inputMode){ return; }
	// バッファ入力の後始末
	if(inputMode_ == modeBuffering){ finalizeBufferInput(); }
	inputMode_ = inputMode;
	// バッファ入力の初期化
	if(inputMode_ == modeBuffering){ initializeBufferInput(); }
}
//------------------------------------------------------------------------------
// ポーリング入力
//------------------------------------------------------------------------------
// ポーリング
bool LampInput::polling(){
	Assert(isInitialized_);
	Assert(inputMode_ == modePolling);
	bool result = true;
	if(!isLogPlaying_){
		// デバイスポーリング
		if(!devicePolling()){ result = false; }
	}else{
		// ログポーリング
		logPolling();
	}
	// ログの書き出し
	writeLog();
	return result;
}
//------------------------------------------------------------------------------
// デバイスポーリング
bool LampInput::devicePolling(){
	bool result = true;
	// キーボードのポーリング
	if(!keyboardDevice_->polling()){ result = false; }
	keyboard_->setNextState(keyboardDevice_->getKeyboardState());

	// マウスのポーリング
	if(!mouseDevice_->polling()){ result = false; }
	mouse_->setNextState(mouseDevice_->getMouseState());

	// ジョイスティックのポーリング
	int joystickDeviceCount = getJoystickDeviceCount();
	for(int i = 0; i < joystickDeviceCount; i++){
		JoystickDevice* joystickDevice = getJoystickDevice(i);
		if(!joystickDevice->polling()){ result = false; }
		getJoystick(i)->setNextState(joystickDevice->getJoystickState());
	}
	return result;
}
//------------------------------------------------------------------------------
// ログポーリング
void LampInput::logPolling(){
	// キーボードのログ読み込み
	KeyboardState keyboardState;
	keyboardState.readBinary(logReader_);
	keyboard_->setNextState(keyboardState);

	// マウスのログ読み込み
	MouseState mouseState;
	mouseState.readBinary(logReader_);
	mouse_->setNextState(mouseState);

	// ジョイスティックのログ読み込み
	int joystickCount = getJoystickCount();
	for(int i = 0; i < joystickCount; i++){
		JoystickState joystickState;
		joystickState.readBinary(logReader_);
		getJoystick(i)->setNextState(joystickState);
	}

	// ログ終端チェック
	if(logReader_->isEnd()){ stopLog(); }
}
//------------------------------------------------------------------------------
// ログの書き出し
void LampInput::writeLog(){
	if(!isLogging()){ return; }
	keyboard_->getState().writeBinary(logWriter_);
	mouse_->getState().writeBinary(logWriter_);
	int joystickCount = getJoystickCount();
	for(int i = 0; i < joystickCount; i++){
		getJoystick(i)->getState().writeBinary(logWriter_);
	}
}
//------------------------------------------------------------------------------
// バッファ入力
//------------------------------------------------------------------------------
// バッファ入力初期化
void LampInput::initializeBufferInput(){
	Assert(bufferedInput_ == NULL);
	// バッファ入力初期化
	bufferedInput_ = new BufferedInput();
	bufferedInput_->setKeyboard(keyboard_, keyboardDevice_);
	bufferedInput_->setMouse(mouse_, mouseDevice_);
	int joystickCount = joysticks_.getCount();
	for(int i = 0; i < joystickCount; i++){
		bufferedInput_->addJoystick(joysticks_[i], joystickDevices_[i]);
	}
	// バッファ入力開始
	bufferedInput_->start();
}
//------------------------------------------------------------------------------
// バッファ入力後始末
void LampInput::finalizeBufferInput(){
	Assert(bufferedInput_ != NULL);
	// バッファ入力終了
	bufferedInput_->stop();
	// バッファ入力後始末
	SafeDelete(bufferedInput_);
}
//------------------------------------------------------------------------------
// 入力があるか
bool LampInput::hasMoreInput(){
	Assert(isInitialized_);
	Assert(inputMode_ == modeBuffering);
	return bufferedInput_->hasMoreInput();
}
//------------------------------------------------------------------------------
// 入力を待つ
void LampInput::waitForInput(){
	while(true){
		if(hasMoreInput()){ return; }
		Thread::sleep(1);
	}
}
//------------------------------------------------------------------------------
// 次の入力
void LampInput::nextInput(){
	Assert(isInitialized_);
	Assert(inputMode_ == modeBuffering);
	bufferedInput_->nextInput();
}
//------------------------------------------------------------------------------
// 入力数の取得
int LampInput::getInputCount(){
	Assert(isInitialized_);
	Assert(inputMode_ == modeBuffering);
	return bufferedInput_->getInputCount();
}
//------------------------------------------------------------------------------
// ログ取得
//------------------------------------------------------------------------------
// ログ取得の開始
void LampInput::startLogging(const String& filePath){
	Assert(binaryFileWriter_ == NULL);
	binaryFileWriter_ = new BinaryFileWriter(filePath);
	startLogging(binaryFileWriter_);
}
//------------------------------------------------------------------------------
// ログ取得の開始
void LampInput::startLogging(BinaryWriter* binaryWriter){
	Assert(!isLogging_);
	Assert(logWriter_ == NULL);
	logWriter_ = binaryWriter;
	// ヘッダの書き出し
	logWriter_->writeString("LampInputLog");
	logWriter_->writeInt(getJoystickCount());
	InputMode inputMode = getInputMode();
	logWriter_->writeInt(inputMode);
	if(inputMode == modeBuffering){ bufferedInput_->startLogging(logWriter_); }
	isLogging_ = true;
}
//------------------------------------------------------------------------------
// ログ取得の終了
void LampInput::endLogging(){
	Assert(isLogging_);
	Assert(logWriter_ != NULL);
	isLogging_ = false;
	if(getInputMode() == modeBuffering){ bufferedInput_->endLogging(); }
	logWriter_->flush();
	logWriter_ = NULL;
	SafeDelete(binaryFileWriter_);
}
//------------------------------------------------------------------------------
// ログ再生
//------------------------------------------------------------------------------
// ログ再生の開始
void LampInput::playLog(const String& filePath){
	Assert(binaryFileReader_ == NULL);
	binaryFileReader_ = new BinaryFileReader(filePath);
	playLog(binaryFileReader_);
}
//------------------------------------------------------------------------------
// ログ再生の開始
void LampInput::playLog(BinaryReader* binaryReader){
	Assert(!isLogPlaying_);
	Assert(logReader_ == NULL);
	logReader_ = binaryReader;
	// ヘッダの読み込み
	String headerString = logReader_->readString();
	if(!headerString.equals("LampInputLog")){
		ErrorOut("LampInput::playLog LampInputLog形式のファイルではありません");
	}
	int joystickCount = logReader_->readInt();
	if(joystickCount != getJoystickCount()){
		ErrorOut("LampInput::playLog ジョイスティックの数が違います");
	}
	// 毎フレーム毎に入力モードを出力すれば動的切り替えにも対応可能
	// ただしLampInput使用するコード側がpollingとnextInputを使い分ける必要がある
	InputMode inputMode = (InputMode)logReader_->readInt();
	setInputMode(inputMode);
	if(inputMode == modeBuffering){ bufferedInput_->playLog(binaryReader); }
	isLogPlaying_ = true;
}
//------------------------------------------------------------------------------
// ログ再生の停止
void LampInput::stopLog(){
	Assert(isLogPlaying_);
	Assert(logReader_ != NULL);
	isLogPlaying_ = false;
	if(getInputMode() == modeBuffering){ bufferedInput_->stopLog(); }
	logReader_ = NULL;
	SafeDelete(binaryFileReader_);
}
//------------------------------------------------------------------------------
// ウィンドウプロシージャ
LRESULT LampInput::windowProcedure(
	HWND windowHandle, u_int message, WPARAM wParam, LPARAM lParam){
/*
	switch(message){
	// ウィンドウのアクティブ、非アクティブに変更
	case WM_ACTIVATEAPP:
bool minimized = ((int)HIWORD(wParam) != 0);
//		if(mouse_->isExclusive()){
			if((LOWORD(wParam) != WA_INACTIVE)){
				// ウィンドウがアクティブになったのでカーソルを消す
				while(::ShowCursor(false) >= 0){}
			}else{
				// ウィンドウがアクティブでなくなったのでカーソルを見せる
				while(::ShowCursor(true) < 0){}
			}
//		}
		break;
	}
*/
	return 0;
}
//------------------------------------------------------------------------------
} // End of namespace Lamp
//------------------------------------------------------------------------------
