///<reference path="all.ts"/>
module jg {
	/**
	 * 一行の情報
	 */
	export class TextLineInfo {
		/** 行の幅 */
		width: number;
		/** 行の高さ */
		height: number;
		/** 縦座標のオフセット値 */
		offsetY: number;

		/**
		 * コンストラクタ
		 * @param offsetY 縦座標のオフセット値
		 */
		constructor(offsetY:number) {
			this.width = 0;
			this.height = 0;
			this.offsetY = offsetY;
		}
	}

	/**
	 * スクリプトを解析するクラス。
	 * このクラスはサンプルであり、#pageコマンドによる改ページのみしかサポートしていない。
	 */
	export class MultilineScriptAnalyzer {
		/** 現在のモード */
		mode: number;
		/** 対象のMultilineTextクラス */
		owner: MultilineText;
		/** 対象の描画コンテキスト */
		context: CanvasRenderingContext2D;
		/** ポジション */
		pos: CommonOffset;
		/** バッファ */
		buf: string;

		/**
		 * 初期化
		 * @param owner 対象のMultilineTextクラス
		 * @param context 描画コンテキスト
		 * @param pos ポジション
		 */
		init(owner:MultilineText, context:CanvasRenderingContext2D, pos:CommonOffset) {
			this.mode = 0;
			this.owner = owner;
			this.context = context;
			this.pos = pos;
		}

		/**
		 * 次の文字を判定する
		 */
		next(c:string):number {
			if (this.mode) {
				if (c == " ") {
					this.mode = 0;
					if (this.buf == "page")
						return -1;
				} else
					this.buf += c;

				return 1;
			}
			if (c == "#") {
				this.mode = 1;
				this.buf = "";
				return 1;
			}
			return 0;
		}
	}

	/**
	 * 複数行のテキストを扱うクラス
	 */
	export class MultilineText extends E {
		/** 元スクリプト */
		script: string;
		/** 裏画面バッファ */
		buffer: HTMLCanvasElement;
		/** クリッピング用Line */
		clip: Line;
		/** テキスト転送用Sprite */
		sprite: Sprite;

		/** デフォルトのスタイル */
		defaultStyle: any;
		/** デフォルトのフォント */
		defaultFont: any;
		/** デフォルトの影 */
		defaultBlur: number;
		/** デフォルトの影色 */
		defaultShadowColor: any;
		/** デフォルトの影オフセットX */
		defaultShadowOffsetX: number;
		/** デフォルトの影オフセットY */
		defaultShadowOffsetY: number;
		/** テキストの影を無効にするかどうか */
		disableShadow: boolean;

		/** 全行情報 */
		lines: TextLineInfo[];
		/** 現在アニメーション中のポジション */
		animePos: CommonOffset;
		/** 現在アニメーション中の行 */
		animeLine: number;
		/** テキストのアニメーションスピード。デフォルトは400 */
		animeSpeed: number;

		/** アニメーション完了時に発火されるイベント */
		animated: Trigger;
		/** スクリプト解析クラス */
		scriptAnalyzer: MultilineScriptAnalyzer;
		/** バッファの背景 */
		bufferBg: ImageData;
		/** 通常の行の高さ */
		static LINE_HEIGHT_NORMAL: number = 1.2;
		/** 特殊なブラウザにおける余白 */
		static BROWSER_BASELINE_MARGIN: number = 0;

		/**
		 * コンストラクタ
		 * @param size 表示サイズ
		 * @param offset 場所
		 */
		constructor(size:CommonSize, offset?:CommonOffset) {
			super();
			this.scriptAnalyzer = new MultilineScriptAnalyzer();
			this.width = size.width;
			this.height = size.height;
			if (offset) 
				this.moveTo(offset.x, offset.y);
			else
				this.moveTo(0, 0);

			//this.disableShadow = true;
			this.defaultStyle = "#000";
			this.defaultFont = "18px sans-serif";//this.getDrawOption("font");
			this.defaultBlur = 0.6;
			this.defaultShadowColor = "#000";
			this.defaultShadowOffsetX = 0.3;
			this.defaultShadowOffsetY = 0.3;

			//とりあえず全表示設定
			this.clip = new Line({x:0, y:0});
			this.clip.addLine(this.width, 0);
			this.clip.addLine(this.width, this.height);
			this.clip.addLine(0, this.height);
			this.clip.addLine(0, this.height);
			this.clip.addLine(0, this.height);
			this.clip.closePath = true;
			this.clip.setClip(true);

			this.entities = [];
			this.entities.push(this.clip);
			this.animeSpeed = 400;

			this.animated = new Trigger();
		}

		/**
		 * テキストをセットする
		 * @param text 設定する文字列
		 * @param offset 読み込み開始位置。省略時は最初から
		 */
		setText(text:string, offset?:number):number {
			//TODO: create plain script
			var plainScript = text;
			return this.setScript(plainScript, offset);
		}

		/**
		 * スクリプトをセットする
		 * @param script 設定するスクリプト
		 * @param offset 読み込み開始位置。省略時は最初から
		 */
		setScript(script:string, offset?:number):number {
			this.script = script.replace(/\r\n?/g, "\n");
			this.updated();
			return this.createBuffer(offset);
		}

		/**
		 * 行の高さを取得する
		 * @param c 対象の描画コンテキスト
		 */
		getLineHeight(c:CanvasRenderingContext2D):number {
			var font = c.font;
			var firstPos = font.indexOf("px");
			var lastPos = font.lastIndexOf(" ", firstPos);
			if (lastPos < 0)
				lastPos = 0;
			if (firstPos < 0)
				return 16;	//バグっとる
			var fontSize = parseInt(font.substring(lastPos, firstPos));
			//line-heightはどうもnormal(= 1.2)固定になるらしい
			//https://developer.mozilla.org/ja/docs/CSS/line-height （ほんとか？）
			var line_height = Math.round(fontSize * MultilineText.LINE_HEIGHT_NORMAL);
			return line_height;
		}

