module y4d_input.joystick;

private import y4d_input.keyinputbase;

private import SDL;
private import ytl.singleton;
private import std.string; // toString

///	ジョイスティック入力クラス。
/**
	joystick入力クラスです。

	Joystick joy = new Joystick(0);
	とすれば、0番に接続されているジョイスティックにattachします。
	番号は0番から始まります。ジョイスティックが接続されていない場合、
	このjoy.isPush() や joy.isPress 等の入力判定関数はすべてfalseを
	返すようになります。(NullDeviceとattachされる)

	すなわちユーザーはjoystickの有無について考える必要は、(普通は)
	ありません。
*/
class JoyStick : KeyInputBase {
	
	/// 入力を無効にする
	void disable() {
		if (m_disable) {
			return;
		}
		
		if (keyInputNullDvice is null) {
			keyInputNullDvice = KeyInputNullDevice.getInstance();
		}
		inputOrg = input;
		input = keyInputNullDvice;
		m_disable = true;
	}
	
	/// 入力を有効にする
	void enable() {
		if (!m_disable) {
			return;
		}
		input = inputOrg;
		m_disable = false;
	}

	///	ボタンの入力判定。現在押されているか(状態はupdate関数を呼び出さないと更新されない)
	/**
		isPushは前回押されていなくて今回押されていればtrue
		isPressは前回の状態は関係なく、今回押されていればtrue

		軸入力もボタンとして扱う。
			↑:0　↓:1　←:2　→:3
			1つ目のボタン:4  2つ目のボタン:5 ...(以下ボタンのついている限り)

		これ以上の情報が欲しいならSDL_joystickを利用すべき
	*/
	bool isPush(int nButtonNo)
	{	return input.isPush(nButtonNo); }

	///	ボタンの入力判定。前回のupdateのときに押されていなくて、今回のupdateで押されたか
	/**
		isPushは前回押されていなくて今回押されていればtrue
		isPressは前回の状態は関係なく、今回押されていればtrue

		軸入力もボタンとして扱う。
			↑:0　↓:1　←:2　→:3
			1つ目のボタン:4  2つ目のボタン:5 ...(以下ボタンのついている限り)

		これ以上の情報が欲しいならSDL_joystickを利用すべき

	*/
	bool isPress(int nButtonNo)
	{	return input.isPress(nButtonNo); }

	///	接続されているJoyStickのdevice nameを返す
	char[] getDeviceName() { return input.getDeviceName(); }
	/**
		未接続のJoyStickの場合、getDeviceName()で
			"KeyInputNullDevice"
		が返ります。
	*/

	///	ボタンの数を返します。
	/**
		軸入力も4つのボタンと扱われているので、
		戻り値は ボタンの数+4になります。
	*/
	int getButtonNum() { return input.getButtonNum(); }

	///	joystickの情報を更新する
	/**
		これを呼び出さないと、isPush,isPressの情報は更新されません。
	*/
	void update() { input.update(); }
	/// update監視
	void updateMark() { input.updateMark(); }

	///	joystickの固有の情報を得る
	/**
		JoyStickならば、獲得したSDL_Joystick* を返す
		(SDLのJoyStickのAPIを直接呼びたいとき用)
		NullDeviceならば、nullを返す
	*/
	void*	getInfo() { return input.getInfo(); }

	/**
			attachしたいJoyStickのナンバーを指定してやる
			nDeviceNo = 0～countJoyStick-1までの数
			不正な値を渡した場合は、NullDeviceが生成される
			(エラーにはならない)
	*/
	this(int nDeviceNo)
	{
		input = singleton!(JoystickControl).get()
			.getDevice(nDeviceNo);
	}

	static int countJoyStick()
	///	接続されているjoyStickの数を返す。0 = 接続なし
	{
		return singleton!(JoystickControl).get()
			.countJoyStick();
	}

private:
	static KeyInputBase keyInputNullDvice;
	KeyInputBase input;
	KeyInputBase inputOrg;
	bool m_disable;
}

