var Ajax = {};
Ajax.objectCounter = 0;

///////////////////////////////////////
/**************************************
ENUMS
**************************************/
///////////////////////////////////////
Ajax.methodEnum = {GET: 'GET', POST: 'POST'};
Ajax.typeEnum = {ASYNC: true, SYNC: false};
Ajax.responseTypeEnum = {OK:200, FAIL:400, TIMEOUT:-15000, UNAVAILABLE: 503};
Ajax.objectTypeEnum = {ACTIVEX: 4, XHR: 5};

///////////////////////////////////////
/**************************************
Headers CLASS
**************************************/
///////////////////////////////////////
Ajax.Headers = function()
{}
Ajax.Headers.prototype.dispose = function()
{
	if (this.mArray !== null) delete this.mArray;	
}
Ajax.Headers.prototype.mArray = null;
Ajax.Headers.prototype.arraySize = 0;
Ajax.Headers.prototype.add = function(name, value)
{
	if (this.mArray === null) this.mArray = new Array();
	if (typeof(name) !== 'string' || typeof(value) !== 'string')
	{
		throw("headers add: name, value type are wrong");
		return;
	}
	
	this.mArray[name] = value;
	++this.arraySize;
}
Ajax.Headers.prototype.remove = function(name)
{
	if (this.mArray === null) return;
	if (typeof(name) !== 'string')
	{
		throw("headers remove: name type is wrong");
		return;
	}
	
	if (typeof(this.mArray[name]) !== 'undefined')
		delete this.mArray[name];
}
Ajax.Headers.prototype.update = function(name, value)
{
	if (this.mArray === null) return;
	if (typeof(name) !== 'string' || typeof(value) !== 'string')
	{
		throw("headers update: name, value type are wrong");
		return;
	}
	
	this.mArray[name] = value;
}
Ajax.Headers.prototype.getValue = function(name)
{
	if (this.mArray === null) return null;
	if (typeof(this.mArray[name]) !== 'undefined')
		return this.mArray[name];
	else
		return null;
}

///////////////////////////////////////
/**************************************
TOKEN CLASS
**************************************/
///////////////////////////////////////
Ajax.Token = function(ctorObject)
{
	if (typeof(ctorObject) !== 'object') return;
	if (typeof(ctorObject.id) !== 'undefined') this.id = ctorObject.id;
	if (typeof(ctorObject.tag) !== 'undefined') this.tag = ctorObject.tag;
	if (typeof(ctorObject.disposable) === 'boolean') this.disposable = ctorObject.disposable;
	
	this.callback = ctorObject.callback;
}
Ajax.Token.prototype.dispose = function()
{
	// preventing from 'tag' to be disposed
    delete this.tag;
}
Ajax.Token.prototype.id = null;
Ajax.Token.prototype.callback = null;
Ajax.Token.prototype.tag = null;
Ajax.Token.prototype.disposable = true;

