/**
 * AJAX library
 *
 */

/*-------------------------------------------------------------------------------------*/
/* Global variables                                                                    */
/*-------------------------------------------------------------------------------------*/

var __xhrThreads = [];


/*-------------------------------------------------------------------------------------*/
function xmXHRThread(aAjax)
{
    /*---------------------------------------------------------------------------------*/
    /* Initialization                                                                  */
    /*---------------------------------------------------------------------------------*/

    //trace("Ajax thread: creating xmlhttprequest object...\n");
    this.fXhr = false;

    if(window.XMLHttpRequest) {
        try {
		    this.fXhr = new XMLHttpRequest();
        } catch(e) {
		    this.fXhr = false;
        }

    } else if(window.ActiveXObject) {
        var xhrVersions = new Array(
            'MSXML2.XMLHTTP.6.0',
            'MSXML2.XMLHTTP.5.0',
            'MSXML2.XMLHTTP.4.0',
            'MSXML2.XMLHTTP.3.0',
            'MSXML2.XMLHTTP',
            'Microsoft.XMLHTTP'
        );

        for (var i = 0; i < xhrVersions.length && !this.fXhr; i++)
        {
            try
            {
                this.fXhr = new ActiveXObject(xhrVersions[i]);
            }
            catch (e) {}
        }
    }

	if (!this.fXhr) {
		//alert(__jsMsg["xmajax"]["cannt_create_thread"]);
        return;
	}


    /**
    *
    */
    this.sendRequest = function (aData)
    {
        clearTimeout(this.fTimeoutId);
        this.fTimeoutId = setTimeout(
            "if (isObject(__xhrThreads["+ this.fId +"]) && __xhrThreads["+ this.fId +"].fFree == false)"+
            "__xhrThreads["+ this.fId +"].fAjax._onRequestTimeout(__xhrThreads["+ this.fId +"])", this.fTimeout);
        //alert(this.fData);
        if (isObject(this.fXhr))
            this.fXhr.send(this.fData);
    }


    /**
     *
     */
    this._reset = function ()
    {
        this.fCallback    = null;
        this.fUserData    = null;
        this.fAsynch      = true;
        this.fURL         = "";
        this.fMethod      = "GET";
        this.fHeaders     = [];
        this.fData        = null;
        this.fNoStaticURL = false;
        this.fRunning     = false;
        this.fTimeout     = 60000; // timeout in miliseconds
        this.fFree        = true;

        // attach ready state change event
        this.fXhr.onreadystatechange = new Function (
            "__xhrThreads["+ this.fId +"].fAjax._onReadyStateChange(__xhrThreads["+ this.fId +"])"
        );
    }


    /*---------------------------------------------------------------------------------*/
    /* Properties                                                                      */
    /*---------------------------------------------------------------------------------*/

    this.fId      = aAjax.getNextThreadId();
    this.fAjax    = aAjax;

    this._reset();
}


