///<reference path="all.ts"/>
module jg {
	/**
	 * シーンを表すクラス
	 */
	export class Scene {
		/** 管理している全レイヤー */
		layers: {[key:string]: Layer; };
		/** レイヤー数 */
		layerCount: number;
		/** このシーンを管理しているゲーム */
		game: Game;
		/** スタックされたモード */
		mode: string[];
		/** 表示された時に発生するイベントハンドラ。startedとは異なり、endSceneで自身が再表示された時に発生する */
		showed: Trigger;
		/** 隠された時に発生するイベントハンドラ。changeSceneで自分が隠された時に発生する */
		hid: Trigger;
		/** このシーンが終了した時に発生するイベントハンドラ */
		ended: Trigger;
		/** このシーンが開始された時に発生するイベントハンドラ */
		started: Trigger;
		/** ルートレイヤー */
		root: Layer;
		/** キーが押された場合のイベントハンドラ。通常はundefinedであるため、利用する場合は自前でscene.keyDown = new jg.Trigger()を行う必要がある */
		keyDown: Trigger;
		/** キーが離された場合のイベントハンドラ。通常はundefinedであるため、利用する場合は自前でscene.keyDown = new jg.Trigger()を行う必要がある */
		keyUp: Trigger;
		/** ポイントが押された場合のイベントハンドラ。enablePointingEventを呼び出すまではundefined */
		pointDown: Trigger;
		/** ポイントが離された場合のイベントハンドラ。enablePointingEventを呼び出すまではundefined */
		pointUp: Trigger;
		/** ポイントが移動した場合のイベントハンドラ。enablePointingEventを呼び出すまではundefined */
		pointMove: Trigger;

		/**
		 * コンストラクタ
		 * @param game 関連するgame
		 */
		constructor(game:Game) {
			this.game = game;
			this.layers = {};
			this.root = new Layer(this);
			this.layers["root"] = this.root;
			this.layerCount = 1;
			this.mode = [];
			this.started = new Trigger();
			this.ended = new Trigger();
			this.showed = new Trigger();
			this.hid = new Trigger();
		}

		/**
		 * 現在のモードを返す
		 */
		currentMode():string {
			return this.mode.length == 0 ? null : this.mode[this.mode.length - 1];
		}

		/**
		 * 管理しているレイヤーを配列にして返す
		 */
		getLayerArray():Layer[] {
			var ret:Layer[] = [];
			for (var i in this.layers)
				ret.push(this.layers[i]);
			return ret;
		}

		/**
		 * ポインティングイベントを有効にする。
		 * この処理はrootレイヤーのポインティングイベントも有効にする点に注意
		 */
		enablePointingEvent() {
			this.root.enablePointingEvent();
			if (! this.pointDown)
				this.pointDown = new Trigger();
			if (! this.pointMove)
				this.pointMove = new Trigger();
			if (! this.pointUp)
				this.pointUp = new Trigger();
		}

		/**
		 * ポインティングイベントを無効にする。実際はrootレイヤーのポインティングイベントを無効にしている。
		 * sceneクラスのpointDown, pointUp, pointMoveのイベントハンドラは解放されないため、解放処理は別途行う必要がある点に注意
		 */
		disablePointingEvent() {
			this.root.disablePointingEvent();
		}

		/**
		 * モードを変更する。
		 * この処理を行った際、引数modeがgameであり、現在のmodeがtitleである場合、以下のメソッドが本クラスのインスタンスに存在する場合には呼び出される。
		 * titleHide
		 * gameStart
		 * gameShow
		 * @param mode 新しいモード
		 */
		changeMode(mode:string) {
			var linkMode = this.currentMode();
			if (linkMode && this[linkMode+"Hide"])
				this[linkMode+"Hide"]();

			this.mode.push(mode);
			if (mode) {
				if (this[mode+"Start"])
					this[mode+"Start"]();

				if (this[mode+"Show"])
					this[mode+"Show"]();
			}
		}

