///<reference path="all.ts"/>
module jg {
	/**
	 * ゲーム用描画クラス
	 */
	export class GameRenderer extends Renderer {
		/** 描画用バッファ。描画方式によっては裏画面用と表画面用の2画面 */
		buffer: HTMLCanvasElement[];
		/** 表画面用描画コンテキスト */
		fc: CanvasRenderingContext2D;
		/** 裏画面用描画コンテキスト */
		bc: CanvasRenderingContext2D;
		/** 現在のシーン。game.sceneと常に等価 */
		scene: Scene;
		/** 描画対象のゲーム */
		game: Game;
		/** 背景。廃止する可能性がある */
		bg: ImageData;
		/** DOMコンテナ */
		container: HTMLElement;
		/** 入力イベント処理用内部DOMコンテナ */
		handler: HTMLDivElement;
		/** フリップ番号。描画モードがFlipの時以外は不要 */
		flipNo: number;
		/** 描画モード。デフォルトはRenderTransferMode.Transfer */
		transferMode: RenderTransferMode;
		/** クリアを無効にする */
		disableClear: boolean;
		/** 表画面のキャンバスのサイズ。Gameのサイズと異なる場合、拡大縮小されていることになる */
		frontCanvasSize: CommonSize;
		/** 表画面のキャンバスの位置。中央寄せなどをする場合に必要な値 */
		frontCanvasOffset: CommonOffset;
		/** ハンドラのページ上の横位置 */
		_pageX: number;
		/** ハンドラのページ上の縦位置 */
		_pageY: number;

		/**
		 * コンストラクタ
		 * @param game 描画対象のゲーム
		 * @param container 親元のDOMコンテナ。指定しない場合、jgameというIDの要素を検索し、それも見つからない場合はjgameというIDのおDIV要素を作りdocument.bodyに追加する
		 * @param transferMode 描画転送モード
		 * @param disableBg trueを指定すると、毎回背景色でクリアする処理を行わない
		 */
		constructor(game:Game, container?:HTMLElement, transferMode?:RenderTransferMode, disableBg?:boolean) {
			super();
			this.game = game;
			this.container = container ? container : document.getElementById("jgame");
			if (! this.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);
				this.container = div;
			}

			this.handler = <HTMLDivElement>document.createElement("div");
			this.handler.style.display = "inline-block";
			this.container.appendChild(this.handler);

			this.changeTransferMode(transferMode ? transferMode : RenderTransferMode.Transfer);

			if (!disableBg) {
				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;
			}
		}

		/**
		 * 表用キャンバスのサイズを変更する
		 * @param size 新しいキャンバスのサイズ
		 * @param offset 省略可。新しいキャンバスのオフセット位置
		 */
		changeFrontCanvasSize(size:CommonSize, offset?:CommonOffset) {
			this.frontCanvasSize = size;
			this.frontCanvasOffset = offset;
			this.refresh();
		}

		/**
		 * 描画転送モードを変更する
		 * @param mode 変更後の描画転送モード
		 */
		changeTransferMode(mode:RenderTransferMode) {
			this.transferMode = mode;
			if (this.transferMode == RenderTransferMode.Flip) {
				this.handler.style.position = "relative";
				this.handler.style.width = this.game.width + "px";
				this.handler.style.height = this.game.height + "px";
			}
			this.refresh();
		}

		/**
		 * シーンを変更する。通常、Gameクラス内からのみ呼び出されるべきメソッド。
		 * @param scene 変更後のシーン
		 */
		changeScene(scene:Scene) {
			this.scene = scene;
		}

