/*
 * params:     as string:           param1=text1&param2=text2
 *             as hash:             {param1:'text1', param2:'text2'}
 *             as form              form
 *             as hash and form:    {frm:form, overridedFormElement:newvalue}
 * url:
 * targetId:
 * cache:      boolean
 * otherParams: as hash
 *         debug:  the id of html element where will be placed debug information (on error),
 *                 overrides the global var SGL_JS_DEBUG
 *     indicator:  {text:, class:, local:true/false},
 *                 overrides the global var SGL_JS_INDICATOR
 *   indicatorId:  where indicator should be shown
 *      callback:  javascript callback code
 *       session:  PHPSESSID=27673635f2748734355
 *                 overrides the global var SGL_JS_SESSID
 *        loader:  xml, script, form
 *        method:  GET, POST
 *        async :  true, false
 */
function jsSubmit(params, url, targetId, cache, otherParams) {
    var debug = (typeof SGL_JS_DEBUG == 'undefined') ? false : SGL_JS_DEBUG;
    debug = (typeof otherParams['debug'] == 'undefined') ? debug : otherParams['debug'];

    var session = (typeof SGL_JS_SESSID == 'undefined') ? false : SGL_JS_SESSID;
    session = (typeof otherParams['session'] == 'undefined') ? session : otherParams['session'];

    var indicatorParams = (typeof SGL_JS_INDICATOR == 'undefined')
        ? false : SGL_JS_INDICATOR;
    indicatorParams = (typeof otherParams['indicator'] == 'undefined')
        ? indicatorParams : otherParams['indicator'];

    var callback = (typeof otherParams['callback'] == 'undefined')
        ? false : otherParams['callback'];
    var async = (typeof otherParams['async'] == 'undefined')
        ? true : otherParams['async'];
    try {
        var req = new JsHttpRequest();

        if (indicatorParams) {
            if (typeof otherParams['indicatorId'] == 'undefined') {
                var indicator = document.createElement('SPAN');
                if (typeof indicatorParams['class'] != 'undefined') {
                    indicator.className = indicatorParams['class'];
                }
                indicator.innerHTML = (typeof indicatorParams['text'] != 'undefined')
                    ? indicatorParams['text'] : '';
                document.body.insertBefore(indicator, document.body.lastChild);
                var scrollXY = getScrollXY();
                indicator.style.position = 'absolute';
                indicator.style.top = scrollXY[1] + 'px';
                indicator.style.right = -scrollXY[0] + 'px';
            } else {
                var indicatorHTML = '<span ';
                if (typeof indicatorParams['class'] != 'undefined') {
                    indicatorHTML = indicatorHTML + 'class="' + indicatorParams['class'] + '">';
                }
                if (typeof indicatorParams['text'] != 'undefined') {
                    indicatorHTML = indicatorHTML + indicatorParams['text'];
                }
                indicatorHTML = indicatorHTML + '</span>';
                document.getElementById(otherParams['indicatorId']).innerHTML = indicatorHTML;
            }
        }

        req.onreadystatechange = function() {
            if (req.readyState == 4) {
                if (indicatorParams) {
                    if (typeof otherParams['indicatorId'] == 'undefined') {
                        indicator.parentNode.removeChild(indicator);
                    } else {
                        if (otherParams['indicatorId'] != targetId) {
                            document.getElementById(otherParams['indicatorId']).innerHTML = '&nbsp;';
                        }
                    }
                }
                if (req.responseText && debug) {
                    document.getElementById(debug).innerHTML =
                        req.responseText;
                    return;
                }
                if (req.responseJS) {
                    for (var k in req.responseJS) {
                        if (req.responseJS[k]['output'] && k) {
                            try {
                                    document.getElementById(k).innerHTML =
                                        req.responseJS[k]['output'];
                            } catch(e) {
                                if (debug) alert('innerHTML: ' + e);
                            }
                        }
                        if (req.responseJS[k]['script']) {
                            try {
                                eval(req.responseJS[k]['script']);
                            } catch (e) {
                                if (debug) alert(e + '. Script: ' + req.responseJS[k]['script']);
                            }
                        }
                    }
                    try {
                        if (callback) eval(callback);
                    } catch(e) {
                        if (debug) alert(e + '. Script: ' + callback);
                    }
                }
            }
        }
        req.caching = (typeof cache == 'undefined') ? false : cache;
        req.session = session;
        req.debug   = debug;
        req.loader  = (typeof otherParams['loader'] == 'undefined') ? null : otherParams['loader'];
        method      = (typeof otherParams['method'] == 'undefined') ? null : otherParams['method'];
        req.targetId = targetId;
        req.open(method, url, async);
        req.send(params);
    } catch(e) {
        if (debug) {
            alert(e);
        }
    }
}

