module y4d_input.mouse;

private import SDL;
private import y4d_aux.gameframe;
private import y4d_timer.fixtimer;

///	マウス入力クラス。
/**
	ウィンドゥが生きていて、かつ、
	メッセージポンプが動いている状態で呼び出されなくてはならない。

例)
<PRE>
	Screen screen = new Screen;
	screen.setVideoMode(640,480,0);

	MouseInput mouse = new MouseInput;
	while (!GameFrame.pollEvent()){
		screen.clear();
		screen.update();

		int x,y;
		mouse.getPos(x,y);

		bool a,b,c;
		a = mouse.isPress(MouseInput.button.left);
		b = mouse.isPress(MouseInput.button.middle);
		c = mouse.isPress(MouseInput.button.right);

		printf("%d %d : %d %d %d\n",x,y,a?1:0,b?1:0,c?1:0);

		if (x <10) 
			mouse.setPos(600,300);

	}
</PRE>

*/

interface MouseInput {
	/// ボタンの押し下げ判定のための構造体
	enum button : int {
		left   = 1,	//	左ボタン
		middle = 2,	//	中ボタン
		right  = 3,	//	右ボタン
		wheel_up = 4,
		wheel_down = 5
	}
	
	///	マウスカーソルを表示する
	void show();

	///	マウスカーソルを非表示にする
	void hide();
	/// マウス非表示か？
	bool isHide();

	///	マウスの位置取得
	/**
		マウスが画面外にあるときは最後に取得した値が返ります。
		すなわち、画面サイズが640×480ならば、xは0～639,yは0～479までの
		値しか返らないことが保証されます。
	*/
	void getPos(out int x,out int y);

	///	マウスの位置を設定
	/**
		SDLのマウスの位置設定イベントが発生する。
	*/
	void setPos(uint x,uint y);

	///	ボタンが押し下げられているかを判定する関数
	bool isPress(MouseInput.button e);

	/** これを設定すると n 回 update が呼び出されるまで入力を無視する */
	void setGuardTime(int n);

	/**  左ボタンが押し下げられているか */
	bool isLButtonDown();
	
	/**  左ボタンが押しあげられたか */
	bool isLButtonUp();
	
	/// 左ボタンがダブルクリックされたか
	bool isLButtonDClick();
	
	/**  右ボタンが押し下げられているか */
	bool isRButtonDown();
	
	/**  右ボタンが押しあげられたか */
	bool isRButtonUp();
	
	/// 右ボタンがダブルクリックされたか
	bool isRButtonDClick();
	
	/**  中ボタンが押し下げられているか */
	bool isMButtonDwon();
	
	/**  中ボタンが押しあげられたか */
	bool isMButtonUp();
	
	/// 中ボタンがダブルクリックされたか
	bool isMButtonDClick();
	
	/// 押し続けているフレーム数を返す
	uint getKeepingTimeL();

	/// 押し続けているフレーム数を返す
	uint getKeepingTimeM();

	/// 押し続けているフレーム数を返す
	uint getKeepingTimeR();
	
	/// 内部状態を更新する
	void update();
	
	/// アップデート監視
	void updateMark();
}

/**
///	マウス入力クラス。
/**
	ウィンドゥが生きていて、かつ、
	メッセージポンプが動いている状態で呼び出されなくてはならない。

例)
<PRE>
	Screen screen = new Screen;
	screen.setVideoMode(640,480,0);

	MouseInput mouse = new MouseInput;
	while (!GameFrame.pollEvent()){
		screen.clear();
		screen.update();

		int x,y;
		mouse.getPos(x,y);

		bool a,b,c;
		a = mouse.isPress(MouseInput.button.left);
		b = mouse.isPress(MouseInput.button.middle);
		c = mouse.isPress(MouseInput.button.right);

		printf("%d %d : %d %d %d\n",x,y,a?1:0,b?1:0,c?1:0);

		if (x <10) 
			mouse.setPos(600,300);

	}
</PRE>


	ゲームで使うのが便利なようにMaouseInputをラップしたクラス
	updateを呼び出すことで内部状態を更新し、次回updateがよばれるまで、
	同状態を保持する。	
*/
class MouseInputImpl : MouseInput {

public:

	///	マウスカーソルを表示する
	void show() {
		SDL_ShowCursor(SDL_ENABLE);
		m_carsorHide = false;
	}

	///	マウスカーソルを非表示にする
	void hide() {
		SDL_ShowCursor(SDL_DISABLE);
		m_carsorHide = true;
	}

	/// カーソルが隠されているか？
	bool isHide() {
		return this.m_carsorHide;
	}
	
	
	///	マウスの位置取得
	/**
		マウスが画面外にあるときは最後に取得した値が返ります。
		すなわち、画面サイズが640×480ならば、xは0～639,yは0～479までの
		値しか返らないことが保証されます。
	*/
	void getPos(out int x,out int y){
		SDL_GetMouseState(&x,&y);
	}

