/*
 * Copyright (c)  2006-2007 Maskat Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
maskat.lang.Class.declare("maskat.core.Application", {

	_static: {
		/**
		 * 起動ステージ 0:
		 * 
		 * この関数は maskat.js の読み込み時に実行され、HTML 文書の読み込みが
		 * 完了後にできるだけ早い段階で起動ステージ 1 (bootstrap 関数) が実行
		 * されるようにハンドラ関数 (bootstrap) を登録します。
		 */
		initialize: function(){
			if (navigator.userAgent.indexOf("MSIE") != -1) {
				document.write('<script id="__init_script" defer="true" src="//:"></script>');
			}
			
			/*
			 * Firefox (Gecko) の場合:
			 *
			 * DOMContentLoaded イベントを利用し、onload イベントの発生前に
			 * マスカットフレームワークを初期化する
			 */
			if (document.addEventListener) {
				document.addEventListener("DOMContentLoaded", this.bootstrap, false);
			}
			
			/*
			 * Internet Explorer の場合:
			 *
			 * script 要素の defer 属性と onreadystatechange イベントを利用し、
			 * onload イベントの発生前にマスカットフレームワークを初期化する
			 */
			if (document.getElementById) {
				var deferScript = document.getElementById("__init_script");
				if (deferScript) {
					deferScript.onreadystatechange = function() {
						if (this.readyState == "complete") {
							maskat.core.Application.bootstrap();
						}
					};

					/* 読み込みが既に完了しているかどうかを確認 */
					deferScript.onreadystatechange();
					deferScript = null;
				}
			}
			
			/* その他のブラウザでは onload イベントを利用 */
			window.onload = this.bootstrap;
		},
		
		/**
		 * 起動ステージ 1:
		 * 
		 * HTML 文書の読み込み完了後、onload イベントの発生より以前の段階で
		 * 実行されます。プラグインのインストールとアプリケーションの起動を
		 * 行います。
		 */
		bootstrap: function(){
			/* この関数が 1 回だけ実行されることを保証する */
			var self = arguments.callee;
			if (self.initialized) {
				return;
			}
			self.initialized = true;
		
			/* フレームワークパスを自動検出して maskat.location に格納 */
			var scripts = document.getElementsByTagName("script");
			var regexp = /\/core\/maskat\.js$/;

			for (var i = 0; i < scripts.length; i++) {
				var src = scripts[i].getAttribute("src");
				var match = src && src.match(regexp);
				if (match) {
					maskat.location = src.substring(0, match.index + 1);
					break;
				}
			}

			try {
				/* 設定ファイルの読み込み */
				var app = maskat.core.Application.getInstance();
				app.loadProperties(maskat.location + "properties.json");
				maskat.app = app;

				/* プラグインマネージャの生成 */
				var manager = new maskat.core.PluginManager(app.getProperty("maskat.core.plugins"));

				/*
				 * すべてのプラグインのロード完了時にアプリケーションの実行を
				 * 開始するようにコールバック関数を設定
				 */
				manager.onReady = function() {
					manager.start();
					app.loadController("transition.xml");
					app.start();
				};
				manager.install();
			} catch (e) {
				var logger = maskat.log.LogFactory.getLog("maskat.core");
				logger.error(e.getMessages ? e.getMessages().join("\n") : e.message);
			}
		},
		
		/**
		 * このクラスの唯一のインスタンスを返します。(Singleton パターン)
		 *
		 * @return このクラスの唯一のインスタンス
		 */
		getInstance: function(){
			var self = arguments.callee;
			if (!self.instance) {
				self.instance = new this();
			}
			return self.instance;
		}
	},
	
	/**
	 * 新しいアプリケーションのインスタンスを生成します。
	 */
	initialize: function(){
		this.layouts = {};
		this.stylesheets = null;
		this.scripts = null;
		this.controller = null;
		
		this.properties = new maskat.util.Properties({
			"maskat.core.plugins": { type: "object", defaultValue: {} },
			"maskat.comm.sendInterval": { type: "number", defaultValue: 100 },
			"maskat.comm.timeoutInterval": { type: "number", defaultValue: 500 },
			"maskat.comm.poolSize": { type: "number", defaultValue: 2 },
			"maskat.log.default.level": { type: "string", defaultValue: "INFO" },
			"maskat.log.factory": {
				type: "string",
				defaultValue: "maskat.log.SimpleLogFactory"
			},
			"maskat.ui.dialog.factory": {
				type: "string",
				defaultValue: "maskat.ui.JavaScriptDialogFactory"
			}
		});
	},
	
	/**
	 * アプリケーションのプロパティを指定した URL から JSON フォーマットで
	 * 読み込みます。
	 * 
	 * @param url プロパティファイル (JSON 形式) の URL
	 */
	loadProperties: function(url){
		this.properties.load(url);
	},

	/**
	 * マスカットアプリケーションのプロパティを取得します。
	 * 
	 * @param key プロパティキー
	 */
	getProperty: function(key){
		return this.properties.getProperty(key);
	},

	/**
	 * アプリケーションの実行を開始します。
	 */
	start: function(){
		/* アプリケーションコントローラを起動 */
		if (this.controller) {
			this.controller.start();
		}
	},

	/**
	 * 画面遷移定義 XML を読み込んで、アプリケーションコントローラを
	 * 構成します。
	 * 
	 * @param url 画面遷移定義 XML の URL
	 */
	loadController: function(url){
		var controller;
		try {
			controller = maskat.control.TransitionXMLReader.getInstance().load(url);
			controller.setApplication(this);
			this.controller = controller;
		} catch (e) {
			var cause = e.cause;
			if (e.cause && e.cause.key == "HTTP_404") {
				/*
				 * 画面遷移定義 XML が見つからない (HTTP ステータス 404) の
				 * 場合はエラーではなく、警告メッセージを表示
				 */
				var logger = maskat.log.LogFactory.getLog("maskat.core");
				logger.warn(e.getMessages ? e.getMessages().join("\n") : e.message);
			} else {
				/* その他の例外は呼び出し元にスロー */
				throw e;
			}
		}
		return controller;
	},
	
	/**
	 * 指定したレイアウト ID に対応するレイアウトを取得します。
	 * 
	 * @param layoutId レイアウト ID
	 * 
	 * @return 指定したレイアウト ID に対応するレイアウト、
	 *          存在しない場合は undefined
	 */
	getLayout: function(layoutId){
		return this.layouts ? this.layouts[layoutId] : undefined;
	},
	
	/**
	 * 指定したレイアウト ID に対応するレイアウトをロードします。
	 * 
	 * @param layoutURL レイアウト定義 XML の URL
	 * @param eventURL イベント定義 XML の URL
	 * @param element レイアウトをロードする HTML 要素
	 * @param visible レイアウトを表示する場合は true、非表示の場合は false
	 * 
	 * @return ロードされたレイアウト
	 */
	loadLayout: function(layoutURL, eventURL, element, visible){
		var layout;

		/* すでにロード済みの場合、レイアウトの表示／非表示を設定する */
		for (var name in this.layouts) {
			layout = this.layouts[name];
			if (layout.url == layoutURL) {
				layout.setVisible(visible);
				return layout;
			}
		}	

		/* レイアウトを新規にロードする */
		layout = maskat.layout.LayoutXMLReader.getInstance().load(layoutURL);
		layout.url = layoutURL;
		this.layouts[layout.getWidgetId()] = layout;
		
		if (eventURL) {
			var dispatcher = maskat.event.EventXMLReader.getInstance().load(eventURL);
			layout.addEventListener(dispatcher);
			dispatcher.setController(this.controller);
		}

		layout.load(element, visible);
		return layout;
	},
	
	/**
	 * 指定したレイアウト ID に対応するレイアウトをアンロードします。
	 * 
	 * @param layoutId レイアウト ID
	 */
	unloadLayout: function(layoutId){
		var layout = this.getLayout(layoutId);
		if (layout) {
			layout.unload();
			delete this.layouts[layoutId];
		}
	},
	
	/**
	 * 新しい CSS スタイルシートを読み込みます。
	 * 
	 * @param url CSS スタイルシート (*.css) の URL
	 */
	loadStyleSheet: function(url){
		if (this.stylesheets && this.stylesheets[url]) {
			return;
		}
		
		/* head 要素の子要素として style 要素を追加 */
		var link = document.createElement("link");
		link.rel = "stylesheet";
		link.type = "text/css";
		link.href = url;
		
		var head = document.getElementsByTagName("head")[0];
		head.appendChild(link);
		
		if (!this.stylesheets) {
			this.stylesheets = {};
		}
		this.stylesheets[url] = link;
	},
	
	/**
	 * 新しい JavaScript ファイルを読み込みます。
	 * 
	 * 非同期読み込みの場合、このメソッドの呼び出し直後には JavaScript が
	 * 解析されていないため、読み込んだスクリプトの内容を利用できません。
	 * このため、setTimeout 関数などを用いて解析を待つ必要があります。
	 * 
	 * @param url JavaScript ファイル (*.js) の URL
	 * @param async 非同期読み込みの場合は true
	 */
	loadJavaScript: function(url, async){
		if (async) {
			if (this.scripts && this.scripts[url]) {
				return;
			}

			/* head 要素の子要素として script 要素を追加 */
			var script = document.createElement("script");
			script.type = "text/javascript";
			script.src = url;
			var head = document.getElementsByTagName("head")[0];
			head.appendChild(script);
			
			if (!this.scripts) {
				this.scripts = {};
			}
			this.scripts[url] = script;
		} else {
			/*
			 * HTTP GET メソッドで取得した JavaScript ソースコードを
			 * グローバルスコープでソースコードを eval() する
			 */
			var source = maskat.util.CrossBrowser.getTextFrom(url);
			this.loadJavaScriptFromString(source, false);
		}
	},

	/**
	 * JavaScript ソースコードを文字列から読み込みます。
	 *
	 * 非同期読み込みの場合、このメソッドの呼び出し直後には JavaScript が
	 * 解析されていないため、読み込んだスクリプトの内容を利用できません。
	 * このため、setTimeout 関数などを用いて解析を待つ必要があります。
	 * 
	 * @param source JavaScript ソースコード文字列
	 * @param async 非同期読み込みの場合は true
	 */
	loadJavaScriptFromString: function(source, async){
		if (async) {
			/* head 要素の子要素として script 要素を追加 */
			var script = document.createElement("script");
			script.type = "text/javascript";
			script.text = source;
			var head = document.getElementsByTagName("head")[0];
			head.appendChild(script);
		} else {
			/* グローバルスコープでソースコードを eval する */
			if (window.execScript) {
				window.execScript(source, "JavaScript");
			} else if (window.eval) {
				window.eval(source);
			} else {
				eval(source);
			}
		}
	}

});