function jsSubmitForm(form, cache, otherParams) {
    jsSubmit(form, form.action, form.parentNode.id, cache, otherParams);
}

function jsLoad(url) {
    jsSubmit('', url, '', true, {loader:'script', indicator:''});
}

function JsHttpRequest() { this._construct() }
(function() { // to create local-scope variables
    var COUNT       = 0;
    var PENDING     = {};
    var CACHE       = {};

    // Called by server script on data load.
        JsHttpRequest.dataReady = function(id, text, js) {
        var undef;
        var th = PENDING[id];
        delete PENDING[id];
        if (th) {
            delete th._xmlReq;
            if (th.caching && th.hash) CACHE[th.hash] = [text, js];
            th._dataReady(text, js);
        } else if (th !== false) {
            throw "JsHttpRequest.dataReady(): unknown pending id: " + id;
        }
    }

    JsHttpRequest.prototype = {
        // Standard properties.
        onreadystatechange: null,
        readyState:         0,
        responseText:       null,
        responseXML:        null,
        status:             200,
        statusText:         "OK",

        // Additional properties.
        targetId:           null,
        session:            null,
        responseJS:         null,         // JavaScript response array/hash
        caching:            true,         // need to use caching?
        fallbackToScript:   false,
        debug:              false,
        loader:             null,         // loader to use ('form', 'script', 'xml'; null - autodetect)
        charset:            null,

        // Internals.
        _span:              null,
        _id:                null,
        _xmlReq:            null,
        _openArg:           null,
        _reqHeaders:        null,
        _maxUrlLen:         2000,

        dummy: function() {}, // empty function

        abort: function() {
            if (this._xmlReq) {
                this._xmlReq.abort();
                this._xmlReq = null;
            }
            this._cleanupScript();
            this._changeReadyState(4, true); // 4 in IE & FF on abort() call; Opera does not change to 4.
        },

        open: function(method, url, asyncFlag, username, password) {
            this._openArg = {
                method:     (method||'').toUpperCase(),
                url:        url,
                asyncFlag:  asyncFlag,
                username:   username != null? username : '',
                password:   password != null? password : ''
            };
            this._id = null;
            this._xmlReq = null;
            this._reqHeaders = [];
            this._changeReadyState(1, true); // compatibility with XMLHttpRequest
            return true;
        },

        send: function(content) {
            this._changeReadyState(1, true); // compatibility with XMLHttpRequest

            var id = (new Date().getTime()) + "" + COUNT++;
            var url = this._openArg.url;

            // Append session, targetId, charset to original URL now.
            if (this.session) url += (url.indexOf('?')>=0? '&' : '?') + this.session;
            if (this.targetId) url += (url.indexOf('?')>=0? '&' : '?') + 'jsRequestTargetId=' + this.targetId;
            if (this.charset == null) {
                this.charset = (typeof document.charset == 'undefined') ? document.characterSet : document.charset;
            }
            url += (url.indexOf('?')>=0? '&' : '?') + 'jsRequestCharset=' + this.charset;

            // Prepare to build QUERY_STRING from query hash.
            var queryText = [];
            var queryElem = [];
            if (!this._hash2query(content, null, queryText, queryElem)) return;
            var loader = (this.loader||'').toLowerCase();
            var method = this._openArg.method;
            var xmlReq = null;
            if (queryElem.length && !loader) {
                // Always use form loader if we have at least one form element.
                loader = 'form';
            } else {
                // Try to obtain XML request object.
                xmlReq = this._obtainXmlReq(id, url)
            }

            // Full URL if parameters are passed via GET.
            var fullGetUrl = url + (url.indexOf('?')>=0? '&' : '?') + queryText.join('&');

            // Solve hashcode BEFORE appending ID and check if cache is already present.
            this.hash = null;
            if (this.caching && !queryElem.length) {
                this.hash = fullGetUrl;
                if (CACHE[this.hash]) {
                    var c = CACHE[this.hash];
                    this._dataReady(c[0], c[1]);
                    return false;
                }
            }

            // Detect loader and method. (Yes, lots of code and conditions!)
            var canSetHeaders = xmlReq && (window.ActiveXObject || xmlReq.setRequestHeader);
            if (!loader) {
                // Auto-detect loader.
                if (xmlReq) {
                    // Can use XMLHttpRequest.
                    loader = 'xml';
                    switch (method) {
                        case "POST":
                            if (!canSetHeaders) {
                                // Use POST method. Pass query in request body.
                                // Opera 8.01 does not support setRequestHeader, so no POST method.
                                loader = 'form';
                            }
                            break;
                        case "GET":
                            // Length of the query is checked later.
                            break;
                        default:
                            // Method is not set: auto-detect method.
                            if (canSetHeaders) {
                                method = 'POST';
                            } else {
                                if (fullGetUrl.length > this._maxUrlLen) {
                                    method = 'POST';
                                    loader = 'form';
                                } else {
                                    method = 'GET';
                                }
                            }
                    }
                } else {
                    // Cannot use XMLHttpRequest.
                    loader = 'script';
                    switch (method) {
                        case "POST":
                            loader = 'form';
                            break;
                        case "GET":
                            // Length of the query is checked later.
                            break;
                        default:
                            if (fullGetUrl.length > this._maxUrlLen) {
                                method = 'POST';
                                loader = 'form';
                            } else {
                                method = 'GET';
                            }
                    }
                }
            } else if (!method) {
                // Loader is pre-defined, but method is not set.
                switch (loader) {
                    case 'form':
                        method = 'POST';
                        break;
                    case 'script':
                        method = 'GET';
                        break;
                    default:
                        if (canSetHeaders) {
                            method = 'POST';
                        } else {
                            method = 'GET';
                        }
                }
            }

            // Correct GET URL.
            var requestBody = null;
            if (method == 'GET') {
                url = fullGetUrl;
                if (url.length > this._maxUrlLen) return this._error('Cannot use so long query (URL is ' + url.length + ' byte(s) length) with GET request.');
            } else if (method == 'POST') {
                requestBody = queryText.join('&');
            } else {
                return this._error('Unknown method: ' + method + '. Only GET and POST are supported.');
            }

            // Append loading ID to URL: a=aaa&b=bbb&<id>
            url = url + (url.indexOf('?')>=0? '&' : '?') + 'jsRequestId=' + id + '&jsRequestType=' + loader;

            // Save loading script.
            PENDING[id] = this;

            // Send the request.
            switch (loader) {
                case 'xml':
                    // Use XMLHttpRequest.
                    if (!xmlReq) return this._error('Cannot use XMLHttpRequest or ActiveX loader: not supported');
                    if (method == "POST" && !canSetHeaders) return this._error('Cannot use XMLHttpRequest loader or ActiveX loader, POST method: headers setting is not supported');
                    if (queryElem.length) return this._error('Cannot use XMLHttpRequest loader: direct form elements using and uploading are not implemented');
                    this._xmlReq = xmlReq;
                    var a = this._openArg;
                    this._xmlReq.open(method, url, a.asyncFlag, a.username, a.password);
                    if (canSetHeaders) {
                        // Pass pending headers.
                        for (var i=0; i<this._reqHeaders.length; i++)
                            this._xmlReq.setRequestHeader(this._reqHeaders[i][0], this._reqHeaders[i][1]);
                        this._xmlReq.setRequestHeader('Content-Type', 'application/octet-stream');
                    }
                    // Send the request.
                    return this._xmlReq.send(requestBody);

                case 'script':
                    // Create <script> element and run it.
                    if (method != 'GET') return this._error('Cannot use SCRIPT loader: it supports only GET method');
                    if (queryElem.length) return this._error('Cannot use SCRIPT loader: direct form elements using and uploading are not implemented');
                    this._obtainScript(id, url);
                    return true;

                case 'form':
                    // Create & submit FORM.
                    if (!this._obtainForm(id, url, method, queryText, queryElem)) return null;
                    return true;

                default:
                    return this._error('Unknown loader: ' + loader);
            }
        },

        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];
        },

        // Constructor.
        _construct: function() {},

        // Do all work when data is ready.
        _dataReady: function(text, js) { with (this) {
            if (text !== null || js !== null) {
                status = 4;
                responseText = responseXML = text;
                responseJS = js;
            } else {
                status = 500;
                responseText = responseXML = responseJS = null;
            }
            _changeReadyState(2);
            _changeReadyState(3);
            _changeReadyState(4);
            _cleanupScript();
        }},

        // Called on error.
        _error: function(msg) {
            throw (window.Error? new Error(msg) : msg);
        },

        // Create new XMLHttpRequest object.
        _obtainXmlReq: function(id, url) {
            // If url.domain specified and differ from current, cannot use XMLHttpRequest!
            // XMLHttpRequest (and MS ActiveX'es) cannot work with different domains.
            var p = url.match(new RegExp('^[a-z]+://(.*)', 'i'));
            if (p) {
                var curHost = document.location.host.toLowerCase();
                if (p[1].substring(0, curHost.length).toLowerCase() == curHost) {
                    url = p[1].substring(curHost.length, p[1].length);
                } else {
                    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) {
                        // Avoid memory leak by removing closure.
                        req.onreadystatechange = th.dummy;
                        th.status = null;
                        try {
                            // In case of abort() call, req.status is unavailable and generates exception.
                            // But req.readyState equals to 4 in this case. Stupid behaviour. :-(
                            th.status = req.status;
                            th.responseText = req.responseText;
                        } catch (e) {}
                        if (!th.status) return;
                        var funcRequestBody = null;
                        try {
                            // Prepare generator function & catch syntax errors on this stage.
                            eval('funcRequestBody = function() {\n' + th.responseText + '\n}');
                        } catch (e) {
                                JsHttpRequest.dataReady(id, "JavaScript code generated by backend is invalid!\n"+th.responseText, null);
                                return th._error(th.responseText);
                        }
                        // Call associated dataReady() outside try-catch block
                        // to pass excaptions in onreadystatechange in usual manner.
                        funcRequestBody();
                    }
                };
                this._id = id;
            }
            return req;
        },

        // Create new script element and start loading.
        _obtainScript: function(id, href) { with (document) {
            var span = createElement('SPAN');
            span.style.display = 'none';
            body.insertBefore(span, body.lastChild);
            span.innerHTML = 'Text for IE.<script></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;
        }},

        // Create & submit form.
        _obtainForm: function(id, url, method, queryText, queryElem) {
            // In case of GET method - split real query string.
            if (method == 'GET') {
                queryText = url.split('?', 2)[1].split('&');
                url = url.split('?', 2)[0];
            }

            // Create invisible IFRAME with temporary form (form is used on empty queryElem).
            var div = document.createElement('DIV');
            div.id = 'jshr_d_' + id;
            div.style.position = 'absolute';
            div.style.visibility = 'hidden';
            div.innerHTML =
                '<form enctype="multipart/form-data"></form>' +
                '<iframe src="javascript:\'\'" name="jshr_i_' + id + '" style="width:0px; height:0px; overflow:hidden; border:none"></iframe>';
            var form = div.getElementsByTagName('FORM')[0];
            var iframe = div.getElementsByTagName('IFRAME')[0];

            // Check if all form elements belong to same form.
            if (queryElem.length) {
                // If we have at least one form element, we use its form as POST container.
                form = queryElem[0][1].form;
                var foundFile = false;
                for (var i = 0; i < queryElem.length; i++) {
                    var e = queryElem[i][1];
                    if (!e.form) {
                        return this._error('Element "' + e.name + '" do not belongs to any form!');
                    }
                    if (e.form != form) {
                        return this._error('Element "' + e.name + '" belongs to different form. All elements must belong to the same form!');
                    }
                    foundFile = foundFile || (e.tagName.toLowerCase() == 'input' && (e.type||'').toLowerCase() == 'file');
                }
                var et = "multipart/form-data";
                if (form.enctype != et && foundFile) {
                    return this._error('Attribute "enctype" of elements\' form must be "' + et + '" (for IE), "' + form.enctype + '" given.');
                }
            }

            // Temporary disable ALL form elements in 'form' (including custom!).
            for (var i = 0; i < form.elements.length; i++) {
                var e = form.elements[i];
                if (e.name != null) {
                    e.jshrSaveName = e.name;
                    e.name = '';
                }
            }

            // Insert hidden fields to the form.
            var tmpE = [];
            for (var i=0; i<queryText.length; i++) {
                var pair = queryText[i].split('=', 2);
                var e = document.createElement('INPUT');
                e.type = 'hidden';
                e.name = unescape(pair[0]);
                e.value = pair[1] != null? unescape(pair[1]) : '';
                form.appendChild(e);
                tmpE[tmpE.length] = e;
            }

            // Enable custom form elements back & change their names.
            for (var i = 0; i < queryElem.length; i++) queryElem[i][1].name = queryElem[i][0];

            // Insert generated form inside the document.
            // Be careful: don't forget to close FORM container in document body!
            document.body.insertBefore(div, document.body.lastChild);
            this._span = div;

            // Temporary modify form attributes, submit form, restore attributes back.
            var sv = {};
            sv.enctype  = form.enctype;  form.enctype = "multipart/form-data";
            sv.action   = form.action;   form.action = url;
            sv.method   = form.method;   form.method = method;
            sv.target   = form.target;   form.target = iframe.name;
            sv.onsubmit = form.onsubmit; form.onsubmit = null;
            form.submit();
            for (var i in sv) form[i] = sv[i];

            // Remove generated temporary hidden elements from form.
            for (var i = 0; i < tmpE.length; i++) tmpE[i].parentNode.removeChild(tmpE[i]);

            // Enable all disabled elements back.
            for (var i = 0; i < form.elements.length; i++) {
                var e = form.elements[i];
                if (e.jshrSaveName != null) {
                    e.name = e.jshrSaveName;
                    e.jshrSaveName = null;
                }
            }
        },

        // 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);
            }
            if (this._id) {
                // Mark this loading as aborted.
                PENDING[this._id] = false;
            }
            return false;
        },

        // Convert hash to QUERY_STRING.
        _hash2query: function(content, prefix, queryText, queryElem) {
            if (prefix == null) prefix = "";
            if (typeof content == 'string') {
                queryText[queryText.length] = content;
            } else {
                var tn = (typeof content.tagName == 'undefined') ? null : content.tagName;
                if (tn == 'FORM' && this.loader == 'form') {
                    for (var i=0; i<content.elements.length; i++) {
                        var e = content.elements[i];
                        if (e.name) queryElem[queryElem.length] = [e.name, e];
                    }
                } else {
                    if (tn == 'FORM') {
                        var hash = {};
                        for (var i = 0; i < content.length; i++) {
                            if ((content[i].type == 'checkbox' || content[i].type == 'radio') && content[i].checked == false) {
                                continue;
                            }
                            var v = content[i].value;
                            var k = content[i].name;
                            hash[k] = v;
                        }
                        content = hash;
                    }
                    for (var k in content) {
                        var v = content[k];
                        if (typeof v == 'object') {
                            this._hash2query(v, curPrefix, queryText, queryElem);
                        } else {
                        if (v == '' || k == '' || v == null || ((v.constructor||{}).prototype||{})[k]) continue;
                            var curPrefix = prefix? prefix+'['+this.escape(k)+']' : this.escape(k);
                            queryText[queryText.length] = curPrefix + "=" + this.escape(v);
                        }
                    }
                }
            }
            return true;
        },

        // Change current readyState and call trigger method.
        _changeReadyState: function(s, reset) { with (this) {
            if (reset) {
                status = statusText = responseJS = null;
                responseText = '';
            }
            readyState = s;
            if (onreadystatechange) onreadystatechange();
        }},

        // Stupid JS escape() does not quote '+'.
        escape: function(s) {
            return escape(s).replace(new RegExp('\\+','g'), '%2B');
        }
    }
})();

function getScrollXY() {
  var scrOfX = 0, scrOfY = 0;
  if( typeof( window.pageYOffset ) == 'number' ) {
    //Netscape compliant
    scrOfY = window.pageYOffset;
    scrOfX = window.pageXOffset;
  } else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
    //DOM compliant
    scrOfY = document.body.scrollTop;
    scrOfX = document.body.scrollLeft;
  } else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
    //IE6 standards compliant mode
    scrOfY = document.documentElement.scrollTop;
    scrOfX = document.documentElement.scrollLeft;
  }
  return [ scrOfX, scrOfY ];
}

function getScreenSize() {
  var myWidth = 0, myHeight = 0;
  if( typeof( window.innerWidth ) == 'number' ) {
    //Non-IE
    myWidth = window.innerWidth;
    myHeight = window.innerHeight;
  } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
    //IE 6+ in 'standards compliant mode'
    myWidth = document.documentElement.clientWidth;
    myHeight = document.documentElement.clientHeight;
  } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
    //IE 4 compatible
    myWidth = document.body.clientWidth;
    myHeight = document.body.clientHeight;
  }
  return [ myWidth, myHeight ];
}