	///	マウスの位置を設定
	/**
		SDLのマウスの位置設定イベントが発生する。
	*/
	void setPos(uint x,uint y) {
		SDL_WarpMouse(cast(ushort) x,cast(ushort) y);
	}

	///	ボタンが押し下げられているかを判定する関数
	bool isPress(button e) {
		int x,y;
		
		if (e==button.wheel_up) {
			return GameFrame.isWheelUp();
		} else if(e==button.wheel_down) {
			return GameFrame.isWheelDown();
		}
		
		return cast(bool) ((SDL_GetMouseState(&x,&y) & SDL_BUTTON(cast(uint)e)) != 0);
	}

	/** これを設定すると n 回 update が呼び出されるまで入力を無視する */
	void setGuardTime(int n) {
		if (n > 0) {
			m_guardTime = n;
		}
	}

	/**  左ボタンが押し下げられているか */
	bool isLButtonDown() {
		return m_lbuttonPre;
	}
	
	/**  左ボタンが押しあげられたか */
	bool isLButtonUp() {
		return m_lbuttonUp;
	}
	
	/// 左ボタンがダブルクリックされたか
	bool isLButtonDClick() {
		return m_lbuttonDClick;
	}
	
	/**  右ボタンが押し下げられているか */
	bool isRButtonDown() {
		return m_rbuttonPre;
	}
	
	/**  右ボタンが押しあげられたか */
	bool isRButtonUp() {
		return m_rbuttonUp;
	}
	
	/// 右ボタンがダブルクリックされたか
	bool isRButtonDClick() {
		return  m_rbuttonDClick;
	}
	
	/**  中ボタンが押し下げられているか */
	bool isMButtonDwon() {
		return m_mbuttonPre;
	}
	
	/**  中ボタンが押しあげられたか */
	bool isMButtonUp() {
		return m_mbuttonUp;
	}
	
	/// 中ボタンがダブルクリックされたか
	bool isMButtonDClick() {
		return m_mbuttonDClick;
	}
	
	/// 押し続けているフレーム数を返す
	uint getKeepingTimeL() {
		return m_keepingTime[0];
	}

	/// 押し続けているフレーム数を返す
	uint getKeepingTimeM() {
		return m_keepingTime[1];
	}

	/// 押し続けているフレーム数を返す
	uint getKeepingTimeR() {
		return m_keepingTime[2];
	}
	
	/// 内部状態を更新する
	void update() 
	/**
		この関数が呼ばれた瞬間の情報を保持し、
		次のupdateが呼ばれるまでマウスの状態は同じ状態を返す
	*/
	{
		if (m_updated) {
			// updateMark が呼ばれるまで何もしない
			return;
		}
		
		m_lbuttonUp = false;
		m_rbuttonUp = false;
		m_mbuttonUp = false;
		m_lbuttonDClick = false;
		m_rbuttonDClick = false;
		m_mbuttonDClick = false;
		
		if ( m_guardTime > 0 ) {
			m_guardTime--;
			return;
		}
		
		// 左ボタン
		if ( isPress( button.left ) ) {
			if ( !m_lbuttonPre ) {
				if ( checkDClick(0) ) {
					m_lbuttonDClick = true;
				}
				
				m_keepingTime[0] = 0;
				
				// 新規押し下げでタイマ起動
				timers[0].reset();
			} else {
				++m_keepingTime[0];				
			}
			m_lbuttonPre = true;
		} else {
			if ( m_lbuttonPre ) {
				// 押し上げられた
				m_lbuttonUp = true;
			}
			m_lbuttonPre = false;
		}
		
		// 右ボタン
		if ( isPress( button.right ) ) {
			if ( !m_rbuttonPre ) {

				if ( checkDClick(1) ) {
					m_rbuttonDClick = true;
				}

				m_keepingTime[1] = 0;
				
				// 新規押し下げでタイマ起動
				timers[1].reset();
			} else {
				++m_keepingTime[1];				
			}

			m_rbuttonPre = true;
		} else {
			if ( m_rbuttonPre ) {
				// 押し上げられた
				m_rbuttonUp = true;
			}
			m_rbuttonPre = false;
		}
		 
		// 中ボタン
		if ( isPress( button.middle ) ) {
			if ( !m_mbuttonPre ) {

				if ( checkDClick(2) ) {
					m_mbuttonDClick = true;
				}
				
				m_keepingTime[2] = 0;

				// 新規押し下げでタイマ起動
				timers[1].reset();
			} else {
				++m_keepingTime[2];	
			}
			m_mbuttonPre = true;
		} else {
			if ( m_mbuttonPre ) {
				// 押し上げられた
				m_mbuttonUp = true;
			}
			m_mbuttonPre = false;
		}
		
		m_updated = true;
	}
	
	/// updateを呼び出すのを制限するため
	void updateMark() {
		m_updated = false;
	}
	
