function JsonLoader() {}

// JSON loader based on XmlHttpRequest
function XmlHttpJsonLoader() { } // {{{

XmlHttpJsonLoader.prototype = {
	_DataLoaded : function() { /* nothing to do here */ },

	_RequestTimedOut : function(requestId) { /* nothing to do here */ },
	
	_CreateXmlHttp: function () {
		if (typeof XMLHttpRequest != 'undefined') {
			return new XMLHttpRequest();
		} else if (window.ActiveXObject) {
			var xmlHttpAppIds = [
						'MSXML2.XMLHttp.5.0',
						'MSXML2.XMLHttp.4.0',
						'MSXML2.XMLHttp.3.0',
						'MSXML2.XMLHttp',
						'Microsoft.XMLHttp'];

			for (var index in xmlHttpAppIds) {
				try {
					return new ActiveXObject(xmlHttpAppIds[index]);
				} catch (err) { }
			}
		}

		return null;
	},

	_IsAvailable : function() {
		return this._CreateXmlHttp() != null;
	},

	_BeginRequest : function(url, requestId) {
		var xmlhttp = this._CreateXmlHttp();

		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState != 4) return;

			// even if readyState is 4, xmlhttp may not be ready to use in Mozilla browsers
			try { xmlhttp.status; } catch(err) { return; }

			xmlhttp.onreadystatechange = function() {};
			
			if (xmlhttp.status != 200) {
				var errorMessage = 'Invalid HTTP response code: ' + xmlhttp.status + ' ' + xmlhttp.statusText;
				JsonLoader._FireError(requestId, errorMessage, xmlhttp.status, xmlhttp.statusText);
			} else {
				try {
					var data = eval(xmlhttp.responseText);

					// pass received data to http_io.js processing pipeline
					JsonLoader._DataLoaded(requestId, data);
				} catch (err) {
					// error: non-json data
					var errorMessage = 'Json evaluation failed: ' + xmlhttp.responseText;
					JsonLoader._FireError(requestId, errorMessage);
				}
			}
		}

		xmlhttp.open('GET', url, true);
		xmlhttp.send(null);
	}
}; // }}}

// JSON loader based on SCRIPT tag
function ScriptTagJsonLoader() { } // {{{

ScriptTagJsonLoader.prototype = {
	_IsAvailable : function () { return true; },

	_FormScriptId : function(requestId) {
		return 'dynload' + requestId;
	},

	_GetDocHead : function() {
		return document.getElementsByTagName('HEAD').item(0);
	},

	_BeginRequest : function(url, requestId) {
		var scriptId = this._FormScriptId(requestId);
		var scriptTag = document.createElement('SCRIPT');
		
		scriptTag.type = 'text/javascript';
		scriptTag.src = JsonLoader._AppendUrlParameter(url, '_dynload_id', requestId);
		scriptTag.id = scriptId;

		this._GetDocHead().appendChild(scriptTag);
	},

	_RemoveScriptTag : function (requestId) {
		var scriptTag = document.getElementById(this._FormScriptId(requestId));
		this._GetDocHead().removeChild(scriptTag);
	},

	_DataLoaded : function (requestId, data) {
		this._RemoveScriptTag(requestId);
	},

	_RequestTimedOut : function(requestId) {
		this._RemoveScriptTag(requestId);
	}
} // }}}

JsonLoader._DataLoaded = function(requestId, data) {
	var requestData = JsonLoader._pendingRequests[requestId];
	if (requestData != null) {
		// call data loader to post-process data
		requestData['loader']._DataLoaded(requestId, data);

		JsonLoader._pendingRequests[requestId] = null;

		// call user-defined callback
		var callback = requestData['callback'];
		if (callback != null) {
			try {
				callback(data);
			} catch (err) {
				alert('Callback error: ' + err + '\nDescription: ' + err.description);
			}
		}
	}
};

JsonLoader._InitInstance = function() {
	JsonLoader.xmlHttpLoader = new XmlHttpJsonLoader();
	JsonLoader.scriptTagLoader = new ScriptTagJsonLoader();
}