		/**
		 * 表画面と裏画面を入れ替える。描画転送モードがRenderTransferMode.Flipの場合にのみ利用
		 */
		flip() {
			var c = this.fc;
			this.fc = this.bc;
			this.bc = this.fc;
			this.flipNo = this.flipNo ? 0 : 1;

			//ちらつくし遅い
			//this.buffer[this.flipNo].style.zIndex = "1";
			//this.buffer[this.flipNo].style.visibility = "visible";
			//this.buffer[this.flipNo ? 0 : 1].style.zIndex = "0";
			//this.buffer[this.flipNo ? 0 : 1].style.visibility = "hidden";

			//遅い
			this.buffer[this.flipNo].style.zIndex = "1";
			this.buffer[this.flipNo ? 0 : 1].style.zIndex = "0";

			//ちらつく上にかなり遅い
			//this.buffer[this.flipNo].style.display = "block";
			//this.buffer[this.flipNo ? 0 : 1].style.display = "none";

			//ちらつく
			//this.buffer[this.flipNo].style.visibility = "visible";
			//this.buffer[this.flipNo ? 0 : 1].style.visibility = "hidden";
		}

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

				if (hasUpdate) {
					if (!this.disableClear)
						this.bc.putImageData(this.bg, 0, 0);
					for (var i in this.scene.layers) {
						var layer = this.scene.layers[i];
						if (layer.isUpdated) {
							layer.context.clearRect(0, 0, layer.width, layer.height);
							this.renderParent(layer, layer.context);
						}
						this.bc.drawImage(layer.canvas, layer.x, layer.y);
						layer.isUpdated = false;
					}
				}
			}

			if (hasUpdate) {
				if (this.filter) {
					//フィルタがある場合、転送モードの設定は無効になる
					var imageData = this.bc.getImageData(0, 0, this.game.width, this.game.height);
					this.filter.filter(imageData);
					this.fc.putImageData(imageData, 0, 0);
				} else {
					if (this.bc != this.fc) {
						this.fc.drawImage(this.buffer[1], 0, 0);
					} else if (this.transferMode == RenderTransferMode.Flip) {
						this.flip();
					}
				}
			}
		}

		/**
		 * バッファの作り直しなど、現在の設定値に基づいた再構成処理を行う
		 */
		refresh() {
			delete this.buffer;
			this.buffer = [];

			if (this.transferMode == RenderTransferMode.Flip) {
				this.handler.innerHTML = "";
				for (var i=0; i<2; i++) {
					this.buffer[i] = window.createCanvas(this.game.width, this.game.height);;
					this.buffer[i].style.position="absolute";
					this.buffer[i].style.zIndex = i.toString();
					this.handler.appendChild(this.buffer[i]);
				}
				this.fc = this.buffer[1].getContext("2d");
				this.bc = this.buffer[0].getContext("2d");
				this.flipNo = 1;
				if (this.frontCanvasSize) {
					JGUtil.scaleCanvas(this.buffer[1], this.frontCanvasSize);
				}
			} else if (this.transferMode == RenderTransferMode.Transfer) {
				this.handler.innerHTML = "";
				for (var i=0; i<2; i++) {
					this.buffer[i] = window.createCanvas(this.game.width, this.game.height);;
				}
				this.handler.appendChild(this.buffer[0]);
				this.fc = this.buffer[0].getContext("2d");
				this.bc = this.buffer[1].getContext("2d");
			} else {
				this.handler.innerHTML = "";
				this.buffer[0] = window.createCanvas(this.game.width, this.game.height);;
				this.handler.appendChild(this.buffer[0]);
				this.fc = this.buffer[0].getContext("2d");
				this.bc = this.fc;
			}
			if (this.frontCanvasSize) {
				JGUtil.scaleCanvas(this.buffer[0], this.frontCanvasSize);
				if (this.frontCanvasOffset) {
					this.handler.style.position = "relative";
					this.handler.style.left = this.frontCanvasOffset.x+"px";
					this.handler.style.top = this.frontCanvasOffset.y+"px";
				}
			}

			var bounding = this.handler.getBoundingClientRect();
			this._pageX = Math.round(window["scrollX"] || window.pageXOffset + bounding.left);
			this._pageY = Math.round(window["scrollY"] || window.pageYOffset + bounding.top);
		}
	}
}