/*
****PCD-2013 GameScriptCoreLibrary****
Tokyo Gakugei University Senior High School.

<クライアント要件>
Mac OSX: Safari, Chrome
Windows: Chrome, IE(9 or later)

以下の技術が実行可能である必要があります:
HTML5
	Canvas
	FormData

<Usage>
HTMLソース側に、
<div id="MainArea">
</div>
<div id="Resources">
</div>
および
mainManager = new GameManager();
が必要。
*/

//
//定数
//

var loc = document.location.href;

var URL_PCD_Root = loc.slice(0, loc.lastIndexOf("/") + 1);

var URL_PCD_Auth = URL_PCD_Root + "auth.php";
var URL_PCD_Audio = URL_PCD_Root + "audio/";
var URL_PCD_Stage = URL_PCD_Root + "stage/";

// ゲームを呼び出す関数	適切なdiv要素で呼び出すとゲームを初期化できる。
// つまり、自作ゲームをしたい場合
// audio, corelib, images, stageの各フォルダをコピーしたdirにhtmlを置き、<div>.InitGameManager() と実行するスクリプトを書く
// stageName == nullだとステージを開始しない。指定する時は.jsおよびパスを省く

//
var CollideBody = 16;
var CollideTop = 8;
var CollideBottom = 4;
var CollideLeft = 2;
var CollideRight = 1;

HTMLDivElement.prototype.InitGameManager = function(stageName)
{
	if(this instanceof HTMLDivElement)
	{
		var man = new GameManager(this, null);
		if(stageName) man.loadStageFromNetwork.apply(man, [stageName]);
		return man;
	}else
	{
		throw new TypeError("InitGameManager はdiv要素にしか実行できません");
	}
};

//
//ゲームマネージャー
//

