module yamalib.gui.guibutton;

private import std.string;
private import std.utf;

private import y4d;
private import y4d_draw.drawbase;
private import y4d_input.mouse;

private import yamalib.draw.tiletexture;
private import yamalib.counterfsp;
private import yamalib.gui.guiparts;
private import yamalib.log.log;

/**
	ボタンが押された時の通知用ハンドラ
	こいつとmediatorから多重継承して派生させると便利
*/
abstract class GUIButtonEventListerner {

	void onInit() {}

	/// 右ボタンクリック
	void onRBClick() {}

	/// 左ボタンクリック
	void onLBClick() {}

	/// 右ボタン押し下げ
	void onRBDown() {}

	/// 左ボタン押し下げ
	void onLBDown() {}

	/// 右ボタン押し上げ
	void onRBUp() {}

	/// 左ボタン押し上げ
	void onLBUp() {}

	/// ボタン画像の(px,py)の地点はボタンの座標か？
	bool isButton(int px,int py) { return false; }

	/// ボタンを(x,y)の座標にpushの状態で表示する
	void onDraw(Screen screen,int x,int y,bool push,bool bin) {}

	/// そのフレーム内にクリックされたか
	bool isLClick() { return false; }

	/// そのフレーム内にクリックされたか
	bool isRClick() { return false; }
	
	/// フォーカス対象となる矩形を返却
	Rect* focusRect() { return new Rect(); }

}

/// 何もしないイベントリスナ
final class GUINullButtonListener : GUIButtonEventListerner {
	/// フォーカス対象となる矩形を返却
	override Rect* focusRect() { 
		return m_focusRect; 
	}

	this() {
		m_focusRect = new Rect();
	}
	
private:
	Rect* m_focusRect;
}


/// 明滅ボタンリスナ
class GUIBlinkButtonListener : GUIButtonEventListerner {
public:
	enum TYPE : uint {
		OFF			= 0x0000,	// 常にオフ
		DEFAULT 	= 0x0001,	// ON（ダウン）/OFF（通常）
		IN_ACTIVE 	= 0x0002,	// ON（ダウン）/OFF（通常）だが、フォーカスのときアクティブ
		BLINK_IN	= 0x0004,	// フォーカス時に明滅
		REVERSE_DBL	= 0x0008,	// リバースモードあり（二枚－通常、ダウン）
		REVERSE_TRI	= 0x0010,	// リバースモードあり（三枚－通常、フォーカス、ダウン）
		OFFSET_FIX	= 0x0020,	// 固定オフセット画像表示
		BLINK_FIX	= 0x0040,	// 入力を受け付け、常に点滅するボタン
	};

	/// テクスチャローダの設定
	void setTextureLader(TextureLoader tl,int no) {
		if ( !(tl is null) ) {
			textureLoader = tl;
		}
		textureStart = no;
	}

	/// ボタンの内側か？
	override bool isButton(int px,int py) {
		if (type == TYPE.OFF) return false;	// 無効ボタン

		// ボタン座標の(px,py)の地点はボタン座標か
		Texture t = getMyTexture(false,false);
		if (!t) return false;

		if (type&4) {
			// TODO
		} else {
			// TODO
		}

		// αを無視した簡易判定
		return cast(bool) (px>=0&&py>=0&&cast(int)t.getWidth()>px&&cast(int)t.getHeight()>py);
	}

	/// 描画処理
	override void onDraw(Screen screen,int x,int y,bool push,bool bin) {
		if (type == TYPE.OFF) return;

		bool bPush;
		bool nowIn = cast(bool) (bin == true && focus == false);	// 新たにマウスがフォーカスされた
		focus = bin;
		
		
		if (type & TYPE.BLINK_FIX) {
			// 明滅
			blink++;

			drawBlink(screen,x,y);
			
			// 完了
			return;
			
		} else if (type & TYPE.BLINK_IN && bin) {
			if (nowIn) {
				blink.reset();
			}
			// 明滅
			blink++;
			
			drawBlink(screen,x,y);
			
			// 完了
			return;
			
		} else if (type & TYPE.IN_ACTIVE) {
			bPush = bin;
		} else {
			// 通常（押し下げられたときアクティブ
			bPush = cast(bool) (push && bin);
		}

		screen.blt(getMyTexture(bPush,bin),x,y);
	}