///////////////////////////////////////
/**************************************
Ajax CLASS
**************************************/
///////////////////////////////////////
Ajax.Request = function(url, requestMethod, requestType, headers)
{
	this.mUrl = url || null;	
	this.mRequestMethod = (requestMethod.toUpperCase() === Ajax.methodEnum.POST) ? Ajax.methodEnum.POST : Ajax.methodEnum.GET;
	this.mRequestType = requestType === Ajax.typeEnum.SYNC ? Ajax.typeEnum.SYNC : Ajax.typeEnum.ASYNC;
	
	// url //
	if (this.mUrl === null)
	{
		throw("Ajax.Request c'tor: No url provided");
		return null;
	}

    this.mEnableStatisticsAndLog = false;
	
	if (headers instanceof Ajax.Headers)
		this.Headers = headers;
	else
		this.Headers = new Ajax.Headers();
	
	this.mNum = Ajax.objectCounter++;
	
	// initiating request //
	this.initRequest();
	
	// initiating events //
	this.incomingResponseEvent = new EVENTS.Event();
	
	return this;
}
Ajax.Request.prototype.dispose = function()
{
	this.incomingResponseEvent.dispose();
	this.incomingResponseEvent = null;
	if(this.Headers)
	{
		this.Headers.dispose();
		this.Headers = null;	
	}
	delete this.Log;
	this.mIsRunning = false;
	delete this.mXmlhttp;
	delete this.incomingResponseEvent;
}
///////// VARIABLES 
Ajax.Request.prototype.mNum = 0;
Ajax.Request.prototype.mUrl = '';
Ajax.Request.prototype.data = '';
Ajax.Request.prototype.mAsyncObject = '';
Ajax.Request.prototype.mRequestMethod = Ajax.methodEnum.GET;
Ajax.Request.prototype.mXmlhttp = null;
Ajax.Request.prototype.mRequestType = Ajax.typeEnum.ASYNC;
Ajax.Request.prototype.mIsRunning = false;
Ajax.Request.prototype.incomingResponseEvent = null;
Ajax.Request.prototype.timeout = 10000;
Ajax.Request.prototype.mTimeoutObject = null;
Ajax.Request.prototype.mTimeoutHandled = false;
Ajax.Request.prototype.mTimedOut = false;
Ajax.Request.prototype.mMaxAttempts = 3;
Ajax.Request.prototype.mLastSendDate = null;
Ajax.Request.prototype.mNumberOfRequests = 0;
Ajax.Request.prototype.mAverageResponseTime = 0;
Ajax.Request.prototype.mLastResponseTime = 0;
Ajax.Request.prototype.mEnableStatisticsAndLog = false;
Ajax.Request.prototype.Headers = null;
Ajax.Request.prototype.Log = null;
Ajax.Request.prototype.token = null;
Ajax.Request.prototype.objectType = -1;

