module jg {
	export class TextLineInfo {
		width:number;
		height:number;
		offsetY:number;
		constructor(offsetY:number) {
			this.width = 0;
			this.height = 0;
			this.offsetY = offsetY;
		}
	}

	export class MultilineScriptAnalyzer {
		mode:number;
		owner:MultilineText;
		context:CanvasRenderingContext2D;
		pos:CommonOffset;
		buf:string;

		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;
		clip:Line;
		sprite:Sprite;

		defaultStyle:any;
		defaultFont:any;
		defaultBlur: number;
		defaultShadowColor: any;
		defaultShadowOffsetX: number;
		defaultShadowOffsetY: number;
		disableShadow:bool;

		lines:TextLineInfo[];
		animePos:CommonOffset;
		animeLine:number;
		animeSpeed:number;

		animated:Trigger;
		scriptAnalyzer:MultilineScriptAnalyzer;
		bufferBg:ImageData;
		static LINE_HEIGHT_NORMAL:number = 1.2;
		static BROWSER_BASELINE_MARGIN:number = 0;

		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 = new E[];
			this.entities.push(this.clip);
			this.animeSpeed = 400;

			this.animated = new Trigger();
		}

		setText(text:string, offset?:number):number {
			//TODO: create plain script
			var plainScript = text;
			return this.setScript(plainScript, offset);
		}

		setScript(script:string, offset?:number):number {
			this.script = script.replace(/\r\n?/g, "\n");
			this.updated();
			return this.createBuffer(offset);
		}

		getLineHeight(c):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;
		}

		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 = new TextLineInfo[];

			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 = ():bool => {
				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();
		}

		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;
		}

		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();
		}
	}
}