	/// ボタンタイプの設定
	void setType(TYPE type_) {
		type = type_;
		if (type & TYPE.BLINK_IN || type & TYPE.BLINK_FIX) blink.reset();
	}
	/// タイプの取得
	int getType() { return type; }

	/// 逆面モードの設定
	void setReverse(bool reverse_) { reverse = reverse_; }
	/// 逆面モードの取得
	bool getReverse() { return reverse; }

	/// 明滅速度の設定
	void setBlinkSpeed(int n) { blink.setStep(n); }
	
	/// イメージオフセットの設定
	void setImageOffset(int n) { imageOffset = n; }
	/// イメージオフセットの取得
	int getImageOffset() { return imageOffset; }
	
	/// 現在の状態より、表示すべきテクスチャを返却する
	Texture getMyTexture(bool push, bool bIn) {
		int n = textureStart;
		if (push) {
			n++;
		}
		if (bIn && (type & TYPE.REVERSE_TRI)) {
			// マウスが内部かつ、フォーカス状態を持つ
			n++;
		}

		// 逆面モード		
		if (reverse) {
			if (type & TYPE.REVERSE_DBL) {
				// 二枚画像逆面
				n += 2;
			} else if (type & TYPE.REVERSE_TRI) {
				// 三枚画像逆面
				n += 3;
			}
		}

		// 固定画像
		if (type & TYPE.OFFSET_FIX) {
			n = textureStart+imageOffset;
		}

		return textureLoader.get(n);
	}

	/// ボタンが押されたときの通知
	void onLButtonClick() {}
	void onRButtonClick() {}

	/// ボタンの入力状態の取得
	override bool isLClick() { return l_click; }
	override bool isRClick() { return r_click; }
	
	/// フォーカス対象となる矩形を返却
	override Rect* focusRect() {
		Rect* rc = new Rect();
		Texture t = getMyTexture(false,false);
		if (t is null) {
			return rc;
		}
		rc.right = t.getWidth();
		rc.bottom = t.getHeight();
		
		return rc; 
	}

	/// コンストラクタ	
	this() {
		blink = new RootCounter;
		blink.set(0, 255, 8);
		blink.setReverse(true);
	}

protected:

	/// 初期化処理
	void onInit() { 
		l_click = false; 
		r_click = false; 
	}
	/// 左ボタンクリック処理
	void onLBClick() {
		if (type == TYPE.OFF) return; // 無効ボタン
		l_click = true;
		onLButtonClick();
	}
	/// 右ボタンクリック処理
	void onRBClick() {
		if (type == TYPE.OFF) return; // 無効ボタン
		r_click = true;
		onLButtonClick();
	}
	/// 左ボタン押し下げ処理
	void onLBDown() {
		if (type == TYPE.OFF) return; // 無効ボタン
//		l_click = true;
		onLButtonClick();	// サブクラスに委譲
	}

	/// 明滅状態を描画する
	void drawBlink(Screen screen, int x, int y) {
		// 描画を独自実装
		Color4ub colorOrg = screen.getColor4ub();
		
		// 通常状態描画
		screen.blt(getMyTexture(false,false),x,y);
		
		// 明度設定
		screen.setColor(255, 255, 255, blink.get() );
		
		if (type & TYPE.REVERSE_TRI) {
			// フォーカス状態の画像
			screen.blt(getMyTexture(false,true),x,y);
		} else {
			// ＯＮ状態の画像
			screen.blt(getMyTexture(true,true),x,y);
		}
	
		screen.setColor(colorOrg);
	}
			


	TextureLoader textureLoader;
	int textureStart;
	TYPE type;
	bool reverse;
	bool l_click;
	bool r_click;
	bool focus;		// フォーカスされていたか？
	RootCounter blink;
	int imageOffset;
}

/**
	通常のボタンリスナ
*/
class GUINormalButtonListener : GUIButtonEventListerner {
	/// テクスチャローダの設定
	void setTextureLader(TextureLoader tl,int no) {
		if ( !(tl is null) ) {
			textureLoader = tl;
		}
		textureStart = no;
	}