///////// METHODS
Ajax.Request.prototype.initRequest = function()
{
	var xmlhttp = null;
	try 
	{
		try
		{
			// Firefox, Konqueror, Safari, Chrome, Opera, IE7, IE8
			if (typeof(XMLHttpRequest) === 'function' || typeof(XMLHttpRequest) === 'object')
			{
				xmlhttp = new XMLHttpRequest();
				this.objectType = Ajax.objectTypeEnum.XHR;	
			}
		}
		catch(e)
		{
			xmlhttp = null;	
		}
		
		// IE6 and sometimes IE7
		if (xmlhttp === null && (typeof(ActiveXObject) === 'function' || typeof(ActiveXObject) === 'object'))
		{
			for(var i=6;i>=-1;--i)
			{
				try
				{
					if (i === -1)
						xmlhttp = new Ajax.ActiveX('Microsoft.XMLHTTP');
					else if (i === 0)
						xmlhttp = new Ajax.ActiveX('MSXML2.XMLHTTP');
					else
						xmlhttp = new Ajax.ActiveX('MSXML2.XMLHTTP.' + i + ".0");
						
					break;
				}	
				catch(e)
				{
					xmlhttp = null;					
				}			
			}
			if (xmlhttp === null) throw("No Ajax object supported");
			this.objectType = Ajax.objectTypeEnum.ACTIVEX;
		}
		else if (xmlhttp === null)
		{
			throw("No Ajax object supported");
		}
	}
	catch(ex)
	{
		if (debugLevel)
		{
			alert("Ajax request: create ajax object exp: " + ex);
		}
		this.mXmlhttp = null;
		this.addToLog("Exception. Couldnt create xmlhttp object.");
		throw(ex);
	}
	
	this.mXmlhttp = xmlhttp;
	
	// adding a referrence to this object //
	this.mXmlhttp.referrence = this;
	
	// log //
	//this.addToLog("xmlhttp object created");
}
Ajax.Request.prototype.send = function (data, token)
{
    // checking required properties //
    if (this.mXmlhttp === null)
    {
        throw ("no xml object created");
    }
    else if (!(this.Headers instanceof Ajax.Headers))
    {
        throw ("Ajax Headers is not instanceof Ajax.Headers");
    }
    else if (this.mIsRunning)
    {
        throw ("Ajax is already running!");
    }

    // setting flag //
    this.mIsRunning = true;

    // saving data //
    data = data || '';
    this.data = data;

    // saving token //
    if (typeof (token) !== 'undefined') this.token = token;
    if (this.token !== null && !this.token instanceof Ajax.Token) throw ("Ajax.Request.prototype.send: token is not of type Ajax.Token");

    // adding data to url //
    if (this.mRequestMethod === Ajax.methodEnum.GET)
        this.mUrl += "?" + data;
    else if (this.Headers.getValue("Content-Type") === null)
        this.Headers.add("Content-Type", "application/x-www-form-urlencoded");
    // opening request //
    this.mXmlhttp.open(this.mRequestMethod, this.mUrl, this.mRequestType);

    // adding headers //
    for (var headerName in this.Headers.mArray)
        this.mXmlhttp.setRequestHeader(headerName, this.Headers.mArray[headerName]);

    // setting response handling //
    if (General.isIE || General.browser === 'opera')
    {
        this.mXmlhttp.onreadystatechange = function ()
        {
            switch (this.readyState)
            {
                case 1:
                    //this.referrence.addToLog("request is packed");
                    break;
                case 2:
                    //this.referrence.addToLog("request was sent");
                    break;
                case 3:
                    //this.referrence.addToLog("response is being sent by server");
                    break;
                case 4:
                    this.referrence.handleResponse();
                    break;
            }
        }
        if (General.browser !== "ie6")
        {
            this.mXmlhttp.onerror = function ()
            {
                this.referrence.handleResponse();
            }
        }
    }
    else
    {
        this.mXmlhttp.onload = function ()
        {
            this.referrence.handleResponse();
        }
        this.mXmlhttp.onerror = function ()
        {
            this.referrence.handleResponse();
        }
    }

    /// starting timer ///
    var xmlhttp = this.mXmlhttp;
    this.mTimeoutObject = setTimeout(
    	function ()
    	{
    	    //alert('aborting');
    	    //xmlhttp.referrence.addToLog("request timed out");
    	    xmlhttp.referrence.mTimeoutHandled = false;
    	    xmlhttp.referrence.mTimedOut = true;

    	    // aborting request 
    	    xmlhttp.abort();

    	    /// when 'aborting' a request in OPERA browser,
    	    /// sometimes it closes the request with an empty 200 response && sometimes it doesn't close the request (didn't find the logical explanation).
    	    /// if the request was closed with an empty 200 (and the response was handled), not calling directly to 'handleResponse()'
    	    if (!xmlhttp.referrence.mTimeoutHandled)
    	        xmlhttp.referrence.handleResponse();

    	    // removing flag
    	    xmlhttp.referrence.mTimedOut = false;
    	},
	this.timeout);

    /// calculating times ///
    //this.addToLog("opening connection");
    if (this.mEnableStatisticsAndLog) this.mLastSendDate = new Date();

    // setting flag
    xmlhttp.referrence.mTimedOut = false;

    /// sending the request ///
    if (this.mRequestMethod === Ajax.methodEnum.POST)
        this.mXmlhttp.send(this.data);
    else
        this.mXmlhttp.send();
}
Ajax.Request.prototype.handleError = function (response)
{
    response.type = Ajax.responseTypeEnum.FAIL;
    this.mIsRunning = false;
    this.incomingResponseEvent.dispatch(response);
}
Ajax.Request.prototype.handleTimeout = function (response)
{
    response.type = Ajax.responseTypeEnum.TIMEOUT;
    this.mIsRunning = false;
    this.mTimeoutHandled = true;
    this.incomingResponseEvent.dispatch(response);
}
Ajax.Request.prototype.handleResponse = function ()
{
    // calculating average response time //
    if (this.mEnableStatisticsAndLog)
    {
        var now = new Date();
        this.mLastResponseTime = now - this.mLastSendDate;
        var totalTime = (this.mAverageResponseTime * this.mNumberOfRequests++);
        this.mAverageResponseTime = (totalTime + this.mLastResponseTime) / this.mNumberOfRequests;
    }

    // canceling timeout //
    clearTimeout(this.mTimeoutObject);
    this.mTimeoutObject = null;

    // creating response object
    var response = new Ajax.Response();
    response.request = this;

    // moving token pointer
    response.token = this.token;
    this.token = null;

    // status //
    try
    {
        response.status = this.mXmlhttp.status;
        if (!response.status) response.status = 400;
        response.statusText = this.mXmlhttp.statusText || "";
    }
    catch (e)
    {
        if (this.mTimedOut)
        {
            response.status = -1;
            response.statusText = "Timed Out";
        }
        else
        {
            response.status = 400;
            response.statusText = "Fail";
        }
    }

    // XHR object confuses and 'thinks' it got 200 response, although there are no response headers
    // (assuming it confuses due to a successful hand-shake)
    if (this.objectType === Ajax.objectTypeEnum.XHR)
    {
        try
        {
            if (!this.mXmlhttp.getAllResponseHeaders()) this.mTimedOut = true;
        }
        catch (e)
        {
            this.mTimedOut = true;
        }
    }

    // log //
    //this.addToLog("response received, status: " + status);

    try
    {
        // saving response body //
        response.text = this.mXmlhttp.responseText || "";
        response.xml = null;
        response.json = null;

        // parsing response //
        if (response.text !== "")
        {
            try
            {
                // replacing system chars (error bytes from server) //
                var fixedText = new Array();
                var l = response.text.length;
                for (var i = 0; i < l; ++i)
                {
                    var _code = response.text.charCodeAt(i); // char to int
                    if (_code > 7) fixedText.push(response.text.charAt(i));
                }
                response.text = fixedText.join('');

                // removing reference
                fixedText = null;

                // getting content-type header
                var contentType = 'text/html';
                if (this.objectType === Ajax.objectTypeEnum.ACTIVEX)
                {
                    contentType = this.mXmlhttp.responseHeaders.getValue("content-type");
                }
                else
                {
                    contentType = this.mXmlhttp.getResponseHeader("content-type");
                    if (contentType === null) contentType = this.mXmlhttp.getResponseHeader("Content-Type");
                    if (contentType === null) contentType = this.mXmlhttp.getResponseHeader("CONTENT-TYPE");
                }
				
                // xml //
                if (contentType !== null && contentType.indexOf("text/xml") !== -1)
                {
                    // safari 3 doesn't handle empty cdata elements.
                    // this 'hack' sets 'safari3_emptyCDataFix' as a value of empty cdata element.
                    // the change allows the responseText to be parsed as XML.
                    if (ISQ.Http.browser.full === 'safari3') response.text = response.text.replace(/\[CDATA\[\]\]/gi, "[CDATA[safari3_emptyCDataFix]]");

                    response.xml = ISQ.Data.Xml.toXML(response.text);

                    // removing the hack for safari3
                    if (ISQ.Http.browser.full === 'safari3') this.removeSafari3Fix(response.xml);
                }
                // json //
                else if (contentType !== null && contentType.indexOf("text/javascript") !== -1)
                {
                    response.json = eval('(' + response.text + ')'); //JSON parse
                }
            }
            catch (ex)
            {
                alert("Ajax.handleResponse exp(1):" + e);
                response.text = "";
                response.xml = null;
                response.json = null;
                //this.addToLog("Could not parse response: " + ex);
            }
        }

        // time out
        if (this.mTimedOut)
        {
            this.handleTimeout(response);
        }
        // if server returns 200
        else if (response.status === 200)
        {
            response.type = Ajax.responseTypeEnum.OK;
            this.mIsRunning = false;
            this.incomingResponseEvent.dispatch(response);
        }
        else if (response.status === 503)
        {
            this.type = Ajax.responseTypeEnum.UNAVAILABLE;
            this.mIsRunning = false;
            this.incomingResponseEvent.dispatch(response);
        }
        // 12029, 12152: ie status represents server has closed connection - network issue.
        // other abort cases are handled in the timeout function
        else if (response.status === 12029 || response.status === 12152)
        {
            this.handleTimeout(response);
        }
        else //if (status === 400 || status === 404 || status === 500)
        {
            this.handleError(response);
        }
    }
    catch (ex)
    {
        alert("Ajax.handleResponse exp(2):" + ex);
        this.handleError(response);
    }

    // disposing response
    response.dispose();
}

