class Renderer {
	buffer: HTMLCanvasElement;
	bc: CanvasRenderingContext2D;
	front: HTMLCanvasElement;
	fc: CanvasRenderingContext2D;
	scene: Scene;
	game: Game;
	bg:ImageData;
	radian:number;
	drawOptionFunctions:Object;

	constructor(game:Game) {
		this.game = game;
		var canvas = window.createCanvas(this.game.width, this.game.height);
		var exists_container = document.getElementById("jgame");
		if (! exists_container) {
			var div = document.createElement("div");
			div.id = "jgame";
			var bodies = document.getElementsByTagName("body");
			if (bodies.length == 0) {
				throw "can not initialize game engine";
			}
			bodies[0].appendChild(div);
			exists_container = div;
		}
		exists_container.appendChild(canvas);
		this.front = canvas;
		this.fc = this.front.getContext("2d");
		this.bg = this.fc.getImageData(0, 0, this.game.width, this.game.height);
		for (var i=0; i<this.bg.data.length; i++) {
			this.bg.data[i] = 255;
		}

		this.buffer = window.createCanvas(this.game.width, this.game.height);
		this.bc = this.buffer.getContext("2d");
		this.radian = Math.PI / 180;

		this.drawOptionFunctions = {
			transform: (c:CanvasRenderingContext2D, entity:E, params:any) => {
				c.transform(
					params.m11,
					params.m12,
					params.m21,
					params.m22,
					params.dx,
					params.dy
				);
			},
			translate: (c:CanvasRenderingContext2D, entity:E, params:any) => {
				c.translate(params.x, params.y);
			},
			scale: (c:CanvasRenderingContext2D, entity:E, params:any) => {
				//c.scale(params.x, params.y);
				c.transform.apply(c, this.getMatrix(
					entity.width,
					entity.height,
					params.x,
					params.y,
					0
				));
			},
			rotate: (c:CanvasRenderingContext2D, entity:E, params:any) => {
				//c.rotate(params);
				c.transform.apply(c, this.getMatrix(
					entity.width,
					entity.height,
					1,
					1,
					params
				));
			}
		}
	}

	changeScene(scene:Scene) {
		this.scene = scene;
	}

	getMatrix(width:number, height:number, scaleX:number, scaleY:number, angle:number) {
		var r = angle / 180 * Math.PI;
		var _cos = Math.cos(r);
		var _sin = Math.sin(r);
		var a = _cos * scaleX;
		var b = _sin * scaleX;
		var c = _sin * scaleY;
		var d = _cos * scaleY;
		var w = width / 2;
		var h = height / 2;
		return [
			a,
			b,
			-c,
			d,
			(-a * w + c * h + w),
			(-b * w - d * h + h)
		]
	}

	render() {
		if (this.scene.layerCount == 1) {
			// 単一レイヤーの場合、バックバッファに直接描く
			var layer = this.scene.layers["root"];
			if (! layer.isUpdate()) {
			} else {
				this.bc.putImageData(this.bg, 0, 0);
				this.renderLayer(layer, this.bc);
				layer.reflected();
				this.fc.drawImage(this.buffer, 0, 0);
			}
		} else {
			// 無駄な処理だけど、更新検知ちゃんとした方が最終的には軽い、と思う
			var hasUpdate:bool = false;
			for (var i in this.scene.layers) {
				if (this.scene.layers[i].isUpdate()) {
					hasUpdate = true;
					break;
				}
			}

			if (hasUpdate) {
				this.bc.putImageData(this.bg, 0, 0);
				for (var i in this.scene.layers) {
					var layer = this.scene.layers[i];
					if (layer.isUpdate()) {
						layer.context.clearRect(0, 0, this.game.width, this.game.height);
						this.renderLayer(layer, layer.context);
					}
					this.bc.drawImage(layer.canvas, 0, 0);
					layer.reflected();
				}

				//Note: getImageDataをするくらいならdrawImageのが速い模様
				//this.fc.putImageData(this.bc.getImageData(0, 0, this.width, this.height), 0, 0);
				this.fc.drawImage(this.buffer, 0, 0);
			}
		}
	}

	refresh() {
		this.buffer = window.createCanvas(this.game.width, this.game.height);
		this.bc = this.buffer.getContext("2d");
		this.front = window.createCanvas(this.game.width, this.game.height);
		this.fc = this.front.getContext("2d");
		var jgame = document.getElementById("jgame");
		jgame.innerHTML = "";
		jgame.appendChild(this.front);
	}

	renderLayer(layer:Layer, c:CanvasRenderingContext2D) {
		var area = new Area(
			layer.x,
			layer.y,
			this.game.width,
			this.game.height
		);
		var emptyArea = new Area(
			0,
			0,
			this.game.width,
			this.game.height
		);

		c.save();
		if (layer.options) {
			for (var p in layer.options) {
				if (this.drawOptionFunctions[p]) {
					this.drawOptionFunctions[p].call(this, c, layer, layer.options[p]);
				} else {
					c[p] = layer.options[p];
				}
			}
		}
		for (var i=0; i<layer.entities.length; i++) {
			var entity = layer.entities[i];
			if (entity.disableTransform) {
				layer.entities[i].draw(area, c);
			} else {
				if (entity.options) {
					c.save();
					c.translate(area.x + entity.x, area.y + entity.y);
					for (var p in entity.options) {
						if (this.drawOptionFunctions[p]) {
							this.drawOptionFunctions[p].call(this, c, entity, entity.options[p]);
						} else {
							c[p] = entity.options[p];
						}
					}
					layer.entities[i].draw(area, c);
					c.restore();
				} else {
					c.save();
					c.translate(area.x + entity.x, area.y + entity.y);
					layer.entities[i].draw(area, c);
					//これ以外に方法が無いけど、この方法はsave/restoreと比較して大して変わらない
					//c.translate(-(area.x + entity.x), -(area.y + entity.y));
					c.restore();
				}
			}
			/*
			//画面内なら、っていう判定をしたいなぁというコード
			var rect = new Rectangle(
				0,
				0,
				this.width,
				this.height
			);
			var rect = new Rectangle(
				layer.x + entity.x,
				layer.y + entity.y,
				layer.x + entity.x + entitiy.width,
				layer.y + entity.y + entitiy.height
			);
			*/
		}
		c.restore();
	}
}