	/// ボタンの内側か？
	override bool isButton(int px,int py) {
		if (type == 0) return false;	// 無効ボタン

		// ボタン座標の(px,py)の地点はボタン座標か
		Texture t = getMyTexture(false,false);
		if (!t) return false;

		if (type&4) {
			// TODO
		} else {
			// TODO
		}

		// αを無視した簡易判定
		return cast(bool) (px>=0&&py>=0&&cast(int)t.getWidth()>px&&cast(int)t.getHeight()>py);
	}

	/// 描画処理
	override void onDraw(Screen screen,int x,int y,bool push,bool bin) {
		if (type == 0) return;

		if (type & (32+64)) {
			screen.blt(textureLoader.get(textureStart+imageOffset),x,y);
			return;
		}

		bool b;
		if (type & 16) {
			// 明滅
			b = cast(bool) !blink.getReversing();
			blink++;
		} else if (type & 8) {
			// フォーカスボタン（フォーカスでアクティブ）
			b = bin;
		} else {
			// 通常（押し下げられたときアクティブ
			b = cast(bool) (push && bin);
		}

		screen.blt(getMyTexture(b,bin),x,y);
	}

	/// ボタンタイプの設定
	void setType(int type_) {
		type = type_;
		if (type & 16) blink.reset();
	}
	/// タイプの取得
	int getType() { return type; }

	/// 逆面モードの設定
	void setReverse(bool reverse_) { reverse = reverse_; }
	/// 逆面モードの取得
	bool getReverse() { return reverse; }

	/// 明滅速度の設定
	void setBlinkSpeed(int n) { blink.setEnd(n); }
	/// イメージオフセットの設定
	void setImageOffset(int n) { imageOffset = n; }
	/// イメージオフセットの取得
	int getImageOffset() { return imageOffset; }
	/// 現在の状態より、表示すべきテクスチャを返却する
	Texture getMyTexture(bool push, bool bIn) {
		assert( textureLoader !is null );
		
		int n = textureStart;
		if (push) n++;
		
		if ((type & 2) && reverse) {	// リバースモードあり
			if ( (type & 128) ) {	// ３画像モード（通常／フォーカス／ダウン
				n += 3;
			} else {	// ２画像モード（通常／フォーカス
				n += 2;
			}
		}
		if (type & (32+64)) n = textureStart+imageOffset;
		if ((type & 128) && bIn) n++;
		return textureLoader.get(n);
	}

	/// ボタンが押されたときの通知
	void onLButtonClick() {}
	void onRButtonClick() {}

	/// ボタンの入力状態の取得
	override bool isLClick() { return l_click; }
	override bool isRClick() { return r_click; }

	/// フォーカス対象となる矩形を返却
	override Rect* focusRect() {
		Rect* rc = new Rect();
		Texture t = getMyTexture(false,false);
		if (t is null) {
			return rc;
		}
		rc.right = t.getWidth();
		rc.bottom = t.getHeight();
		
		return rc; 
	}

	/// コンストラクタ	
	this() {
		blink = new RootCounter;
		blink.set(0,24,1);
		blink.setReverse(true);
	}

protected:
	/// 初期化処理
	void onInit() { 
		l_click = false; 
		r_click = false; 
	}
	/// 左ボタンクリック処理
	void onLBClick() {
		if (type == 0) return; // 無効ボタン
		l_click = true;
		onLButtonClick();
	}
	/// 右ボタンクリック処理
	void onRBClick() {
		if (type == 0) return; // 無効ボタン
		r_click = true;
		onLButtonClick();
	}
	/// 左ボタン押し下げ処理
	void onLBDown() {
		if (!(type & 8) || (type & 16)) return;	// 無効ボタン
//		l_click = true;
		onLButtonClick();	// サブクラスに委譲
	}

	TextureLoader textureLoader;
	int textureStart;