Ajax.Request.prototype.removeSafari3Fix = function (node)
{
    var childNodes = node.childNodes;
    for (var i = 0; i < childNodes.length; ++i)
	{
		// if this node is a regular node (not cdata),
		// checking his child nodes
		if (childNodes[i].nodeType === 1)
			this.removeSafari3Fix(childNodes[i]);
		else if (childNodes[i].nodeType === 4)
		{
			var value = childNodes[i].nodeValue;
			if (value === "safari3_emptyCDataFix") childNodes[i].nodeValue = "";
		}
	}
}

Ajax.Request.prototype.addToLog = function (text)
{
    if (!text || !this.mEnableStatisticsAndLog) return;

    var d = new Date();
    var time = d.ToString("%d/%m/%y   %H:%M:%S");

    if (this.Log === null) this.Log = new Array();
    this.Log.push(time + "  -  " + text);
}

Ajax.Request.prototype.logToHTML = function()
{
	var html = new Array();
	var l = this.Log.length;
	for (var i=0;i<l;++i)
	{
		html.push(this.Log[i] + "<br/>");
	}
	return html.join("");
}

Ajax.Request.prototype.logToString = function()
{
	var html = new Array();
	var l = this.Log.length;
	for (var i=0;i<l;++i)
	{
		html.push(this.Log[i] + "\n");
	}
	return html.join("");
}

