class Game {
	//inner parameters
	_exit: bool;
	tick: number;
	renderTick: number;
	keymap: Object;

	//parameters for public
	renderer: GameRenderer;
	scenes: Scene[];
	currentScene: Scene;
	resource: Resource;
	width:number;
	height:number;

	//for heavy games
	targetFps:number;

	//debug
	fps: HTMLElement;

	//events
	loaded: Trigger;
	update: Trigger;
	timers: GameTimer[];
	render: Trigger;
	inputDown: Trigger;
	inputUp: Trigger;
	inputMove: Trigger;
	onmousedown: any;
	onmousemove: any;
	onmouseup: any;
	enterFrame: Trigger;
	enterFrameTick: number;

	//未定義パラメータとして、第三匹数以降にRenderTransferModeと、HTMLElementを指定可能（第三匹数以降であればどこでよい）
	//それぞれ、GameRendererの第二、第三匹数として利用される
	constructor(width:number, height:number) {
		this._exit = false;
		this.width = width;
		this.height = height;
		this.targetFps = 0;

		this.loaded = new Trigger();
		this.update = new Trigger();
		this.inputDown = new Trigger();
		this.inputUp = new Trigger();
		this.inputMove = new Trigger();
		this.timers = new GameTimer[];

		this.currentScene = new Scene(this);
		this.scenes = new Scene[];
		this.scenes.push(this.currentScene);

		this.resource = Resource.getInstance();

		var container:HTMLElement, transferMode:RenderTransferMode;
		for (var i=2; i<arguments.length; i++) {
			if (arguments[i] instanceof HTMLElement)
				container = <HTMLElement>arguments[i];
			else
				transferMode = <RenderTransferMode>arguments[i]
		}
		this.renderer = new GameRenderer(this, container, transferMode);
		this.renderer.changeScene(this.currentScene);

		this.keyboardHandler();
		this.pointHandler();

		if (document.getElementById("fps_show")) {
			this.fps = document.getElementById("fps_show");
		}

		this.main()
	}

	refresh() {
		if (document.addEventListener) {
			if (this.isTouchEnable()) {
				this.renderer.container.removeEventListener("mousedown" , this.onmousedown, false);
				this.renderer.container.removeEventListener("mousemove" , this.onmousemove, false);
				this.renderer.container.removeEventListener("mouseup"   , this.onmouseup  , false);
			} else {
				this.renderer.container.removeEventListener("mousedown" , this.onmousedown, false);
				this.renderer.container.removeEventListener("mousemove" , this.onmousemove, false);
				this.renderer.container.removeEventListener("mouseup"   , this.onmouseup  , false);
			}
		} else {
			if (this.isTouchEnable()) {
				this.renderer.container.detachEvent("onmousedown", this.onmousedown);
				this.renderer.container.detachEvent("onmousemove", this.onmousemove);
				this.renderer.container.detachEvent("onmouseup",   this.onmouseup  );
			} else {
				this.renderer.container.detachEvent("onmousedown", this.onmousedown);
				this.renderer.container.detachEvent("onmousemove", this.onmousemove);
				this.renderer.container.detachEvent("onmouseup",   this.onmouseup  );
			}
		}

		this.renderer.refresh();
		for (var i=0; i<this.scenes.length; i++) {
			this.scenes[i].refresh();
		}
		this.pointHandler();
	}

	//copied by enchant.js (enchant.ENV.TOUCH_ENABLED)
	isTouchEnable() {
		var div:any = document.createElement('div');
		div.setAttribute('ontouchstart', 'return');
		return typeof div.ontouchstart === 'function';
	}

	pointHandler() {
		var dragParam:InputPointEvent = null;
		/*
		var ontouchstart = (e) => {
		}
		var ontouchend = (e) => {
		}
		var ontouchmove = (e) => {
		}
		*/
		this.onmousedown = (e) => {
			var layers = this.currentScene.getLayerArray();
			var layer;
			var offset:CommonOffset = {
				x: e.offsetX,
				y: e.offsetY
			}
			while (layer = layers.pop()) {	//上のレイヤーから先に処理
				if (! layer.pointCapture)
					continue;

				var dragObj = layer.getEntityByPoint(offset);
				if (! dragObj)
					dragObj = layer;
				dragParam = new InputPointEvent(
					e,
					dragObj
				);
				this.inputDown.fire(dragParam);
				if (dragObj.inputDown) {
					dragObj.inputDown.fire(dragParam);
				}
				break;
			}

			e.preventDefault();
		}
		this.onmousemove = (e) => {
			if (! dragParam)
				return;

			var param = new InputPointEvent(
				e,
				dragParam.entity
			);
			if (dragParam.entity.inputMove) {
				dragParam.entity.inputMove.fire(param);
			}
			this.inputMove.fire(param);

			e.preventDefault();
		}
		this.onmouseup = (e) => {
			if (! dragParam)
				return;

			var param = new InputPointEvent(
				e,
				dragParam.entity
			);
			if (dragParam.entity.inputUp) {
				dragParam.entity.inputUp.fire(param);
			}
			this.inputUp.fire(param);

			dragParam = null;

			e.preventDefault();
		}

		//TODO: Can not implementation because i dont have a test devices.
		if (document.addEventListener) {
			if (this.isTouchEnable()) {
				this.renderer.container.addEventListener("mousedown" , this.onmousedown , false);
				this.renderer.container.addEventListener("mousemove" , this.onmousemove , false);
				this.renderer.container.addEventListener("mouseup"   , this.onmouseup   , false);
			} else {
				this.renderer.container.addEventListener("mousedown" , this.onmousedown , false);
				this.renderer.container.addEventListener("mousemove" , this.onmousemove , false);
				this.renderer.container.addEventListener("mouseup"   , this.onmouseup   , false);
			}
		} else {
			if (this.isTouchEnable()) {
				this.renderer.container.attachEvent("onmousedown" , onmousedown);
				this.renderer.container.attachEvent("onmousemove" , onmousemove);
				this.renderer.container.attachEvent("onmouseup"   , onmouseup);
			} else {
				this.renderer.container.attachEvent("onmousedown" , onmousedown);
				this.renderer.container.attachEvent("onmousemove" , onmousemove);
				this.renderer.container.attachEvent("onmouseup"   , onmouseup);
			}
		}
	}

