class TextLineInfo {
	width:number;
	height:number;
	offsetY:number;
	constructor(offsetY:number) {
		this.width = 0;
		this.height = 0;
		this.offsetY = offsetY;
	}
}
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;
	}
}
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.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.fillText(s, pos.x, pos.y+m);
			pos.x += metric.width;
			lineInfo.width += metric.width;

			offset++;
		}

		this.sprite = new Sprite(this.width, this.height, 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();
	}
}
