var NO_SESSION_EXCEPTION = 100;
var MUST_LOGIN_EXCEPTION = 101;
var ACCESS_DENIED_EXCEPTION = 102;
var DATAFEEDER_EXPIRED_EXCEPTION = 103;

var AR_LOGLEVEL = 0;

var AXHRManager = Class.create({
initialize: function() {
	var oTT = this.GetTransport();
	if (oTT) delete oTT; // delete test object
	AXHRManager.prototype.aRequests_ = new Array(); // TODO: Remove this ARRAY!!!!
	AXHRManager.prototype.nReqNum_ = 0;
},
GetTransport: function() {
	var oTransport = null;
	// try kosher browser object
	try {
		oTransport = new XMLHttpRequest();
		if (oTransport) {
			return oTransport;
		}
	} catch (e) { oTransport = null; }
	// try two MSIE < 7.0 ways
	try {
		oTransport = new ActiveXObject('Msxml2.XMLHTTP');
		if (oTransport) {
			return oTransport;
		}
	} catch (e) { oTransport = null; }
	try {
		oTransport = new ActiveXObject('Microsoft.XMLHTTP');
		if (oTransport) {
			return oTransport;
		}
	} catch (e) { oTransport = null; }
	throw new Error("AJAX is not supported by your browser, sorry!");
},
// Must be called on page destruction!
// This method is a very important feature for IE7: if some requests
// are not killed, IE will hang due to maximum parallel requests
// limit is reached (2 for IE7, 6 for Fx3)
DestroyRequests: function() {
	var nDCount = 0;
	for (var i = 0; i < AXHRManager.prototype.nReqNum_+1; ++i) {
		if (AXHRManager.prototype.aRequests_[i]) {
			AXHRManager.prototype.aRequests_[i].Kill();
			nDCount++;
		}
	}
	//alert("Destroyed requests: "+nDCount);
}
});