	keyboardHandler() {
		this.keymap = {
			13: Keytype.enter,
			27: Keytype.esc,
			37: Keytype.left,
			38: Keytype.up,
			39: Keytype.right,
			40: Keytype.down
		}
		var onkeydown = (e) => {
			if (this.keymap[e.keyCode] != undefined) {
				this.inputDown.fire(new InputKeyboardEvent(
					this.keymap[e.keyCode]
				));
				e.preventDefault();
			}
		}
		var onkeyup = (e) => {
			if (this.keymap[e.keyCode] != undefined) {
				this.inputUp.fire(new InputKeyboardEvent(
					this.keymap[e.keyCode]
				));
				e.preventDefault();
			}
		}
		if (document.addEventListener) {
			document.addEventListener("keydown", onkeydown, false);
			document.addEventListener("keyup"  , onkeyup  , false);
		} else {
			document.attachEvent("onkeydown", onkeydown);
			document.attachEvent("onkeyup"  , onkeyup);
		}
	}

	addTimer(wait:number, owner:any, handler:Function) {
		var timer:GameTimer = null;
		for (var i=0; i<this.timers.length; i++) {
			if (this.timers[i].wait == wait) {
				timer = this.timers[i];
				break;
			}
		}
		if (timer == null) {
			timer = new GameTimer(wait);
			this.timers.push(timer);
		}
		timer.trigger.handle(owner, handler);
	}

	removeTimer(wait:number, owner:any, handler:Function) {
		var timer:GameTimer = null;
		for (var i=0; i<this.timers.length; i++) {
			if (this.timers[i].wait == wait) {
				timer = this.timers[i];
				break;
			}
		}
		if (timer == null)
			throw "error removeTimer: dont have "+wait+" timer";

		timer.trigger.remove(owner, handler);
	}

	removeTimerAll(owner:any) {
		for (var i=0; i<this.timers.length; i++) {
			this.timers[i].trigger.removeAll(owner);
		}
	}

	exit() {
		this._exit = true;
	}

	changeScene(scene:Scene) {
		this.scenes.push(scene);
		scene.game = this;
		this.currentScene.hid.fire();
		this.currentScene = scene;
		this.renderer.changeScene(this.currentScene);
		this.currentScene.started.fire();
	}

	endScene() {
		if (this.scenes.length == 1) {
			this.exit();
			return;
		}
		this.currentScene.destroy();
		this.scenes.pop();
		this.currentScene.ended.fire();
		this.currentScene = this.scenes[this.scenes.length-1];
		this.renderer.changeScene(this.currentScene);
		this.currentScene.showed.fire();
	}

	r(name:string) {
		return this.resource.get(name);
	}

	preloadArray(ary: string[], loadingScene?:LoadingScene) {
		var param = {};
		for (var i=0; i<ary.length; i++) {
			param[i] = ary[i];
		}
		this.preload(param);
	}

	preload(ary: {[key:string]: string; }, loadingScene?:Scene) {
		for (var i in ary) {
			this.resource.load(i, ary[i]);
		}
		if (! loadingScene)
			loadingScene = new LoadingScene(this, this.resource);
		this.changeScene(loadingScene);
		this.resource.loaded.handle(this, this.preloadComplete)
	}

	preloadComplete(cnt:number) {
		if (cnt == 0) {
			this.loaded.fire();
			this.resource.loaded.remove(this, this.preloadComplete);
		}
	}


	main() {
		var fps_stack = new number[];
		var _main = () => {
			var t:number = window.getTime();
			if (this.tick > (t+10000) || (this.tick+10000) < t) {
				//this.tick > (t+10000): 前回更新分が10秒以上未来の時間の場合。多分タイマーバグっとるのでリセット
				//(this.tick+10000) < t: 10秒以上更新されてない。多分タイマーバグっとる。バグっとるよね？
				this.tick = t - 1;
				this.renderTick = t - this.targetFps;
				if (this.enterFrame)
					this.enterFrameTick = t - 1;
				this.refresh();
			}

			if (this.tick < t) {
				this.update.fire(t - this.tick);
				this.tick = t;
			}
			if (this.enterFrame) {
				if (! this.enterFrameTick) {
					this.enterFrameTick = t -1;
				}
				while ((this.enterFrameTick+16) < t) {
					this.enterFrameTick += 16;
					this.enterFrame.fire();
				}
			}

			for (var i=0; i<this.timers.length; i++)
				this.timers[i].tryFire(t);

			if (this.targetFps == 0 || (this.renderTick+this.targetFps) <= t) {
				if (this.fps) {
					fps_stack.shift();
					fps_stack.push(t);
					this.fps.innerHTML = Math.round(20000 / (t-fps_stack[0])).toString();
				}

				if (this.render)
					this.render.fire();

				this.renderer.render();
				this.renderTick = t;
			}

			if (! this._exit)
				window.requestAnimationFrame(_main);
		}

		this.tick = window.getTime();
		this.renderTick = this.tick - this.targetFps;
		if (this.fps) {
			for (var i=0; i<20; i++)
				fps_stack.push(0);
		}
		window.requestAnimationFrame(_main);
		//_main();
		//_render();
	}
};