		/**
		 * 現在のモードを終了する。すべてのモードが終了した場合、シーン自体も終了させる。
		 * この処理を行った際、現在のmodeがgameであり、次のモードがtitleである場合、以下のメソッドが本クラスのインスタンスに存在する場合には呼び出される。
		 * gameEnd
		 * titleShow
		 * ただし、newMode引数を指定した場合titleShowは発生しない。
		 * @param newMode 次のモードを指定する。省略時は変更しない
		 */
		endCurrentMode(newMode?:string) {
			if (this.mode.length == 0) {
				this.end();
				return;
			}

			var mode = this.mode.pop();
			if (mode)
				if (this[mode+"End"])
					this[mode+"End"]();

			var linkMode = this.currentMode();
			if (newMode !== undefined)
				this.changeMode(newMode);
			else if (linkMode && this[linkMode+"Show"])
				this[linkMode+"Show"]();
		}

		/**
		 * 指定したレイヤーを作成する
		 * @param name 作成するレイヤー名
		 * @param size レイヤーの大きさ。またはLayerクラスのインスタンス
		 */
		createLayer(name:string, size?:CommonSize):Layer {
			for (var i in this.layers)
				if (! this.layers[i].hasBuffer())
					this.layers[i].createBuffer();

			if (size && size instanceof Layer) {
				this.layers[name] = <Layer>size;
				this.layers[name].scene = this;
			} else {
				this.layers[name] = new Layer(this);
				if (size) {
					this.layers[name].width = size.width;
					this.layers[name].height = size.height;
				}
			}
			this.layers[name].createBuffer();
			this.layerCount++;
			return this.layers[name];
		}

		/**
		 * 指定した名前のレイヤーを削除する。rootレイヤーは削除できない
		 * @param name 削除するレイヤー名
		 */
		deleteLayer(name:string) {
			if (name == "root")
				throw "can not delete root layer";

			this.layers[name].destroy();
			delete this.layers[name];
			this.layerCount--;
			if (this.layerCount == 1)
				this.root.deleteBuffer();
		}

		/**
		 * このシーンとシーンに関連するリソースをすべて削除する
		 */
		destroy() {
			for (var i in this.layers)
				this.layers[i].destroy();
			if (this.keyDown)
				this.keyDown.destroy();
			if (this.keyUp)
				this.keyUp.destroy();
			if (this.pointDown)
				this.pointDown.destroy();
			if (this.pointUp)
				this.pointUp.destroy();
			if (this.pointMove)
				this.pointMove.destroy();
		}

		/**
		 * このシーンを終了する
		 */
		end() {
			this.game.endScene();
		}

		/**
		 * このシーンとそれに関連するリソースをすべて再構築する
		 */
		refresh() {
			for (var i in this.layers) {
				this.layers[i].refresh();
				this.layers[i].updated();
			}
		}

		/**
		 * シーンをスクロールする
		 * @param x スクロール先X座標
		 * @param y スクロール先Y座標
		 * @param layerName レイヤー名を指定する。省略した場合rootレイヤーが対象になる
		 */
		scrollTo(x:number, y:number, layerName?:string) {
			if (! layerName)
				layerName = "root";

			this.layers[layerName].scrollTo(x, y);
		}

		/**
		 * シーンを相対的にスクロールする
		 * @param x X座標の変化値
		 * @param y Y座標の変化値
		 * @param layerName レイヤー名を指定する。省略した場合rootレイヤーが対象になる
		 */
		scrollBy(x:number, y:number, layerName?:string) {
			if (x == 0 && y == 0)
				return;

			if (! layerName)
				layerName = "root";

			this.layers[layerName].scrollBy(x, y);
		}

		/**
		 * このシーンにentityを追加する
		 * @param entity 追加するentity
		 * @param layerName 追加するレイヤー名。省略時はrootレイヤー
		 */
		append(entity:E, layerName?:string) {
			if (! layerName)
				this.root.append(entity);
			else
				this.layers[layerName].append(entity);
		}

		/**
		 * このシーンからentityを削除する。
		 * どのレイヤーにあっても必ず削除されるが、entity.remove()の利用を推奨。
		 * @param entity 削除するentity
		 */
		removeEntity(entity:E) {
			for (var i in this.layers)
				this.layers[i].removeChild(entity);
		}
	}
}