	int type;
	/**
	 *	0:無効
	 *	+1:On/Offボタン(通常ボタン)
	 *	+2:On1/Off1,On2/Off2の４つの表示を持つボタン(On2/Off2はReverseモード)
	 *		（このタイプでなければ、SetReverseは無効になる。
	 *	+4:YGA画像の場合、画像のα≠0の部分だけ有効（通常は画像の矩形全体が有効）
	 *	+8:On/Offボタンだが、カーソルを上に置くとアクティブになり、
	 *		左クリック押し下げの瞬間に押したことになる。
	 *		（このタイプでなければ、WindowsGUIボタン互換となる）
	 *	+16:点滅(On/Offを繰り返す)。入力は受付けない。押されたことを示すのに使う。
	 *		+8のカーソルを押すと押し下げ状態になるボタンで使うと効果的。
	 *		点滅スピードはSetBlinkSpeedで設定する
	 *	+32:入力情報を完全に無視して SetImageOffsetで設定されたボタンを表示。
	 *  +64:入力は行なうが、表示は常にSetImageOffsetで設定されたボタンを表示する。
	 *  +128: 押し下げ状態画像付き
  　 */

	bool reverse;
	bool l_click;
	bool r_click;
	RootCounter blink;
	int imageOffset;
}


/// ボタンの文言を動的に作成するようにフォントによって動かすもの
class GUIFontButtonListener : GUINormalButtonListener {
	
	/// ボタンを生成するために使用するフォントクラスを設定
	void setFontLoader(FontLoader fl,int no) {
		m_fontloader = fl;
		m_no = no;
	}
	
	/// アクティブになったときに背景に描画するテクスチャを設定
	void setActiveBackTexture(TileTexture tex_) {
		m_activeBg = tex_;
	}
	
	/// これをtrueに設定すると常に、選択状態を保つ
	void setActiveAlways(bool b) {
		m_alwaysActive = b;
	}
	
	/// ボタンに表示する文言の設定
	void setText(char[] text) {
		m_text = text;
		Surface surface = m_fontloader.get(m_no).drawBlendedUTF8(.toUTF8(text));
		if (m_texture) {
			m_texture.release();
		}
		m_texture = new Texture();
		m_texture.setSurface( surface );
	}
	
	/// フォントカラーを設定する
	void setColor(char[] active, char[] non_active) {
		m_activeColor = Color4ub.valueOf(active);
		m_nonActiveColor = Color4ub.valueOf(non_active);
	}

	/// 描画処理
	override void onDraw(Screen screen,int x,int y,bool push,bool bin) {
		if (type == 0) return;
		
		Color4ub colorOrg = screen.getColor4ub();
	
		if (bin || m_alwaysActive) {
			drawButtonBg(screen, x, y);
			screen.setColor(m_activeColor);
		} else {
			screen.setColor(m_nonActiveColor);
		} 
		
		screen.blt(getMyTexture(push,bin),x,y);
		
		screen.setColor(colorOrg);
	}
	
	/// 現在の状態より、表示すべきテクスチャを返却する
	override Texture getMyTexture(bool push, bool bIn) {
		return m_texture;
	}

	/// フォーカス対象となる矩形を返却
	override Rect* focusRect() {
		Rect* rc = new Rect();
		Texture t = getMyTexture(false,false);
		if (t is null) {
			return rc;
		}
		rc.right = t.getWidth();
		rc.bottom = t.getHeight();
		
		return rc; 
	}
	
private:

	/// アクティブ背景テクスチャの描画
	void drawButtonBg(Screen screen, int x, int y) {
		if (m_activeBg is null) return;
		
		int tw = cast(int) m_texture.getWidth();
		int th = cast(int) m_texture.getHeight();
		
		PointInt[4] pts;
		// 描画する大きさ
		pts[0].x = x - ACT_BG_MARGIN_X;
		pts[0].y = y - ACT_BG_MARGIN_Y;
		pts[1].x = x + tw + ACT_BG_MARGIN_X;
		pts[1].y = pts[0].y;
		pts[2].x = pts[1].x;
		pts[2].y = y + th + ACT_BG_MARGIN_Y;
		pts[3].x = pts[0].x;
		pts[3].y = pts[2].y;
		
		// タイルとして描画
		m_activeBg.blt(screen, pts[0].x, pts[0].y,
			pts[1].x - pts[0].x,
			pts[3].y - pts[0].y
			);
	}
	
private :
	static final ACT_BG_MARGIN_X = 8;
	static final ACT_BG_MARGIN_Y = 2;

	bool m_alwaysActive;
	FontLoader m_fontloader;
	int m_no;	//!< 使用するフォント番号
	char[] m_text;
	Texture m_texture;
	TileTexture m_activeBg;
	
