/**
 * 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.20
 */

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:       "uid",  // 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,

		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 = 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 + "=" + 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 "&".
				var a = this._openArg;
				this._xmlReq.open(a.method, href+"&", 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!
			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() {
					if (req.readyState == 4) {
						// Call associated dataReady().
						eval(req.responseText);
					} else {
						th.readyState = req.readyState;
						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) {
			var query = [];
			if (content instanceof Object) {
				// TODO: build more nested objects in PHP-style.
				for (var k in content) {
					query[query.length] = escape(k) + "=" + escape(content[k]);
				}
			} else {
				query = [content];
			}
			return query.join('&');
		},

		// Return value of SID based on QUERY_STRING or cookie
		// (PHP compatible sessions).
		_getSid: function() {
			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;
		}
	}
})();