//	このクラスはsingleton的に用いると良いだろう。
private:
class JoystickControl {
	int countJoyStick()
	///	接続されているjoystickの数を返す
	///	一度目の呼び出し以降に接続されたものは認識できない
	{
		if (bInit) {
			if (!devices) return 0;
		} else {
			bInit = true;
			if (SDL_InitSubSystem(SDL_INIT_JOYSTICK)<0)
				return 0;

			devices = new KeyInputBase[SDL_NumJoysticks()];
		}
		return devices.length;
	}

	//	生成を兼ねるのでsynchronizedにしておく
	synchronized KeyInputBase getDevice(int device_index)
	/*
			対応するjoystick deviceを生成して返す
			最初のgetDeviceの呼び出し時点で接続されていない
			deviceについては認識できﾅｰ
	*/
	{
		if (device_index<0 || device_index >= countJoyStick()){
			return KeyInputNullDevice.getInstance();
			//	繋がってないのでnull deviceを代入
		}
		if (!devices[device_index]){
			//	未生成っぽいので生成する
			SDL_Joystick* j = SDL_JoystickOpen(device_index);
			if (!j)
			// open失敗しちょる
				devices[device_index] = KeyInputNullDevice.getInstance();
			else
				devices[device_index] = new JoyStickImp(j);
		} else {
			//	生成済みならそれを返せば?
		}
		return devices[device_index];
	}

	this() {
		bInit = false;
	}

	~this(){
		if (/* bInit &&*/ devices)
			SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
	}

private:
	KeyInputBase[] devices;
	bool bInit;
}


private:
class JoyStickImp : KeyInputBase {
///	JoyStickの実装
	bool isPush(int nButtonNo) {
		if (nButtonNo > getButtonNum()) return false;
		return cast(bool) (!buttonState[1-flip][nButtonNo]
			&& buttonState[flip][nButtonNo]);
	}
	bool isPress(int nButtonNo) {
		if (nButtonNo > getButtonNum()) return false;
		return buttonState[flip][nButtonNo];
	}

	int getButtonNum() { return buttonState[0].length; }

	void	update(){
		if (updated) {
			return;
		}
		
		// ジョイスティックの状態の更新
		SDL_JoystickUpdate();
		//	↑これ全部のjoystickをpollするので、イベントループのなかで
		//	行なわれていることに期待すべきだが..忘れそうなのでここに入れる

		flip = 1-flip; // flipping

		//	軸情報の更新
		for(int i=0;i<axes && i<2;++i){
			short u = SDL_JoystickGetAxis(joystick,i);
			//	 * The state is a value ranging from -32768 to 32767.

			//	2/3以上倒れてたら、入力ありと見なす
			buttonState[flip][3-((i<<1)+0)] = cast(bool) (u >=  32767*2/3);
			buttonState[flip][3-((i<<1)+1)] = cast(bool) (u <= -32768*2/3);

			//	一般的には、軸0が左右、軸1が上下
			//	たいていは左上が(-32768,-32768)のようだ
		}

		//	buttonの更新
		for(int i=4;i<getButtonNum();++i){
			buttonState[flip][i] = cast(bool) (SDL_JoystickGetButton(joystick,i-4) != 0);
		}

		updated = true;
	}

	/// update監視
	void updateMark() { updated = false; }

	char[] getDeviceName() {
		int nDeviceIndex = SDL_JoystickIndex(joystick);
		return cast(char[]) .toString(SDL_JoystickName(nDeviceIndex));
	}

	///	獲得したSDL_Joystick* を返す(SDLのJoyStickのAPIを直接呼びたいとき用)
	void* getInfo() { return cast(void*) joystick; }

	this(SDL_Joystick*j) {
		joystick = j;
		int nNumButtons = SDL_JoystickNumButtons(joystick);
		for(int i=0;i<2;++i){
			buttonState[i] = new bool[nNumButtons+4]; // 4というのは軸の分
		}
		axes = SDL_JoystickNumAxes(joystick);
	}
	~this() { if (!joystick) SDL_JoystickClose(joystick); }

private:
	SDL_Joystick* joystick;	//	SDLのjoystick識別子
	bool[]	buttonState[2];	//	前回の入力状態と今回の入力状態
	int flip;	//	前回の入力状態と今回の入力状態をflipさせて使う。flip={0,1}
	int axes;	//	軸の数
	bool updated;
}

static this() {} // for singleton template's static constructor