var AjaxRequest = Class.create({
initialize: function(sMethod, sURL, sParameters, oOptions, bAsync) {
	if (typeof bAsync == "undefined") bAsync = true;
	this.sMethod_ = sMethod.toUpperCase();
	if (this.sMethod_ != "GET" && this.sMethod_ != "POST" && this.sMethod_ != "POSTMULTIPART") {
		throw new Error("AjaxRequest(): Invalid method (proper values are 'GET', 'POST' and 'POSTMULTIPART')! ");
	}
	this.sURL_ = sURL;
	this.sParameters_ = sParameters;
	this.sExceptionText_ = "";

	if (AR_LOGLEVEL) {
		LogL("$$ " + this.sMethod_ + " " + this.sURL_ + " $$");
	}

	this.bAsync_ = bAsync;
	
	this.oTransport_ = GXHRManager.GetTransport();
	if (!this.oTransport_) throw new Error("AjaxRequest: XHR creation failed! ");
	if (oOptions) {
		if (oOptions.onException) this.onException_ = oOptions.onException.bind(oOptions.thisObject);
		if (oOptions.onComplete) this.onComplete_ = oOptions.onComplete.bind(oOptions.thisObject);
		if (oOptions.onSuccess) this.onSuccess_ = oOptions.onSuccess.bind(oOptions.thisObject);
		if (oOptions.timeout) this.timeout = oOptions.timeout;
	}
	// request counting
	var nReqNum = AXHRManager.prototype.nReqNum_;
	this.nReqNum_ = nReqNum;
	AXHRManager.prototype.nReqNum_++;
	AXHRManager.prototype.aRequests_[nReqNum] = this;
		
	try  {
		var m = this.sMethod_;
		if (m == 'POSTMULTIPART') m = 'POST';
		this.oTransport_.open(m, this.makeURL(), this.bAsync_);
	} catch (exc) {
		this.raiseError("Error while opening a transport (method: '"+this.sMethod_+"', url: '"+
			this.makeURL()+"', async: '"+this.bAsync_+"'). Message is ' "+exc.message+"' ");
	}
	if (this.oTransport_.readyState == 0) {
		LogE("AJAX transport opening failed (method: '"+this.sMethod_+"', url: '"+
			this.makeURL()+"', async: '"+this.bAsync_+"'). ");
		return;
	}

	if (this.bAsync_) this.oTransport_.onreadystatechange = this.RequestCallback.bind(this);
	if (this.sMethod_ == "POST") {
		if (typeof(sParameters) == 'undefined' || sParameters === null) sParameters = "";
		this.oTransport_.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		if (!Prototype.Browser.WebKit) {
			this.oTransport_.setRequestHeader("Content-length", sParameters.length);
			this.oTransport_.setRequestHeader("Connection", "close");
		}
		if (oOptions.headers) {
			for (var key in oOptions.headers) {
				var val = oOptions.headers[key];
				if (typeof(val) == "string") {
					this.oTransport_.setRequestHeader(key, val);
				}
			}
		}
		try {
			this.oTransport_.send(sParameters);
		} catch (exc) {
			this.raiseError("AJAX POST Error: Send failed with message '"+ exc.message + "'. ");
			return;
		}
	}
	else if (this.sMethod_ == "POSTMULTIPART") {
		this.postMultipart();
	}
	else {
		// TODO: work out this !!! overrideMimeType for text/xsl (!!!)
		if (!this.bAsync_  && this.oTransport_.overrideMimeType) this.oTransport_.overrideMimeType('text/xml');
		try {
			this.oTransport_.send(null);
		} catch (exc) {
			this.raiseError("AJAX GET Error: Send failed with message '"+ exc.message + "'. ");
			return;
		}
	}
	//LogL("AjaxRequest: Started "+this.sURL_);
	// in case of sync requests, set state flags and get data
	if (!this.bAsync_) {
		this.SetMessage("Completed", true);
		// drop from active requests
		AXHRManager.prototype.aRequests_[this.nReqNum_] = undefined;
		if (this.onComplete_) this.onComplete_(this);
		this.CompleteRequest();
	}
	else if(this.timeout) { // async request with timeout defined
		this.execTimer=this.timeIsOut.bind(this).delay(this.timeout);
	}
},
timeIsOut: function() {
	this.execTimer=undefined;
	this.Kill();
	this.raiseError("Timeout exceeded for call (method: '"+this.sMethod_+"', url: '"+
			this.makeURL()+"', async: '"+this.bAsync_+"'). ");
},
postMultipart: function() {
	if (!this.sParameters_) return;
	var boundary = ("" +Math.random() + Math.random() + Math.random() + Math.random()).replace(/0\./g, '');

	this.oTransport_.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
	this.oTransport_.setRequestHeader("Connection", "close") 

	boundary = "--" + boundary;
	var request = "";
	for (var i = 0; i < this.sParameters_.length; ++i) {
		request += boundary + "\r\n";
		for (var key in this.sParameters_[i]) {
			if (key == "data") continue;

			var v = this.sParameters_[i][key];
			if (!v) continue;

			request += key + ': ' + v + "\r\n";
		}
		request += "\r\n" + this.sParameters_[i].data + "\r\n";
	}
	request += boundary + "--\r\n";

	this.oTransport_.setRequestHeader("Content-Length", request.length);
	try {
		this.oTransport_.send(request);
	} catch (exc) {
		this.raiseError("AJAX POSTMULTIPART Error: Send failed with message '"+ exc.message + "'. ");
		return;
	}

	
},
makeURL: function() {
	if (this.sMethod_ == "POST" || this.sMethod_ == "POSTMULTIPART" || !this.sParameters_) {
		return this.sURL_;
	}

	var nP = this.sURL_.indexOf('?');
	if (nP != -1) {
		// ? already exists in sURL
		// TODO (FIXME): probably amp must not be added (as URL serializers glue it)
		return this.sURL_ + '&' + this.sParameters_;
	}
	return this.sURL_ + '?' + this.sParameters_;
},

RequestCallback: function() {
	// 'this' bound to AjaxRequest object itself
	
	if(this.execTimer) window.clearTimeout(this.execTimer);
	if (!this.oTransport_) throw new Error("AjaxRequest::RequestCallback: Request not initialized! ");
	if (this.oTransport_.readyState != 4) return;
	// drop from active requests
	AXHRManager.prototype.aRequests_[this.nReqNum_] = undefined;
	
	this.SetMessage("Completed", true);
	if (this.onComplete_) this.onComplete_(this);
	// do not handle local calls
	if (this.oTransport_.status == 0) return;
	this.CompleteRequest();
},
CompleteRequest: function() {
	if (this.oTransport_.status == 200)
	{ /* server call done */ }
	else if (this.oTransport_.status == 400)
	{ /* server call done, got exception */ }
	else if (this.oTransport_.status == 404)
	{
		this.raiseError("Request URL '"+this.sURL_+"' does not exist. ");
		return;
	}
	else
	{
		if (this.bAsync) {
			this.raiseError("Unknown error: Status code is " + this.oTransport_.status + ", readyState is " + this.oTransport_.readyState+". ");
			return;
		}
	}

	this.substXml_ = undefined;
	this.bCompleted_ = true;

	var oXML = this.oTransport_.responseXML;
	if (!oXML || !oXML.documentElement) {
		var doc = new AXML();
		doc.Parse(this.oTransport_.responseText);
		this.substXml_ = doc.XML();
		oXML = this.substXml_;
		if (!oXML) {
			var sResponse = this.oTransport_.responseText;
			this.raiseError("Ajax ERROR: Got text response instead of XML: '"+sResponse+"'. ");
			return;
		}
	}
	if (oXML.documentElement) {
		if(oXML.documentElement.nodeName == "parsererror") {
			// bad XML received (Mozilla)
			var sResponse = oXML.xml;
			this.raiseError("Ajax ERROR: Parser Error: "+AXML.EscapeEntities(sResponse));
			return;
		}
		else if( oXML.documentElement.nodeName == "xmlstream") {
			var xR = oXML.documentElement;
			if (xR.firstChild.nodeName=="Exception") {
				try {
					var exc=XMLRestore(this.oTransport_.responseText);
					this.raiseError(exc.Message(), exc.ErrorCode());
					return;
				}
				catch(err) {
					LogE("Unrecognized xmlstream/Exception: "+this.oTransport_.responseText);
				}
			} else if (this.oTransport_.status == 400 && xR.firstChild.nodeName=="APacket") {
				var oPacket;
				try {
					oPacket = XMLRestore(this.oTransport_.responseText);
					var oData = oPacket.Data();
					var sErrorText = oData["ExceptionText"];
					// unsigned int lies in ErrorCode
					var nErrorCode = (typeof(oData["ErrorCode"]) != 'undefined') ? oData["ErrorCode"].Value() : undefined;
					this.raiseError(sErrorText, nErrorCode);
					return;
				} catch (err) {
					LogE("Unrecognized xmlstream/APacket: "+this.oTransport_.responseText);
				}
			}
		}
	} else {
		alert("AjaxRequest: oXML.documentElement is NULL! Text Content is: "+this.oTransport_.responseText);
		return;
	}

	//LogL("AjaxRequest: "+this.sURL_+" completed.");
	this.SetMessage("Success", true);
	if (AR_LOGLEVEL) {
		LogL("$$ DONE: " + this.sMethod_ + " " + this.sURL_ + " $$");
	}


	if (this.onSuccess_) this.onSuccess_(this);
},
XML: function() {
	if (!this.bCompleted_) throw new Error("AjaxRequest: Request not completed yet! ");
	if (this.substXml_) {
		return this.substXml_;
	}
	return this.oTransport_.responseXML;
},
Status: function() {
	return this.oTransport_.status;
},
// TODO: remove this function, rewrite to exception handlers
SetMessage: function(sMessage, bSuccess) {
	if (!sMessage) sMessage = "";
	var sClassName = (!bSuccess) ? "ajaxerror" : "ajaxsuccess";
	// TODO: Remove spike with AjaxError element
	var elAjaxError = $("AjaxError");
	if (elAjaxError) elAjaxError.innerHTML =
		"<span class=\""+sClassName+"\">Ajax Message: " + AXML.EscapeEntities(sMessage) + "</p>";
},
raiseError: function(sMessage, code) {
	// TODO: set error codes for typical AJAX errors
	this.sExceptionText_ = sMessage;
	this.ErrorCode_ = code;
	if (this.onException_) this.onException_(this);
},
Error: function() {
	return this.sExceptionText_;
},
ErrorCode: function() {
	return this.ErrorCode_;
},
Kill: function() {
	// kill server request
	if (this.oTransport_) this.oTransport_.abort();
	this.bCompleted_=true;
},
IsAsync: function() { return this.bAsync_; }
});

var GXHRManager = new AXHRManager();

