#pragma once

#define DIRECTINPUT_VERSION		DIRECTINPUT_HEADER_VERSION

#ifdef USE_OFFICIAL_DXSDK
  #include <dinput.h>
#else
  #include "dinput/dinput.h"
#endif // USE_OFFICIAL_DXSDK

#include <map>
#include <vector>

#include "Misc.h"

// FKUTフォルダをプロジェクトファイルがあるフォルダに置いた場合のパス指定になっています
// 置き場所をいじった場合は適宜変更してください
#ifndef USE_OFFICIAL_DXSDK
  #pragma comment(lib,"FKUT/dinput/dxguid.lib")
  #pragma comment(lib,"FKUT/dinput/dinput8.lib")
#else
  #pragma comment(lib,"dxguid.lib")
  #pragma comment(lib,"dinput8.lib")
#endif


struct fkut_InputInfo {
	// 押されているボタンがtrue、押されていなければfalse
	bool	button[32];
	// 左スティックを基準とした上下左右の入力状態
	bool	up;
	bool	down;
	bool	left;
	bool	right;
	// x[0],y[0]が左スティック、x[1],y[1]が右スティックのアナログ値
	// 360コントローラはx[2],y[2]が右スティックのアナログ値で、x[1]がLT(+),RT(-)の押し込みに対応
	short	x[3];
	short	y[3];
	// -1でPOV未入力状態、真上を0とし、時計回りに4500ずつ増加した数値が入る
	int		pov;
};

enum fkut_AxisDirection {
	AXIS1_X = 1,
	AXIS1_Y,
	AXIS2_X,
	AXIS2_Y,
	AXIS3_X,
	AXIS3_Y
};

enum fkut_RawAxis {
	AXIS_X_POS = 0,
	AXIS_X_NEG,
	AXIS_Y_POS,
	AXIS_Y_NEG,
	AXIS_Z_POS,
	AXIS_Z_NEG,
	AXIS_RX_POS,
	AXIS_RX_NEG,
	AXIS_RY_POS,
	AXIS_RY_NEG,
	AXIS_RZ_POS,
	AXIS_RZ_NEG
};


#include <stdio.h>

class fkut_Input : public Noncopyable {
private:
	static const int BUTTON_NUM = 32;
	static const int DIMGR_BUFFER_SIZE = 256;
	static const int INPUT_DEVICE_NUM = 16;
	static const int MOUSE_BUTTON_MAX = 8;
	static const int KEY_NUM = 256;

	int		buttonSwap[INPUT_DEVICE_NUM][BUTTON_NUM], axisSwap[INPUT_DEVICE_NUM][2*3];
	DWORD	axisMap[4*3], povMap[8], keyMap[BUTTON_NUM];

	struct AxisParam {
		int pos, neg, border;
	};
	std::map<int, AxisParam>	axisParams[INPUT_DEVICE_NUM];

	//キーボードのメンバー変数
	bool m_Key[KEY_NUM];

	//マウスのメンバー変数
	POINT	m_MouseWorldPos;	//マウスの絶対座標
	int		m_MouseWheel;
	POINT	m_MouseLocalPos;	//マウスのローカル座標
	bool	m_Button[MOUSE_BUTTON_MAX];
	bool	m_LastButton[MOUSE_BUTTON_MAX];

	//Joyのメンバー変数
	DWORD					m_JoyCount;
	DWORD					m_JoyMaxCount;
	fkut_InputInfo*			m_JoyButton;
	int						m_AllowX[INPUT_DEVICE_NUM];		//X座標の遊び	0〜1000の値です。低いほど敏感
	int						m_AllowY[INPUT_DEVICE_NUM];		//Y座標の遊び	0〜1000の値です。低いほど敏感
	int						m_AllowMin[INPUT_DEVICE_NUM];	// アナログスティックの値で有効とする範囲を指定します
	int						m_AllowMax[INPUT_DEVICE_NUM];	// アナログスティックの値で有効とする範囲を指定します

	//デバイス関連のメンバー変数
	HWND					m_hwnd;
	LPDIRECTINPUT8			m_Input;
	LPDIRECTINPUTDEVICE8	m_KeyBoardDevice;
	LPDIRECTINPUTDEVICE8	m_MouseDevice;
	LPDIRECTINPUTDEVICE8	m_JoyStciDevice[INPUT_DEVICE_NUM];
	DIDEVCAPS				m_DevCaps;
	DIDEVICEOBJECTDATA		m_DeviceObject[DIMGR_BUFFER_SIZE];  // Receives buffered data

	//=====================
	//Update関連
	//=====================
	HRESULT UpdateJoy(void);
	HRESULT UpdateMouse(void);
	HRESULT UpdateKey(void);

	//===========================
	//Joyのコールバック関数
	//===========================
	static BOOL CALLBACK EnumSizesCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID lpvRef);
	static BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE* lpddi, LPVOID lpContext);

	short	AdjustParam(int, short);
	void	SetAxisParam(int, int, short);

