function Request()
{
    var _this = this;

    this.default_timeout = 30; // seconds
    this.js_http_request = null;
    this.caller = null;
    this.request_params = null;
    this.is_busy = false;
    
    this.request_callback = function(result, errors)
    {
        _this.processResult(result, errors);
    }
    this.timeout_callback = function()
    {
        _this.timeoutHandler();
    }

    this.send = function(url, params, caller, timeout)
    {
        if (this.is_busy) {
            return false;
        }

        var req = new JsHttpRequest;
        this.js_http_request = req;
        req.caching = false;
        req.onreadystatechange = function()
        {
            if (req.readyState == 4) {
                _this.processResult(req.responseJS, req.responseText);
            }
        }
        req.open(null, url, true);
        req.send(params);
                        
        timeout = timeout || this.default_timeout; // -1 cancels timeout
        if (timeout > 0) {
            setTimeout(this.timeout_callback, timeout * 1000);
        }
                    
        this.is_busy = true;
        this.request_params = params;
        if (caller && caller.requestCallback) {
            this.caller = caller;
        } else {
            this.caller = null;
        }

        return true;
    }
    
    this.abort = function()
    {
        if (!this.is_busy) {
            return;
        }
        
        this.js_http_request.abort();
        this.is_busy = false;
    }

    this.processResult = function(result, errors)
    {
        this.is_busy = false;        

        if (!result || (result && result.exception && result.exception.code == 'TIMEOUT')) {
            Request.timeouts_count++;
            Request.reportOffline();
            return;                            
        } else {
            Request.timeouts_count = 0;
        }
        
        if (this.caller) {
            if (result && result.exception) {
                this.caller.requestCallback(result, this.request_params, result.exception);
            } else if (result) {
                this.caller.requestCallback(result, this.request_params);
            }
        }
        if (result && result.exception && !result.exception.handled) {
            alert('Exception during processing query: ' + result.exception.message);
        }
        if (errors) {
            alert(errors);
        }
    }

    this.timeoutHandler = function()
    {
        if (!this.is_busy) {
            return true;
        }

        this.abort();
        this.processResult({'exception': {'code': 'TIMEOUT', 'message': 'Request is timed out'}}, null);
    }
}

Request.timeouts_count = 0;
Request.show_offline_warning = false;

Request.create = function() 
{
    if (Request.isOnline()) {
        return new Request();
    } else {
        Request.showOfflineWarning();
        return null;
    }
}

Request.isOnline = function()
{
    return Request.timeouts_count > 5 ? false : true;
}

Request.warningIsShown = function()
{
    return Request.show_offline_warning;
}

Request.reportOffline = function() 
{  
    if (!Request.isOnline() && !Request.warningIsShown()) {
        Request.showOfflineWarning();        
    }       
}

Request.showOfflineWarning = function()
{
    Request.show_offline_warning = true;
    alert('Соединение с сервером потеряно. Возможно проблема с подключением к сети. После того, как соединение будет востановлено, нажмите F5, чтобы перезагруззить страницу и продолжить работу.');
}