JsonLoader._GetLoaderForUrl = function(url) {
	var re = /:\/\/([^\?]+)/;
	var results = re.exec(url);
	var currentHost = results == null || results[1] == window.location.host;
	
	if (currentHost) {
		if (JsonLoader.xmlHttpLoader._IsAvailable()) {
			return JsonLoader.xmlHttpLoader;
		} else {
			return JsonLoader.scriptTagLoader;
		}
	} else {
		return JsonLoader.scriptTagLoader;
	}
}

JsonLoader._pendingRequests = new Object();

JsonLoader.Load = function (url, callback, noeval) {
	// register request
	var requestId = '' + JsonLoader._GenerateRandomNumber();
	var loader = JsonLoader._GetLoaderForUrl(url);
	JsonLoader._pendingRequests[requestId] = {'callback' : callback, 'url' : url, 'loader' : loader, 'noeval' : noeval};

	url = JsonLoader._AppendUrlParameter(url, 'cache_bypass', requestId);

	// schedule timeout check
	setTimeout('JsonLoader._RequestTimedOut(\'' + requestId + '\');', JsonLoader.Timeout);

	// pass it to the script loader implementation
	loader._BeginRequest(url, requestId);
}

JsonLoader._RequestTimedOut = function(requestId) {
	var requestData = JsonLoader._pendingRequests[requestId];
	if (requestData == null) {
		return;
	}

	requestData['loader']._RequestTimedOut(requestId);
	
	JsonLoader._pendingRequests[requestId] = null;
	
	if (JsonLoader.OnTimeout != null) {
		JsonLoader.OnTimeout(requestData['url']);
	}
};

JsonLoader._FireError = function(requestId, message, httpStatusCode, httpStatusText) {
	var requestData = JsonLoader._pendingRequests[requestId];
	if (requestData != null) {
		message = 'Error loading url ' + requestData['url'] + '\n' + message;
		JsonLoader._pendingRequests[requestId] = null;
	}

	if (JsonLoader.OnError != null) {
		try {
			JsonLoader.OnError(message, httpStatusCode, httpStatusText);
		} catch (err) {
			alert('Error processing error: ' + err + '\nDescription: ' + err.description);
		}
	}
};

// user code can set these handlers
JsonLoader.OnTimeout = function(url) {}
JsonLoader.OnError = function(message, httpStatusCode, httpStatusMessage) {
	alert(message);
}

JsonLoader.Timeout = 40000;

JsonLoader.Invoke = function (url, noeval) {
	JsonLoader.Load(url, null, noeval);
}

JsonLoader._GenerateRandomNumber = function() {
    var random = Math.random() * 1000000000;

    return Math.ceil(random);
}

JsonLoader._AppendUrlParameter = function (url, name, value) {
    var href = url;
    var lastChar = href.charAt(href.length - 1);
    var containsQ = href.indexOf('?') != -1;
    var containsA = href.indexOf('&') != -1;

    if (!containsQ) {
        href += '?';
    } else {
        if ((!containsA && lastChar != '?') ||
            (containsA && lastChar != '&')) {

            href += '&';
        }
    }

    href += escape(name) + '=' + escape(value);

    return href;
}

JsonLoader._scriptReloads = new Object();

JsonLoader.StartScriptReload = function(url, callback, interval) {
	var handle = '' + JsonLoader._GenerateRandomNumber();

	JsonLoader._scriptReloads[handle] = {'url' : url, 'callback' : callback, 'interval' : interval, 'should_reload' : false};

	JsonLoader._ReloadScript(handle);
	
	return handle;
}

JsonLoader._ReloadScript = function(handle)
{
	var reloadData = JsonLoader._scriptReloads[handle];
	if (reloadData == null) return;
	if (reloadData['abort']) {
		JsonLoader._scriptReloads[handle] = null;
		return;
	}
	
	JsonLoader.Load(reloadData['url'], reloadData['callback']);

	setTimeout('JsonLoader._ReloadScript(\'' + handle + '\');', reloadData['interval']);
}

JsonLoader.StopScriptReload = function(handle) {
	var reloadData = JsonLoader._scriptReloads[handle];
	reloadData['abort'] = true;
}

JsonLoader._InitInstance();
window.JsonLoader = JsonLoader;