public:
	//==============================
	//	コンストラクタ・デストラクタ
	//==============================
	fkut_Input(void);
	~fkut_Input(void);

	//==============================
	//	マウス・キーボード取得関連
	//==============================
	/*
		ここらへんは自力で使えそうなら使ってもいいですが、
		今回のバージョンでは丁寧なサポートはしていません。
		特にゲームパッドとキーボードにリンクについては下の方の命令が便利なので、
		そちらの方を使うようにしてください。
	*/

	// マウス関連
	POINT&	GetMouseLocalPos(void);
	POINT&	GetMouseWorldPos(void);
	bool	GetMouseButton(DWORD keycode);
	bool	GetLastMouseButton(DWORD keycode);
	int		GetMouseWheel(void);

	// キー関連
	bool	GetKey(const DWORD keycode);

	//==============================
	//	基本処理
	//==============================

	//! 初期化処理
	/*
		最初に必ず1回呼んでください。タイミングがちょっとシビアで、
		「Fl_Windowの変数に対してshow()を呼んだ後」に呼ぶ必要があります。
		引数には(fl_display, fl_xid(&Fl_Windowの変数))と記述します。
		fl_xid()という関数を使うにあたって#include <Fl/x.h>が必要になります。
		記述例としてサンプルを参照してください。
		きちんと初期化が済むとS_OKという返値が返ります。
	*/
	HRESULT init(HINSTANCE hinst, HWND hwnd);

	//! 後片付け処理
	/*
		プログラムを閉じる前に呼びます。忘れてもデストラクタで呼び出されますが、
		自分で呼んでおいた方が安心確実です。
	*/
	HRESULT uninit(void);

	//! 更新処理
	/*
		毎フレームごとに入力状態を更新します。描画処理の後にでも呼んでください。
		これを呼ばないと、何回繰り返しても入力に反応してくれません。
	*/
	HRESULT update(void);

	//==============================
	//	ジョイスティック関連
	//==============================

	//!	ジョイスティック本数取得
	/*
		init()した時点で何本のジョイスティックを認識したかを取得します。
		init()されてから抜き差しがあっても関知できません。
	*/
	inline DWORD	getJoyCount(void);

	//!	ジョイスティックの情報取得
	/*
		ジョイスティックの状態そのままを取得します。
		fkut_InputInfo型のポインタ変数を用意し、それで受け取ってください。
		ボタンの入れ替えは反映された情報が返りますが、キーボードでの代替操作は考慮しません。
		キーボードと処理を一本化したい場合はgetInputStatus()がおすすめです。
		引数には取得したいジョイスティックのID番号を指定します。1本目が0になります。
		無効なジョイスティックの番号を指定された場合はNULLを返します。
	*/
	fkut_InputInfo*	getJoyStick(DWORD inJoyIndex);

	//!	スティックのしきい値設定
	/*
		左スティックは十字キー代わりに使われることが多いですが、
		その際にどのくらいスティックを倒したらその方向への入力と見なすか、を設定できます。
		第1引数にジョイスティックのID番号、第2引数は横方向、第3引数は縦方向のしきい値を指定します。
		引数を2つだけ指定する関数は、ID0のジョイスティックへの指定を表します。
		値の範囲は0〜1000です。初期値は500になっています。
	*/
	void			setBorder(int joyIndex, int borderX, int borderY);
	void			setBorder(int borderX, int borderY);

	//!	スティックの最小・最大値の設定
	/*
		スティックが返す値は微妙にブレるため、動かしていなくてもぴったり0にならなかったり、
		目一杯倒しても1000にならなかったりします。
		そのため、ある値未満は0と見なし、ある値より大きければ1000と見なすという設定ができます。
		値をばっさりカットするだけではなく、有効範囲となった値は0〜1000の範囲にマッピングされますので、
		ゲームの処理を書く側は常に0〜1000の範囲で処理を考えてくれれば結構です。
		第1引数にジョイスティックのID番号、第2引数に最小値、第3引数に最大値を指定します。
		引数を2つだけ指定する関数は、ID0のジョイスティックへの指定を表します。
		値の範囲は0〜1000ですが、最小値が最大値よりも大きいような指定をした場合はエラーになるので、
		何も設定を変更せずに無視します。初期値は最小値200、最大値900です。
	*/
	void			setMinMax(int joyIndex, int min, int max);
	void			setMinMax(int min, int max);

	//!	ボタンの入れ替え設定
	/*
		ボタンの番号を入れ替えることができます。
		第1引数にジョイスティックのID番号、第2引数にゲーム上でのボタン番号、第3引数にパッドの生のボタン番号を指定します。
		初期値はボタンの生の番号そのままが対応づけられています。ゲーム内でのボタン名称を列挙型で名前付けして、それに
		対応付けするのがおすすめです。例によって番号は0スタートなので注意してください。
	*/
	void			setButtonSwap(int joyIndex, int id, int raw);

	//!	軸の入れ替え設定
	/*
		スティックの軸方向と向きを入れ替えることができます。
		第1引数にジョイスティックのID番号、第2引数にゲーム上での軸番号、第3引数にパッドの生の軸番号を指定します。
		ゲーム上の軸番号は AXIS1_X, AXIS1_Y, AXIS2_X, AXIS2_Y, AXIS3_X, AXIS3_Y の何れかを指定します。
		パッドの軸番号は AXIS_X_POS, AXIS_Y_POS, AXIS_Z_POS, AXIS_RX_POS, AXIS_RY_POS, AXIS_RZ_POS の何れかか、
		POS の部分を NEG に変更して得られる値を指定してください。
		NEG にした場合は、パッドから得られる左右や上下の方向を反転した値を取得するようになります。
		初期値の対応は以下の通りです。使用するパッドやコントローラ変換器に応じて変更してください。
			AXIS1_X = AXIS_X_POS,  AXIS1_Y = AXIS_Y_POS,
			AXIS2_X = AXIS_Z_POS,  AXIS2_Y = AXIS_RZ_POS,
			AXIS3_X = AXIS_RX_POS, AXIS3_Y = AXIS_RY_POS
	*/
	void			setAxisSwap(int joyIndex, fkut_AxisDirection id, fkut_RawAxis raw);

	//! 軸のボタン割り当て
	/*
		スティックの軸方向をボタンとして扱うことができます。実質XBox360コントローラ向けの機能です。
		第1引数にジョイスティックのID番号、第2引数に軸番号、第3引数に+方向のボタン番号、第4引数-方向のボタン番号、
		第5引数にボタンを押したと判断するしきい値を指定します。
		以下はID0に接続されたXBox360コントローラを、PS系コントローラ互換の割り当てに変更するコード例です。
		デフォルトだとAXIS2_X(AXIS_Z)がLT,RTに対応しますが、右スティックを軸2にアサインする設定も含めて例示します。
			// 軸2と3を入れ替え
			setAxisSwap(0, AXIS2_X, AXIS_RX_POS);
			setAxisSwap(0, AXIS2_Y, AXIS_RY_POS);
			setAxisSwap(0, AXIS3_X, AXIS_Z_POS);
			setAxisSwap(0, AXIS3_Y, AXIS_RZ_POS);
			// 入れ替え後の軸3のX方向を6,7番に割り当て
			setAxisToButton(0, AXIS3_X, 6, 7, 500);
			// ボタン入れ替え
			setButtonSwap(0,  8, 6);	// Back->Select
			setButtonSwap(0,  9, 7);	// Start->Start
			setButtonSwap(0, 10, 8);	// StickL->StickL
			setButtonSwap(0, 11, 9);	// StickR->StickR
	*/
	void			setAxisToButton(int joyIndex, fkut_AxisDirection axisID, int posBtn, int negBtn, int border);

	//! 軸のボタン割り当て解除
	/*
		スティックの軸方向をボタンに割り当てた設定を解除します。
	*/
	void			clearAxisToButton(int joyIndex);

	//!	ボタンに対するキーボードのアサイン
	/*
		ジョイスティックのボタンの代わりに使用するキーを設定します。
		第1引数はゲーム上でのボタン番号、第2引数にキーコードを指定します。
		ゲーム上でのボタン番号とは、setButtonSwap()で設定した「入れ替えた後の番号」を指します。

		キーコードの指定は、dinput.h内で定義されている「DIK_」で始まる定数を使って行います。
		キーボードでの代替操作はジョイスティックIDが0のものに対してのみ有効です。
	*/
	void			setKeyAssign(int id, DWORD keycode);

	//!	スティックに対するキーボードのアサイン
	/*
		ジョイスティックのスティックの代わりに使用するキーを設定します。
		第1引数は0で左スティック(通常十字キーとして使われる方)を、1で右スティックを、2で3番目の軸を表します。
		第2引数以降は順番に、上下左右に対応するキーコードを指定します。

		キーコードの指定は、dinput.h内で定義されている「DIK_」で始まる定数を使って行います。
		キーボードでの代替操作はジョイスティックIDが0のものに対してのみ有効です。
	*/
	void			setAxisAssign(int axis_id, DWORD up, DWORD dn, DWORD lt, DWORD rt);

	//!	POVボタンに対するキーボードのアサイン
	/*
		ジョイスティックのPOVボタンの代わりに使用するキーを設定します。
		第1引数から順番に、左上、真上、右上、左、右、左下、真下、右下に対応するキーコードを指定します。

		キーコードの指定は、dinput.h内で定義されている「DIK_」で始まる定数を使って行います。
		キーボードでの代替操作はジョイスティックIDが0のものに対してのみ有効です。
	*/
	void			setPOVAssign(DWORD ul, DWORD u, DWORD ur, DWORD l, DWORD r, DWORD dl, DWORD d, DWORD dr);

	//!	キーボードを含めた入力状態取得
	/*
		入力状態を取得します。fkut_InputInfo型の変数をあらかじめ用意し、それで受け取ってください。
		引数にはジョイスティックIDを指定しますが、0を指定した場合はジョイスティックが接続されていない場合、
		キーボードからの入力が反映された情報が返ってきます。接続している場合はジョイスティックの状態が返ります。
	*/
	fkut_InputInfo	getInputStatus(int = 0);

};