/*-------------------------------------------------------------------------------------*/
function AJAX()
{
    /*---------------------------------------------------------------------------------*/
    /* Properties                                                                      */
    /*---------------------------------------------------------------------------------*/

    /***** Private *****/
    this.fNextThreadId = 1;

    /***** Public *****/
    this.staticURL = "?ajax=1&";

    this.onBeforeRequestStart = null;
    this.onRequestStart       = null;
    this.onRequestsDone       = null;
    this.onRequestTimeout     = null;


    /*---------------------------------------------------------------------------------*/
    /* Private Methods                                                                 */
    /*---------------------------------------------------------------------------------*/

    /**
     *
     */
    this._createThread = function ()
    {
        //trace("Ajax: creating new thread...\n");
        for (id in __xhrThreads)
        {
            if (isObject(__xhrThreads[id]) && __xhrThreads[id].fFree)
            {
                __xhrThreads[id]._reset();
                __xhrThreads[id].fFree = false;
                return __xhrThreads[id];
            }
        }
        var thread = new xmXHRThread(this);
        return thread;
    }


    /**
     *
     */
    this._destroyThread = function (aThreadId)
    {
        //trace("Ajax: destroying thread #" + aThreadId);
        try
        {
            if (isObject(__xhrThreads[aThreadId]) && isObject(__xhrThreads[aThreadId].fXhr))
            {
                __xhrThreads[aThreadId].fXhr.onreadystatechange = function() {};
                //__xhrThreads[aThreadId].fXhr = null;
            }
        }
        finally
        {
            try
            {
                __xhrThreads[aThreadId].fXhr.abort();
                __xhrThreads[aThreadId]._reset();
                __xhrThreads[aThreadId].fFree = true;
            }
            finally
            {
                if (isFunction(this.onRequestsDone) && this._threadsCount(null, -1, null) == 0)
                    this.onRequestsDone();
            }
        }
    }


    /**
     *
     */
    this._onReadyStateChange = function (aThread)
    {
        if (aThread.fXhr.readyState == 4)
        {
            //trace("Ajax: request completed #" + aThread.fId);
            try
            {   //alert("Receiving "+aThread.fId);
                if (isFunction(aThread.fCallback))
                    aThread.fCallback(aThread);
            }
            finally
            {
                try
                {
                    this._destroyThread(aThread.fId);
                }
                finally
                {
                    //alert("running next synch thread");
                    this._runNextSynchThread();
                }
            }
        }
    }


    /**
     *
     */
    this._onRequestTimeout = function (aThread)
    {
        if (!isObject(aThread)) return;

        //trace("Ajax: request timeout #" + aThread.fId);

        try
        {
            try
            {
                aThread.fXhr.onreadystatechange = function() {};
            }
            finally
            {
                if (isFunction(this.onRequestTimeout))
                    this.onRequestTimeout();
            }
        }
        finally
        {
            this._destroyThread(aThread.fId);
        }
    }


    /**
     *
     */
     this._runNextSynchThread = function ()
     {
        for (key in __xhrThreads)
        {
            if (isObject(__xhrThreads[key]) && isObject(__xhrThreads[key].fXhr) && !__xhrThreads[key].fAsynch)
            {
                //alert(__xhrThreads[key].fAsynch);
                //trace("Ajax: executing next thread...");
                if (this._runThread(key, true))
                    return;
            }
        }
     }


    /**
     *
     */
    this._runThread = function (aThreadId)
    {
        try
        {
            if (__xhrThreads[aThreadId].fRunning)
                return false;

            if (!__xhrThreads[aThreadId].fAsynch && this._threadsCount(null, aThreadId, true) > 0)
                return false;

            var thread = __xhrThreads[aThreadId];

            with (thread)
            {
                if ((isFunction(this.onBeforeRequestStart) && !this.onBeforeRequestStart()) ||
                    (isFunction(this.onRequestStart) && !this.onRequestStart()))
                    {
                        //trace("Ajax: request #"+fId+" aborted from event (onBeforeRequestStart or onRequestStart)");
                        _reset();
                        fFree = true;
                        //delete __xhrThreads[aThreadId];
                        return false;
                    }

                fRunning = true;

                //trace("Ajax: sending request #" + fId + " " + ((fNoStaticURL === true) ? fURL : this.staticURL + fURL));

                var time = (new Date()).getTime();
                fXhr.open(fMethod, ((fNoStaticURL === true) ? fURL : this.staticURL + fURL) + "&" + time, true);

                //fXhr.setRequestHeader('Cookie', '__test__=abugfix');
                //fXhr.setRequestHeader('Cookie', 'xmTermin='+readCookie('xmTermin'));

                for (name in fHeaders) {
                    fXhr.setRequestHeader(name, fHeaders[name]);
                    //alert(name + ": " +fHeaders[name]);
                }

                thread.sendRequest(fData);
                return true;
            }
        }
        catch (e)
        {
            this._destroyThread(aThreadId);
            return true;
        }
    }


    /**
     *
     */
    this._threadsCount = function (aAsynch, aThreadId, aRunning)
    {
        var count = 0;

        for (key in __xhrThreads)
        {
            if (key != aThreadId && isObject(__xhrThreads[key]) && isObject(__xhrThreads[key].fXhr) && __xhrThreads[key].fFree == false
                && ((aAsynch === null) || (aAsynch == __xhrThreads[key].fAsynch))
                && ((aRunning === null) || (aRunning == __xhrThreads[key].fRunning)))
                count++;
        }

        return count;
    }


    /*---------------------------------------------------------------------------------*/
    /* Public Methods                                                                  */
    /*---------------------------------------------------------------------------------*/


    /**
     *
     */
    this.getNextThreadId = function ()
    {
        return this.fNextThreadId++;
    }


    /**
     *
     */
    this.sendRequest = function (aURL, aMethod, aCallback, aUserData, aNoStaticURL, aAsynch, aData, aHeaders)
    {
        if (!isString(aMethod))
            aMethod = "GET";

        //trace("Ajax: preparing input data\n");

        if (aMethod.toUpperCase() != "GET" && aMethod.toUpperCase() != "POST")
            return false;//(trace) ? trace('Invalid request method: ' + aMethod) : false;

        if (isUndefined(aNoStaticURL)) aNoStaticURL = false;
        if (isUndefined(aAsynch)) aAsynch = true;
        if (isUndefined(aData)) aData = null;
        if (!isObject(aHeaders)) aHeaders = [];

        var thread = this._createThread();

        with (thread)
        {
            fMethod      = aMethod;
            fCallback    = aCallback;
            fUserData    = aUserData;
            fAsynch      = aAsynch;
            fURL         = aURL;
            fHeaders     = aHeaders;
            fNoStaticURL = aNoStaticURL;
            fData        = aData;
            fFree        = false;
            // add this thread to global array
            __xhrThreads[fId] = thread;
            this._runThread(fId);
        }

        return true;
    }


    /**
     *
     */
    this.sendForm = function (aFormName, aURL, aMethod, aCallback, aUserData, aNoStaticURL, aAsynch, aHeaders)
    {
        if (!isObject(aHeaders)) aHeaders = [];

        var headers = [],
            postParams = this.getFormDataAsString(aFormName);

        headers["Content-Type"]   = "application/x-www-form-urlencoded";
        headers["Content-length"] = postParams.length;
        headers["Connection"]     = "close";

        for (name in aHeaders)
            headers[name] = aHeaders[name];

        this.sendRequest(aURL, aMethod, aCallback, aUserData, aNoStaticURL, aAsynch, postParams, headers);
    }


    /**
     *
     */
    this.getFormDataAsString = function (aFormName)
    {
        if (!document.forms[aFormName]) return null;

        var qstr = '',
            boxes = [],
            form = document.forms[aFormName];

        for (var i = 0; i < form.length; i++)
        {
            if (form[i].type == 'radio')
            {
                if (form[i].checked)
                    qstr += encodeURIComponent(form[i].name) + '=' + encodeURIComponent(form[i].value) + '&';
                continue;
            }
            if (form[i].type == 'checkbox') {
                boxes[form[i].name] = form[i].checked ? encodeURIComponent(form[i].value) : 0;
                continue;
            }
            qstr += (form[i].name != "") ? encodeURIComponent(form[i].name) + '=' + encodeURIComponent(form[i].value) + '&' : "";
        }

        for (key in boxes)
            if (boxes[key] != null)
                qstr += encodeURIComponent(key) + '=' + encodeURIComponent(boxes[key]) + '&';

        return qstr;
    }


    /**
    */
    this.abortAll = function ()
    {
        for (threadId in __xhrThreads)
            this._destroyThread(threadId);
    }


    /**
    */
    this.attachBeforeRequestStartHandler = function (aName, aHandler, aUserData)
    {
        objectEvents_AttachHandler(this, "onBeforeRequestStart", aHandler, aUserData);
    }


    /**
    */
    this.attachRequestStartHandler = function (aName, aHandler, aUserData)
    {
        objectEvents_AttachHandler(this, "onRequestStart", aHandler, aUserData);
    }


    /**
    */
    this.attachRequestsDoneHandler = function (aName, aHandler, aUserData)
    {
        objectEvents_AttachHandler(this, "onRequestsDone", aHandler, aUserData);
    }


    /**
    */
    this.attachRequestTimeoutHandler = function (aName, aHandler, aUserData)
    {
        objectEvents_AttachHandler(this, "onRequestTimeout", aHandler, aUserData);
    }
}