	/// コンストラクタ
	this() {
		foreach (inout FixTimer t; timers) {
			t = new FixTimer();
			t.reset();
		}
		m_carsorHide = false;
	}

protected:
	/// ダブルクリックをチェックします
	bool checkDClick(int button) {
		timers[button].update();
		return cast(bool) (timers[button].get() < LC_DEFAULT_DCLICK_TIME);
	}
	
	/* デフォルトの許容ダブルクリック間隔 */
	static const uint LC_DEFAULT_DCLICK_TIME = 250;
	
	FixTimer[3] timers;
	uint[3]		m_keepingTime;
	
	bool m_carsorHide;
	bool m_updated;
	int m_guardTime;	//!< ガードタイム

	bool m_lbuttonPre;	//!< 現在のマウスの状態
	bool m_rbuttonPre;	
	bool m_mbuttonPre;	
	bool m_lbuttonUp;	//!< 現在マウスが押し上げられたか
	bool m_rbuttonUp;
	bool m_mbuttonUp;	
	bool m_lbuttonDClick;	//!< 現在マウスがダブルクリックされたか
	bool m_rbuttonDClick;
	bool m_mbuttonDClick;	
	
	
}

///	入力系のnull device。
/**
	MouseInputで、すべての関数が何もしないように実装されたクラス。
	JoyStickがつながっていないときなど、内部的には、このクラスをnewして、
	そのオブジェクトに委譲してやればnullチェックが省略できる、というもの。
*/

class MouseInputNullDevice : MouseInput {
	void show() { }
	void hide() { }
	bool isHide() {return false; }
	void getPos(out int x,out int y) { }
	void setPos(uint x,uint y) { }
	bool isPress(MouseInput.button e) { return false; }
	void setGuardTime(int n) { }
	bool isLButtonDown() { return false; }
	bool isLButtonUp() { return false; }
	bool isLButtonDClick() { return false; }
	bool isRButtonDown() { return false; }
	bool isRButtonUp() { return false; }
	bool isRButtonDClick() { return false; }
	bool isMButtonDwon() { return false; }
	bool isMButtonUp() { return false; }
	bool isMButtonDClick() { return false; }
	uint getKeepingTimeL() { return 0; }
	uint getKeepingTimeM() { return 0; }
	uint getKeepingTimeR() { return 0; }
	void update() { }
	void updateMark() { }
	
	/// NullDeviceを取得する
	static MouseInputNullDevice getInstance() {
		if (m_instance is null) {
			m_instance = new MouseInputNullDevice();
		}
		return m_instance;
	}

private:
	/// 一枚札
	this() {
	}
	static MouseInputNullDevice m_instance;
}

/**
	任意のタイミングでデバイスをNULLデバイスに切り替える仕組みを
	実装しているMouseInput
*/
class NullableMouseInput : MouseInput {
	void show() { get().show(); }
	void hide() { get().hide(); }
	bool isHide() {return false;  }
	void getPos(out int x,out int y) { get().getPos(x,y); }
	void setPos(uint x,uint y) { get().setPos(x,y); }
	bool isPress(MouseInput.button e) { return get().isPress(e);  }
	void setGuardTime(int n) { get().setGuardTime(n); }
	bool isLButtonDown() { return get().isLButtonDown();  }
	bool isLButtonUp() { return get().isLButtonUp();  }
	bool isLButtonDClick() { return get().isLButtonDClick();  }
	bool isRButtonDown() { return get().isRButtonDown();  }
	bool isRButtonUp() { return get().isRButtonUp();  }
	bool isRButtonDClick() { return get().isRButtonDClick();  }
	bool isMButtonDwon() { return get().isMButtonDwon();  }
	bool isMButtonUp() { return get().isMButtonUp();  }
	bool isMButtonDClick() { return get().isMButtonDClick();  }
	uint getKeepingTimeL() { return get().getKeepingTimeL();  }
	uint getKeepingTimeM() { return get().getKeepingTimeM();  }
	uint getKeepingTimeR() { return get().getKeepingTimeR();  }
	void update() { get().update(); }
	void updateMark() { get().updateMark(); }
	
	/// ヌルデバイスに切り替える
	void disable() {
		m_enable = false;
	}
	/// ノーマルデバイスに切り替える
	void enable() {
		m_enable = true;
	}
	/// デバイスが有効になっているか？
	bool isEnable() {
		return m_enable;
	}
	/// オリジナルのデバイスを取得する
	MouseInput getOriginal() {
		return this.m_mouse;
	}
	/// コンストラクタ
	this(MouseInput mouse) {
		m_mouse = mouse;
		m_enable = true;
	}
	/// 静的コンストラクタ
	static this() {
		m_nullMouse = MouseInputNullDevice.getInstance();
	}
	
private:

	MouseInput get() {
		return m_enable ? m_mouse : m_nullMouse;
	}

	static MouseInput m_nullMouse;
	MouseInput m_mouse;
	bool m_enable;
}