	Color4ub m_activeColor;
	Color4ub m_nonActiveColor;
	
}

/**
	Windows互換のGUIボタンを表現するクラス
*/
class GUIButton : IGUIParts {
public:

	/// 設定すべきリスナ
	void setEvent(GUIButtonEventListerner v) {
		reset();
		buttonEvent = v;
	}

	void setLeftClick(bool b) { leftClick = b; }
	void setRightClick(bool b) { rightClick = b; }

	GUIButtonEventListerner getEvent() { return buttonEvent; }
	bool isPushed() { return pushed; }
	bool isIn() { return bin; }

	bool isLClick() { return buttonEvent.isLClick(); }
	bool isRClick() { return buttonEvent.isRClick(); }

	bool isFocusing() { return focusing; }
	
	
	/// フォーカス処理を実装する
	override void focus() 
	in 
	{
		// プロトタイプだけ作るのは、バグの温床
		assert( !(this.mouse is null) );
		assert( !(this.buttonEvent is null) );
	}
	body
	{
		Rect* rc = this.buttonEvent.focusRect();
		int mx = x + xOffset + cast(int) (rc.getWidth() / 2);
		int my = y + yOffset + cast(int) (rc.getHeight() / 2);
		mouse.setPos(mx, my);
	}

	/// 移動処理
	override void onMove(Screen screen) {
		if (buttonEvent is null) return;
		// マウスが設定されていない
		if (mouse is null) return;

		try {
			// 初期化
			buttonEvent.onInit();
			
			int mx,my,b,x,y;
			mouse.getPos(mx,my);
			bool mouseL = mouse.isPress(MouseInput.button.left);
			bool mouseR = mouse.isPress(MouseInput.button.right);
	
			bool RUp,LUp,RDown,LDown;
	
			RUp = mouse.isRButtonUp();
			RDown = cast(bool) (!buttonR && mouseR);
			LUp = mouse.isLButtonUp();
			LDown = cast(bool) (!buttonL && mouseL);
			
			getXY(x,y);
			bool bIn = buttonEvent.isButton(mx-(x+xOffset),my-(y+yOffset));
	
			//	ガードフレーム間は入力を拒否するための機構
			/* TODO */
			if (bIn) {
				if (RDown) buttonEvent.onRBDown();
				if (LDown) buttonEvent.onLBDown();
				if (RUp)	buttonEvent.onRBUp();
				if (LUp)	buttonEvent.onLBUp();
			}
	
			//	前回範囲内で押されていなくて、今回ボタン範囲内で押し下げがあった
			if (leftClick && bin && bIn && LUp) {
				pushed = true;
			}
			if (rightClick && bin && bIn && RUp) {
				pushed = true;
			}
	
			if (bIn) {
				if (LUp) {
					buttonEvent.onLBClick();
					Log.print("-- BUTTON LCLICK --");
				}
				if (RUp) {
					buttonEvent.onRBClick();
					Log.print("-- BUTTON RCLICK --");
				}
			}
	
			if(pushed) {
				if (!mouseL&&!mouseR)	pushed = false;	// ボタンおされてない
			}
	
			focusing = cast(bool) (!bin && bIn);
	
			bin = bIn;
			buttonL = mouseL;
			buttonR = mouseR;
		} catch (Object e) {
			Log.printFatal("Exception %s#onMove : [%s] ", super.toString(), e.toString());
			throw e;
		}
	}

	/// 描画処理
	override void onDraw(Screen screen) {
		if (buttonEvent is null) return;
		// 初期化
		buttonEvent.onInit();

		// マウスが設定されていない
		if (mouse is null) return;

		buttonEvent.onDraw(screen,x+xOffset,y+yOffset,pushed,bin);
	}

	void reset() {
		pushed = false;
		buttonL = buttonR = false;
		bin = false;
		focusing = false;
	}

	this() {
		leftClick = true;
		reset();
	}

protected:
	GUIButtonEventListerner buttonEvent;

private:
	bool pushed;	//!< ボタンの押し下げ情報
	bool buttonL;
	bool buttonR;
	bool bin;

	bool leftClick;
	bool rightClick;
	bool focusing;
}