		/**
		 * バッファを生成する
		 * @param offset 読み込み開始位置。省略時は最初から
		 */
		createBuffer(offset?:number):number {
			if (! this.buffer)
				this.buffer = window.createCanvas(this.width, this.height);
			if (offset === undefined)
				offset = 0;

			var script = this.script;
			var len = script.length;
			var pos = {x: 0, y: 0}
			var c = this.buffer.getContext("2d");
			var s;
			var m = MultilineText.BROWSER_BASELINE_MARGIN;
			this.lines = [];

			if (this.bufferBg)
				c.putImageData(this.bufferBg, 0, 0);
			else
				c.clearRect(0, 0, this.width, this.height);

			c.fillStyle = this.defaultStyle;
			c.font = this.defaultFont;
			c.textBaseline = "top";
			if (! this.disableShadow) {
				c.shadowBlur = this.defaultBlur;
				c.shadowColor = this.defaultShadowColor;
				c.shadowOffsetX = this.defaultShadowOffsetX;
				c.shadowOffsetY = this.defaultShadowOffsetY;
			}

			var lineHeight = this.getLineHeight(c);
			var lineInfo = new TextLineInfo(0);
			lineInfo.height = lineHeight;
			this.lines.push(lineInfo);

			var _newLine = ():boolean => {
				pos.x = 0;
				pos.y += lineInfo.height;	//lineHeight
				if ((pos.y+lineInfo.height) > this.height)
					return false;
				lineInfo = new TextLineInfo(pos.y);
				lineInfo.height = lineHeight;
				this.lines.push(lineInfo);
				return true;
			}

			this.scriptAnalyzer.init(this, c, pos);
			while (offset < len) {
				s = script.substr(offset, 1);

				var script_ret = this.scriptAnalyzer.next(s);
				if (script_ret) {
					lineHeight = lineInfo.height;	//スクリプト側でフォントサイズ変更した場合などの処置
					if (script_ret < 0) {
						offset -= script_ret;
						break;
					}
					offset += script_ret;
					continue;
				}
				if (s == "\n") {
					offset++;
					if (! _newLine())
						break;
					continue;
				}

				var metric = c.measureText(s);
				if ((pos.x+metric.width) > this.width) {
					if (! _newLine())
						break;
				}
				//マニュアルシャドウの例。しかし全然綺麗に出ない。
				//c.fillStyle = "#fff";
				//c.fillText(s, pos.x+1, pos.y+m+1);
				//c.fillStyle = this.defaultStyle;
				c.fillText(s, pos.x, pos.y+m);
				pos.x += metric.width;
				lineInfo.width += metric.width;

				offset++;
			}

			this.sprite = new Sprite(this.buffer);
			this.sprite.moveTo(0, 0);
			if (this.entities.length == 1)
				this.entities.push(this.sprite);
			else
				this.entities[1] = this.sprite;

			return offset == len ? -1 : offset;
		}

		/**
		 * バッファの再構築を行う
		 */
		refresh() {
			delete this.buffer;
			this.createBuffer();
		}

		/**
		 * テキストのアニメーション表示を開始する
		 * @param animeSpeed アニメーションのスピード。省略時は前回の設定から変更しない
		 */
		startAnimation(animeSpeed?:number) {
			this.start();
			this.animeLine = 0;
			this.animePos = {x: 0, y: this.lines[this.animeLine].height}
			if (animeSpeed !== undefined)
				this.animeSpeed = animeSpeed;
			this.hideAll();
			this.clip.p[4].y = this.animePos.y;
			this.clip.p[5].y = this.animePos.y;
		}

		/**
		 * Game.updateイベントに対するコールバック
		 * @param t 経過時間
		 */
		update(t:number) {
			this.animePos.x += this.animeSpeed / 1000 * t;
			if (this.animePos.x >= this.lines[this.animeLine].width) {
				this.animePos.x = 0;
				this.animePos.y += this.lines[this.animeLine].height;
				this.animeLine++;

				if (this.animeLine < this.lines.length) {
					this.clip.p[2].y = this.lines[this.animeLine].offsetY;
					this.clip.p[3].y = this.clip.p[2].y;
					this.clip.p[4].y = this.animePos.y;
					this.clip.p[5].y = this.animePos.y;
				}
			}

			if (this.animeLine >= this.lines.length) {
				this.showAll();
			} else {
				this.clip.p[3].x = this.animePos.x;
				this.clip.p[4].x = this.clip.p[3].x;
			}

			this.updated();
		}

		/**
		 * テキストをすべて非表示にする
		 */
		hideAll() {
			this.clip.p[0] = {x:0, y:0};
			this.clip.p[1] = {x:this.width, y:0};
			this.clip.p[2] = {x:this.width, y:0};
			this.clip.p[3] = {x:0, y:0};
			this.clip.p[4] = {x:0, y:0};
			this.clip.p[5] = {x:0, y:0};
		}

		/**
		 * テキストをすべて表示する
		 */
		showAll() {
			this.clip.p[0] = {x:0, y:0};
			this.clip.p[1] = {x: this.width, y: 0};
			this.clip.p[2] = {x: this.width, y: this.height};
			this.clip.p[3] = {x: 0, y: this.height};
			this.clip.p[4] = {x: 0, y: this.height};
			this.clip.p[5] = {x: 0, y: this.height};
			this.stop();
			this.animated.fire();
		}
	}
}