/*
 * 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.
 */
/**
 * ブラウザ依存の処理をカプセル化します。
 *
 * このクラスは static メソッドのみを持つユーティリティクラスであり、
 * インスタンス化する必要はありません。
 */
maskat.lang.Class.declare("maskat.util.CrossBrowser", {

	_static: {
		/**
		 * 新しい XMLHttpRequest を生成します。
		 *
		 * @return XMLHttpRequest のインスタンス
		 */
		createXMLHttpRequest: function(){
			var xhr;
			
			if (window.XMLHttpRequest &&
				(navigator.appName != "Microsoft Internet Explorer" ||
				 window.location.protocol != "file:")) {
				
				xhr = new XMLHttpRequest();
			} else if (window.ActiveXObject) {
				try {
					xhr = new ActiveXObject("Msxml2.XMLHTTP");
				} catch (e) {
					try {
						xhr = new ActiveXObject("Microsoft.XMLHTTP");
					} catch (e) { /* suppress */ }
				}
			}

			if (!xhr) {
				throw new maskat.lang.Error("CREATEXHR_ERROR",
					{ agent: navigator.userAgent });
			}
			return xhr;
		},

		/**
		 * XMLHttpRequest に通信エラーが発生しているかどうかを確認し、通信
		 * エラーの場合には例外をスローします。
		 *
		 * @throws maskat.lang.Error 通信エラーが発生している場合
		 */
		checkHttpError: function(xhr) {
			var err = this.getHttpError(xhr);
			if (err) {
				throw err;
			}
		},
		
		getHttpError: function(xhr) {
			try {
				if (xhr.readyState != 4 || xhr.status != 200) {
					switch (xhr.status) {
					case 0:
						return undefined;
					case 403:
					case 404:
					case 500:
						return new maskat.lang.Error("HTTP_" + xhr.status,
							{ status: xhr.status, url: xhr.statusText });
					default:
						/*
						 * IE独自のエラー(12002-12152)が発生した場合、
						 * statusTextには "Unknown"が格納される。
						 */
						var url = xhr.statusText != "Unknown" ? xhr.statusText : "";
						return new maskat.lang.Error("HTTP_DEFAULT",
							{ status: xhr.status, url: url });
					}
				}
			} catch (e) {
				/*
				 * Firefox ではサーバサイドがダウンしている状態で send した
				 * XHRの status, statueTextにアクセスするとエラーが発生する。
				 * そのための catch 処理
				 */
				return new maskat.lang.Error("HTTP_ERROR");
			}
		},

		/**
		 * HTTP GET メソッドを用いた同期通信で XML 文書を取得し、Document
		 * ノードを返します。
		 *
		 * @return 取得した XML 文書の Document ノード
		 */
		getXMLDocumentFrom: function(url){
			var xhr = this.createXMLHttpRequest();
			
			try {
				xhr.open("GET", url, false);
				xhr.send(null);
				
			} catch (e) {
				throw new maskat.lang.Error("HTTP_404",
					{ status: 404, url: url });
			}
			if (xhr.readyState == 4) {
				switch (xhr.status) {
				case 200:
					this.checkParseError(xhr.responseXML);
					if (xhr.responseXML && xhr.responseXML.nodeType == 9) {
						return xhr.responseXML;
					}
					break;
				case 0:
					/* ローカル環境の場合 responseText から DOM を生成 */
					if (document.location.protocol == "file:" && xhr.responseText) {
						var doc = this.parseXMLDocument(xhr.responseText);
						this.checkParseError(doc);
						return doc;
					}
					break;
				default:
					this.checkHttpError(xhr);
				}
			}
		},

		/**
		 * HTTP GET メソッドを用いた同期通信でテキストを取得します。
		 *
		 * @return 取得したテキスト
		 */
		getTextFrom: function(url){
			var xhr = this.createXMLHttpRequest();
			try {
				xhr.open("GET", url, false);
				xhr.send(null);
			} catch (e) {
				throw new maskat.lang.Error("HTTP_404",
					{ status: 404, url: url });
			}
			this.checkHttpError(xhr);
			return xhr.responseText;
		},

		/**
		 * XML 文書の文字列表現から DOM オブジェクトを構築し、Document
		 * ノードを返します。
		 *
		 * @return XML 文書の Document ノード
		 */
		parseXMLDocument: function(source){
			var doc;
			if (window.DOMParser) {
				doc = (new DOMParser()).parseFromString(source, "text/xml");
			} else if (window.ActiveXObject) {
				doc = new ActiveXObject("MSXML2.DOMDocument");
				doc.async = false;
				doc.resolveExternals = false;
				doc.validateOnParse = false;
				doc.loadXML(source);
			}
			return doc;
		},
		
		/**
		 * XML 文書に解析エラーが発生しているかどうかを確認し、解析エラーの
		 * 場合には例外をスローします。
		 *
		 * @throws maskat.lang.Error 解析エラーが発生している場合
		 */
		checkParseError: function(xml) {
			if (xml.parseError && xml.parseError.errorCode < 0) {
				/* IE */
				var p = xml.parseError;
				var param = {
					msg: p.reason,
					line: p.line,
					pos: p.linepos,
					text: p.srcText
				};
				throw new maskat.lang.Error("PARSEERROR_IE", param);
			} else if (xml.documentElement &&
					   xml.documentElement.tagName == "parsererror") {
				/* Firefox */
				throw new maskat.lang.Error("PARSEERROR_DEFAULT",
					{ msg: xml.documentElement.textContent });
			}
		},

		/**
		 * HTTP GET メソッドを用いた同期通信で JSON 形式のリテラル文字列を
		 * 取得し、それを評価したオブジェクトを返します。
		 *
		 * @return オブジェクト
		 */
		loadJSONFrom: function(url){
			return this.parseJSON(this.getTextFrom(url));
		},
		
		/**
		 * JSON (JavaScript Object Notation) で記述されたリテラルを評価して
		 * オブジェクトを返します。
		 *
		 * @return オブジェクト
		 */
		parseJSON: function(text){
			/* JSON パーサが読み込まれている場合は利用する */
			if (typeof(JSON) != "undefined" && typeof(JSON.parse) == "function") {
				return JSON.parse(text);
			}
			return eval("(" + text + ")");
		},
		
		/**
		 * HTML 要素にイベントリスナを追加します。
		 *
		 * このメソッドで追加されたイベントリスナはバブリング段階で実行
		 * されます。
		 *
		 * @param node リスナを追加する HTML 要素
		 * @param eventType イベントタイプ
		 *        "click", "focus", "blur" など、先頭に "on" を含まない文字列
		 * @param listener イベントリスナとして追加される関数
		 *
		 * @return XML 文書の Document ノード
		 */
		addEventListener: function(node, eventType, listener){
			if (node.addEventListener) {
				node.addEventListener(eventType, listener, false);
			} else if (node.attachEvent) {
				node.attachEvent("on" + eventType, listener);
			} else if (node["on" + eventType]) {
				node["on" + eventType] = listener;
			}
		}
	}
});