///////////////////////////////////////////////////////////////////////////
///////// IE6 ACTIVEX OBJECT IMPROVED
///////////////////////////////////////////////////////////////////////////
Ajax.ActiveX = function(progid)
{
	var ax = new ActiveXObject(progid);
	var object = null;
	// properties //
	object = 
	{
		_ax: ax,
      	responseText: "",
      	responseXml: null,
      	readyState: 0,
      	status: 0,
      	statusText: 0,
      	onreadystatechange: null,
      	referrence : null,
      	responseHeaders : null
	}
	
	// methods //
	object._onreadystatechange = function()
	{
		var self = object;
		self.readyState = self._ax.readyState;
		if (self.readyState === 4)
		{
			try
			{
				self.responseXml = self._ax.responseXml;
				self.responseText = self._ax.responseText;
				self.status = self._ax.status;
				self.statusText = self._ax.statusText;
			}
			catch(e)
			{
				self.responseText = "";
				self.responseXml = null;
				self.status = 400;
				self.statusText = "";
				alert(e.message);
			}
			
			// getting response headers
			var allResponseHeaders = self._ax.getAllResponseHeaders();
			self.responseHeaders = new Ajax.Headers();
			var _split = allResponseHeaders.split("\n");
			for (var i=0;i<_split.length;++i)
			{
				var _header = _split[i];
				
				// skipping an empty header (\r\n)
				if (_header.Trim() === '') continue;
				
				var _headerL = _header.length;
				var key = new Array();
				var ndx = 0;
				
				// key
				while (ndx < _headerL && _header.charAt(ndx) !== ':')
					key.push(_header.charAt(ndx++));
					
				// skipping ':'
				++ndx;
				
				// value
				var value = new Array();
				while (ndx < _headerL)
					value.push(_header.charAt(ndx++));
				// adding header
				self.responseHeaders.add(key.join('').toLowerCase(), value.join('').toLowerCase());
			}
			
			self.abort();
		}
		if (typeof(self.onreadystatechange) === 'function')
			self.onreadystatechange();
	}
	object.open = function(bstrMethod, bstrUrl, varAsync, bstrUser, bstrPassword)
	{
		varAsync = (varAsync !== false);
		this._ax.onreadystatechange = this._onreadystatechange
		this._ax.open(bstrMethod, bstrUrl, varAsync, bstrUser, bstrPassword);
	};
	object.send = function(varBody)
	{
		varBody = varBody || null;
		this._ax.send(varBody);
	};
	object.setRequestHeader = function(param, value)
	{
		if (!param || !value) return;
		
		this._ax.setRequestHeader(param, value);	
	}
	object.abort = function()
	{
		this._ax.abort();
	}
	return object;
}


///////////////////////////////////////////////////////////////////////////
///////// Response Object
///////////////////////////////////////////////////////////////////////////
Ajax.Response = function()
{}
Ajax.Response.prototype.dispose = function ()
{
    delete this.data;
    delete this.text;
    delete this.xml;
    delete this.json;
    delete this.request;
    delete this.type;
    delete this.status;
    delete this.statusText;

    if (this.token !== null && this.token.disposable) this.token.dispose();
    this.token = null;
    delete this.token;
}
Ajax.Response.prototype.data = null;
Ajax.Response.prototype.text = null;
Ajax.Response.prototype.xml = null;
Ajax.Response.prototype.json = null;
Ajax.Response.prototype.token = null;
Ajax.Response.prototype.request = null;
Ajax.Response.prototype.type = Ajax.responseTypeEnum.FAIL;
Ajax.Response.prototype.status = 400;
Ajax.Response.prototype.statusText = "Bad Request";
