/**
 * Subsys_JsHttpRequest_Js: JavaScript DHTML data loader. (C) 2005 Dmitry
 * Koterov, http://forum.dklab.ru/users/DmitryKoterov/
 * 
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version. See http://www.gnu.org/copyleft/lesser.html
 * 
 * Do not remove this comment if you want to use script! Не удаляйте данный
 * комментарий, если вы хотите использовать скрипт!
 * 
 * This library tries to use XMLHttpRequest (if available), and on failure - use
 * dynamically created <script> elements. Backend code is the same for both
 * cases.
 * 
 * @author Dmitry Koterov
 * @version 3.29
 */

function Subsys_JsHttpRequest_Js() {
	this._construct()
}
( function() { // to create local-scope variables
	var COUNT = 0;
	var PENDING = {};
	var CACHE = {};

	// Called by server script on data load.
	Subsys_JsHttpRequest_Js.dataReady = function(id, text, js) {
		var undef;
		var th = PENDING[id];
		delete PENDING[id];
		if (th) {
			delete th._xmlReq;
			if (th.caching)
				CACHE[th.hash] = [ text, js ];
			th._dataReady(text, js);
		} else if (typeof (th) != typeof (undef)) {
			alert("ScriptLoader: unknown pending id: " + id);
		}
	}

	Subsys_JsHttpRequest_Js.prototype = {
		// Standard properties.
		onreadystatechange :null,
		readyState :0,
		responseText :null,
		responseXML :null,
		status :200,
		statusText :"OK",

		// Additional properties.
		session_name :"PHPSESSID", // set to SID cookie or GET parameter name
		responseJS :null, // JavaScript response array/hash
		caching :false, // need to use caching?

		// Internals.
		_span :null,
		_id :null,
		_xmlReq :null,
		_openArg :null,
		_reqHeaders :null,

		dummy : function() {
		}, // empty function

		abort : function() {
			if (this._xmlReq)
				return this._xmlReq.abort();
			if (this._span) {
				this.readyState = 0;
				if (this.onreadystatechange)
					this.onreadystatechange();
				this._cleanupScript();
			}
		},

		open : function(method, url, asyncFlag, username, password) {
			this._openArg = {
				'method' :method,
				'url' :url,
				'asyncFlag' :asyncFlag,
				'username' :username,
				'password' :password
			};
			this._id = null;
			this._xmlReq = null;
			this._reqHeaders = [];
			return true;
		},

		send : function(content) {
			var id = (new Date().getTime()) + "" + COUNT++;

			// Build QUERY_STRING from query hash.
			var query = this._hash2query(content);

			// Append SID to original URL now.
			var url = this._openArg.url;
			var sid = this._getSid();
			if (sid)
				url += (url.indexOf('?') >= 0 ? '&' : '?') + this.session_name
						+ "=" + this.escape(sid);

			// Solve hash BEFORE appending ID.
			var hash = this.hash = url + '?' + query;
			if (this.caching && CACHE[hash]) {
				var c = CACHE[hash];
				this._dataReady(c[0], c[1]);
				return false;
			}

			// Try to use XMLHttpRequest.
			this._xmlReq = this._obtainXmlReq(id, url);

			// Pass data in URL (GET, HEAD etc.) or in request body (POST)?
			var hasSetHeader = this._xmlReq
					&& (window.ActiveXObject || this._xmlReq.setRequestHeader);
			var href, body;
			if (this._xmlReq && hasSetHeader
					&& ("" + this._openArg.method).toUpperCase() == "POST") {
				// Use POST method. Pass query in request body.
				// Opera 8.01 does not support setRequestHeader, so no POST
				// method.
				this._openArg.method = "POST";
				href = url;
				body = query;
			} else {
				this._openArg.method = "GET";
				href = url + (url.indexOf('?') >= 0 ? '&' : '?') + query;
				body = null;
			}

			// Append ID: a=aaa&b=bbb&<id>
			href = href + (href.indexOf('?') >= 0 ? '&' : '?') + id;

			// Save loading script.
			PENDING[id] = this;

			if (this._xmlReq) {
				// Open request now & send it.
				// In XMLHttpRequest mode request URL MUST be ended with
				// "<id>-xml".
				var a = this._openArg;
				this._xmlReq.open(a.method, href + "-xml", a.asyncFlag,
						a.username, a.password);
				if (hasSetHeader) {
					// Pass pending headers.
					for ( var i = 0; i < this._reqHeaders.length; i++)
						this._xmlReq.setRequestHeader(this._reqHeaders[i][0],
								this._reqHeaders[i][1]);
					// Set non-default Content-type. We cannot use
					// "application/x-www-form-urlencoded" here, because
					// in PHP variable HTTP_RAW_POST_DATA is accessible only
					// when
					// enctype is not default (e.g., "application/octet-stream"
					// is a good start). We parse POST data manually in backend
					// library code.
					this._xmlReq.setRequestHeader('Content-Type',
							'application/octet-stream');
				}
				// Send the request.
				return this._xmlReq.send(body);
			} else {
				// Create <script> element and run it.
				this._obtainScript(id, href);
				return true;
			}
		},

		getAllResponseHeaders : function() {
			if (this._xmlReq)
				return this._xmlReq.getAllResponseHeaders();
			return '';
		},

		getResponseHeader : function(label) {
			if (this._xmlReq)
				return this._xmlReq.getResponseHeader(label);
			return '';
		},

		setRequestHeader : function(label, value) {
			// Collect headers.
		this._reqHeaders[this._reqHeaders.length] = [ label, value ];
	},

	//
		// Internal functions.
		//

		// Constructor.
		_construct : function() {
		},

		// Do all work when data is ready.
		_dataReady : function(text, js) {
			with (this) {
				if (text !== null || js !== null) {
					readyState = 4;
					responseText = responseXML = text;
					responseJS = js;
				} else {
					readyState = 0;
					responseText = responseXML = responseJS = null;
				}
				if (onreadystatechange)
					onreadystatechange();
				_cleanupScript();
			}
		},

		// Create new XMLHttpRequest object.
		_obtainXmlReq : function(id, url) {
			// If url.domain specified, cannot use XMLHttpRequest!
			// XMLHttpRequest (and MS ActiveX'es) cannot work with different
			// domains.
			if (url.match(new RegExp('^[a-z]+://', 'i')))
				return null;

			// Try to use built-in loaders.
			var req = null;
			if (window.XMLHttpRequest) {
				try {
					req = new XMLHttpRequest()
				} catch (e) {
				}
			} else if (window.ActiveXObject) {
				try {
					req = new ActiveXObject("Microsoft.XMLHTTP")
				} catch (e) {
				}
				if (!req)
					try {
						req = new ActiveXObject("Msxml2.XMLHTTP")
					} catch (e) {
					}
			}
			if (req) {
				var th = this;
				req.onreadystatechange = function() {
					var s = req.readyState;
					if (s == 4) {
						// Avoid memory leak by removing closure.
						req.onreadystatechange = th.dummy;
						// Remove possible junk from response.
						var responseText = req.responseText;
						try {
							// Call associated dataReady().
							eval(responseText);
						} catch (e) {
							Subsys_JsHttpRequest_Js.dataReady(id,
									"JavaScript code generated by backend is invalid!\n"
											+ responseText, null);
						}
					} else {
						th.readyState = s;
						if (th.onreadystatechange)
							th.onreadystatechange()
					}
				};
				this._id = id;
			}
			return req;
		},

		// Create new script element and start loading.
		_obtainScript : function(id, href) {
			with (document) {
				var span = null;
				// Oh shit! Damned stupid fucked Opera 7.23 does not allow to
				// create SCRIPT
				// element over createElement (in HEAD or BODY section or in
				// nested SPAN -
				// no matter): it is created deadly, and does not respons on
				// href assignment.
				// So - always create SPAN.
				span = body.appendChild(createElement("SPAN"));
				span.style.display = 'none';
				span.innerHTML = 'Text for stupid IE.<s' + 'cript></' + 'script>';
				setTimeout( function() {
					var s = span.getElementsByTagName("script")[0];
					s.language = "JavaScript";
					if (s.setAttribute)
						s.setAttribute('src', href);
					else
						s.src = href;
				}, 10);
				this._id = id;
				this._span = span;
			}
		},

		// Remove last used script element (clean memory).
		_cleanupScript : function() {
			var span = this._span;
			if (span) {
				this._span = null;
				setTimeout( function() {
					// without setTimeout - crash in IE 5.0!
						span.parentNode.removeChild(span);
					}, 50);
			}
			return false;
		},

		// Convert hash to QUERY_STRING.
		_hash2query : function(content, prefix) {
			if (prefix == null)
				prefix = "";
			var query = [];
			if (content instanceof Object) {
				for ( var k in content) {
					var v = content[k];
					if (v == null || ((v.constructor || {}).prototype || {})[k])
						continue;
					var curPrefix = prefix ? prefix + '[' + this.escape(k)
							+ ']' : this.escape(k);
					if (v instanceof Object)
						query[query.length] = this._hash2query(v, curPrefix);
					else
						query[query.length] = curPrefix + "=" + this.escape(v);
				}
			} else {
				query = [ content ];
			}
			return query.join('&');
		},

		// Return value of SID based on QUERY_STRING or cookie
		// (PHP compatible sessions).
		_getSid : function() {
			// return '';
			var m = document.location.search.match(new RegExp(
					'[&?]' + this.session_name + '=([^&?]*)'));
			var sid = null;
			if (m) {
				sid = m[1];
			} else {
				var m = document.cookie.match(new RegExp(
						s = '(;|^)\\s*' + this.session_name + '=([^;]*)'));
				if (m)
					sid = m[2];
			}
			return sid;
		},

		// Stupid JS escape() does not quote '+'.
		escape : function(s) {
			return escape(s).replace(new RegExp('\\+', 'g'), '%2B');
		}
	}
})();