function GameManager(parent, debugTextName){
	//引数チェック
	if(debugTextName == undefined) debugTextName = "DebugText";
	if(parent == undefined) parent = document.getElementById("MainArea");
	
	//parentの初期設定
	if(parent.style.position != 'absolute') parent.style.position = 'relative';
	
	this.userID = 0;
	//サブマネージャーの設定
	this.networkManager = new NetworkManager(this);
	this.UIManager = new UIManager(this);
	this.userManager = new UserManager(this);
	this.mainArea = parent;
	//必要最低限のCanvasとコンテキストの設定
	this.mainCanvas = createCanvas("MainCanvas", 640, 480, 0, 0, 1, parent);
	this.mainCanvas.style.border = "solid 1px";
	this.mainContext = this.mainCanvas.getContext('2d');
	this.debugText = document.getElementById(debugTextName);	//要素が存在しないとnullになり、デバッグが無効になる
	if(!this.debugText) this.debugText = null;
	
	//ブラウザチェック
	this.isIE = false;
	if(!this.isAvailableBrowser()){
		return null;
	}
	//描画コンテキストの初期設定
	this.mainContext.fillStyle = "rgba(255,255,255,0.5)";
	this.mainContext.strokeStyle = "rgba(0, 0, 0, 1)";
	this.mainContext.font = "normal 20px sans-serif";
	//実行中のGameStageオブジェクトを格納
	this.runningStage = null;
	this.runningStageName = null;
	//現在存在しているWidghetのリストを格納
	this.runningWidgets = [];
	//タイマーカウントを初期化
	this.tickCount = 0;
	
	//死んだときにもどってくるステージ
	this.returningStage = 'onieasy';
	
	//ゲームの7分間タイマー初期化
	this.startTimeStamp = 0;
	this.timerRunning = false;
	//this.gameTime = 7 * 60 * 1000;
	this.gameTime = 7 * 60 * 1000;
	
	// pauseStage()関連の配列。
	this.stagePaused = false;
	this.stagePausedFunctions = [];
	this.stagePausedInitFunctions = [];
	
	this.backgroundMusic = null;
	
	// loadStageFromLocal でtrueにされる
	this.isLocalMode = false;

	//**イベントリスナー設定**
	//コールバックを行うために、イベントリスナーのmanagerプロパティにGameManagerのインスタンスを代入する。
	//timerTick
	timerTickEventListener.manager = this;
	window.setInterval(timerTickEventListener, 1000/this.tickPerSecond);
	
	//各種コールバック(使用元のスクリプトで使う用)
	this.stageStartedEvent = null;		//ステージが開始されたときに呼ばれる。引数: stage
	this.stageStoppedEvent = null;		//ステージが終了されたときに呼ばれる。引数: stage
}
GameManager.prototype = {
	//タイマーカウントの秒あたりの回数を設定
	tickPerSecond: 60,
	timerTick: function(){
		//各オブジェクトの単位時間ごとの動作と再描画を行う
		//単位時間ごとの動作
		this.tickCount++;
		if(!this.stagePaused){
			//ポーズしていなければ更新処理
			if(this.runningStage){
				//ステージ
				this.runningStage.timerTick();
			}
		}
		// runningStage.timerTick() 内でpauseStage()された時、ここで再度判定しないとWidghetのtickが実行されてしまう
		if(!this.stagePaused){
			//ウィジェット
			for(var i = 0; i < this.runningWidgets.length; i++){
				var w = this.runningWidgets[i];
				if(!w.tick()){
					// Widghetのtick()からfalseで帰ってきたらWidghetを開放
					this.removeWidget(w);
					i--;
				}
			}
		} else{
			//ポーズしているならば処理関数を実行
			this.stagePausedFunctions[0]();
		}
		
		
		//描画処理
		if(this.runningStage){
			//ステージ
			this.runningStage.draw();
		}
		//ウィジェット
		for(var i = 0; i < this.runningWidgets.length; i++){
			var w = this.runningWidgets[i];
			w.draw();
		}
		
		//各オブジェクトの走査が終わってから、死亡判定およびステージ再読み込みを行う。
		if(this.runningStage && this.runningStage.userControlledCharacter){
			if(this.runningStage.userControlledCharacter.HP == 0){
				this.runningStage.userControlledCharacter.HP = this.runningStage.userControlledCharacter.max_HP;
				var w = new MessageWidgetClass(this, ["死んでしまった……\n", null, function(w){
					w.manager.UIManager.clearInput();
					w.manager.userManager.deletePlusPointIngredient();
					if(w.manager.runningStageName){
						w.manager.loadStageFromNetwork(w.manager.returningStage);
					} else{
						//ローカルモード時は動作を停止させるだけ
						w.manager.stopStage();
					}
				}]);
				w.cancelKeyEnabled = false;
				this.addWidget(w);
				
			}
		}
	},
	addWidget: function(w){
		w.attach();
		this.runningWidgets.push(w);
	},
	removeWidget: function(w){
		if(removeObjectFromArray(this.runningWidgets, w))
		{
			w.detach();
		}
	},
	runStage: function(stage){
		//新たなステージを開始する
		//実行中のステージがあれば終了処理を行わせる。
		if(this.runningStage){
			this.stopStage();
		}
		//ステージ名の変更
		if(this.userID != 0){
			console.log(this.runningStageName);
			var rq2 = this.networkManager.CreateRequestObject();
			//同期モード
			rq2.open('GET', URL_PCD_Auth + "?action=chstg&name=" + this.runningStageName + "&id=" + this.userID);
			this.networkManager.RequestObjectDisableCache(rq2);
			rq2.send(null);
			console.log("changed.");
		}
		//新たに開始するステージの初期化
		//GameManager側の情報をGameStageに渡す。
		stage.manager = this;
		stage.mainCanvas = this.mainCanvas
		stage.debugCanvas = this.debugCanvas
		stage.mainContext = this.mainContext
		stage.debugContext = this.debugContext
		//GameStage側の初期化処理を行わせる。
		stage.runStage();
		//ネットワーク同期初期化
		this.networkManager.joinStage(stage);
		//runningStageに登録することで、イベントの通知が開始され、GameStageは実行状態に入る。
		this.runningStage = stage;
		
		if(this.stageStartedEvent)
		{
			this.stageStartedEvent.apply(window, [stage]);
		}
		
	},
	// func : 停止中にtick毎に呼ばれる関数。
	// initFunc : pauseが可能となった時に呼ばれる関数。指定しないと、その時点でpauseが不可能だったときに例外がおこる。
	pauseStage: function(func, initFunc){
		//ステージの実行を一時停止する。一時停止中、funcに指定された関数が毎tick毎に呼ばれる
		if(!this.stagePaused){
			if(!initFunc)
			{
				initFunc = null;
			}else
			{
				initFunc();
			}
			this.stagePausedFunctions.push(func);
			this.stagePausedInitFunctions.push(initFunc);
			this.stagePaused = true;
			return true;
		} else{
			if(!initFunc)
			{
				throw "pauseStage calling doubled.";
			}else
			{
				this.stagePausedFunctions.push(func);
				this.stagePausedInitFunctions.push(initFunc);
				return true;
			}
		}
	},
	//func は、pauseStageのinitFuncが実行されてそのpauseStage()による待機状態に入る前にresumeStage()が実行された時の安全策
	resumeStage: function(func){
		//ステージの実行を再開する
		if(this.stagePaused) {
			//必ずpauseStage()の引数に指定したfunc()の中から呼ばれる・・・はず。
			var beforeFunc = this.stagePausedFunctions[0];
			if(func)
			{
				var ind = -1;
				for(var i in this.stagePausedFunctions)
				{
					if(this.stagePausedFunctions[i] == func)
					{
						ind = i; break;
					}
				}
				if(ind == -1)
				{
					return false;
				}else
				{
					this.stagePausedFunctions.splice(ind, 1);
					this.stagePausedInitFunctions.splice(ind, 1);
				}
			}else
			{
				this.stagePausedFunctions.splice(0, 1);
				this.stagePausedInitFunctions.splice(0, 1);
			}
			if(this.stagePausedFunctions.length == 0)
			{
				this.stagePaused = false;
			}else
			{
				if(this.stagePausedFunctions[0] != beforeFunc) this.stagePausedInitFunctions[0]();
			}
			return true;
		} else{
			return false;
		}
	},
	stopStage: function(){
		//現在実行中のステージを終了する
		if(this.runningStage){
			//runningStageから設定解除することで、イベントの通知は行われなくなる。
			var aGameStage = this.runningStage;
			this.runningStage = null;
			this.runningStageName = null;
			//GameStage側の終了処理を行わせる。
			aGameStage.stopStage();
			//GameStageインスタンスからGameManagerの情報を削除する。
			aGameStage.manager = null;
			aGameStage.mainCanvas = null;
			aGameStage.debugCanvas = null;
			aGameStage.mainContext = null;
			aGameStage.debugContext = null;
			this.stagePausedFunctions = [];
			this.stagePausedInitFunctions = [];
			this.stagePaused = false;
			
			//画面上に表示されたすべてのWidgetを解放する
			for(;this.runningWidgets.length>0;)
			{
				this.removeWidget(this.runningWidgets[0]);
			}
			
			if(this.stageStoppedEvent)
			{
				this.stageStoppedEvent.apply(window, [aGameStage]);
			}
		}
	},
	loadStageFromLocal: function(code){
		//各種パスをローカル用に変更
		URL_PCD_Root = "./";
		URL_PCD_Auth = URL_PCD_Root + "auth.php";
		URL_PCD_Audio = URL_PCD_Root + "audio/";
		URL_PCD_Stage = URL_PCD_Root + "stage/";
		this.isLocalMode = true;
		
		var stage = eval(code);
		mainManager.runStage(stage);
	},
	loadStageFromNetwork: function(name){
		
		if(this.isLocalMode)
		{
			if(localModeLoadHandler)
			{
				var val = localModeLoadHandler(name);
				var stage = eval(val);
				this.stopStage();
				this.runningStageName = name;
				this.runStage(stage);
			}else
			{
				throw "ローカルモードではステージを超えたデバッグはできません";
			}
		}else
		{
			//URL_PCD_Stage/name.jsを利用してステージを作成する。
			var request = this.networkManager.CreateRequestObject();
			//同期モード
			request.open('GET', URL_PCD_Stage + name + ".js", false);
			this.networkManager.RequestObjectDisableCache(request);
			request.send(null);
			
			if(request.status == 0){
				alert("ネットワークにアクセスできません。" + request.status + ":" + request.statusText);
			}else if((200 <= request.status && request.status < 300) || (request.status == 304)){
				var stage = eval(request.responseText);
				this.stopStage();
				this.runningStageName = name;
				if(this.runningStageName != name){
					alert("e");
				}
				this.runStage(stage);
			}else{
				alert("サーバーがエラーを返しました。" + request.status + ":" + request.statusText);
			}
		}
	},
	debugOut: function(str){
		if(this.debugText != null)
		{
			if(this.isIE)
			{
				//CRLF
				this.debugText.value = str.replace(/\n/g,"\r\n") + this.debugText.value;
			} else{
				//LF
				this.debugText.innerHTML = str + this.debugText.value;
			}
		}
	},
	isAvailableBrowser: function(){
		//ブラウザの判別を行う。実行不可能な場合はfalseを返す。
		//http://d.hatena.ne.jp/Naotsugu/20110927/1317140891
		var userAgent = window.navigator.userAgent.toLowerCase();
		var appVersion = window.navigator.appVersion.toLowerCase();
		
		if (userAgent.indexOf('opera') != -1) {
			//opera
			this.debugOut("Browser:Opera\n");
		} else if (userAgent.indexOf('msie') != -1) {
			if (appVersion.indexOf("msie 9.") != -1) {
				//ie9
				this.debugOut("Browser:IE9\n");
			} else if (appVersion.indexOf("msie 8.") != -1) {
				//ie8
				this.debugOut("Browser:IE8\n");
			} else if (appVersion.indexOf("msie 7.") != -1) {
				//ie7
				this.debugOut("Browser:IE7\n");
			} else if (appVersion.indexOf("msie 6.") != -1) {
				//ie6
				this.debugOut("Browser:IE6\n");
			} else{
				this.debugOut("Browser:IE?\n");
			}
			this.isIE = true;
		} else if (userAgent.indexOf('chrome') != -1) {
			//chrome
			this.debugOut("Browser:Chrome\n");
		} else if (userAgent.indexOf('safari') != -1) {
			//safari
			this.debugOut("Browser:Safari\n");
		} else if (userAgent.indexOf('gecko') != -1) {
			//gecko
			this.debugOut("Browser:Gecko\n");
		} else {
			//unknown
			this.debugOut("Browser:Unknown\n");
		}
		//描画コンテキストからHTML5対応チェック
			if(!this.mainCanvas || !this.mainCanvas.getContext){
				//HTML5未対応の場合
				alert("このゲームを遊ぶためには、HTML5に対応しているブラウザ(Google Chrome等)でアクセスしてください...。");
			return false;
		}
		return true;
	},
	setBackgroundMusic: function(name){
		if(this.backgroundMusic){
			//再生していたら止める
			this.backgroundMusic.pause();
			this.backgroundMusic = null;
		}
		if(name){
			//引数がnullでなければaudioオブジェクトを取得
			this.backgroundMusic = createAudio(name);
			if(this.backgroundMusic){
				//ループを設定して再生開始
				this.backgroundMusic.loop = true;
				this.backgroundMusic.play();
			}
		}
	},
};

//
//イベントリスナー
//
//イベントリスナーにおけるthisは、イベントリスナーを登録したオブジェクトまたはwindowになり、通常とは異なるので注意。

function timerTickEventListener(event)
{
	timerTickEventListener.manager.timerTick(event);
}
