add_stylesheet('httpclient');

// Simple XMLHTTPRequest wrapper to perform GET and POST requests. Usage:
//     receipt = HTTPClient.GET('/url', function(response) { ... }, {query_var_1: 1, query_var_2: 2 }, {'Custom-Header': 'foo'});
//     receipt = HTTPClient.POST('/url', {post_var_1: 1, post_var_2: 2 }, function(response) { ... }, {'Custom-Header': 'foo'});
//
// The callback will receive a response object, see _prepareResponse. You can also addEventListeners for:
// - start: {name: 'start'} (called when the request is sent)
// - end: {name: 'end', target: the response object} (called when the request is sent)
// - abort: {name: 'abort'};
HTTPClient = {
    'logging': false,
    'counter': 0,
    'POST': function(url, data, callback, headers) {
        receipt = this.counter++;        
        this._log('Queueing POST, receipt: ' + receipt);  
        this._queue.push({method: 'POST', args: {url: url, data: data, callback: callback, headers: headers, receipt: receipt}});
        this._process();
        return receipt;
    },    
    'GET': function(url, callback, query_vars, headers) {
        receipt = this.counter++;          
        this._log('Queueing GET, receipt: ' + receipt);  
        this._queue.push({method: 'GET', args: {url: url, callback: callback, query_vars: query_vars, headers: headers, receipt: receipt}});
        this._process();
        return receipt;
    },    
    'abort': function() {
        if (this._transport) {
            this._log('Aborting');
            this._transport.abort();            
            this._transport = false;
            this._busy = false;
            this._queue = [];
            this._dispatch({name: 'abort'});
        }
    },    
    'showLoading': function() {
        if (!document.getElementById('httpclient_pi')) {
            div_pi = tag('div', {id: 'httpclient_pi'}, 'Loading...');
            document.getElementsByTagName('body')[0].appendChild(div_pi);
        }        
    },
    'hideLoading': function() {
        if (div_pi = document.getElementById('httpclient_pi')) {
            div_pi.parentNode.removeChild(div_pi);
        }        
    },    
    '_log': function(msg) {
        if (!this.logging) {
            return;
        }
        try {
            console.log(msg);
        }catch(e) {}
    },
    '_transport': false,
    'getTransport': function() {
        if (this._transport == false) {
            try {
                this._transport = new XMLHttpRequest();
            } catch(e) {
                try {
                    this._transport = new ActiveXObject('Msxml2.XMLHTTP');
                } catch (e) {
                    try {
                        this._transport = new ActiveXObject('Microsoft.XMLHTTP');
                    } catch (e) {
                        throw "HTTPClient not supported by this browser!";
                    }
                }
            }
        }
        return this._transport;
    },
    '_makeQueryString': function(query_vars) {
        var str = new Array();
        for(var k in query_vars) {   
            if (typeof(query_vars[k]) == 'object') {
                for(var i = 0; i < query_vars[k].length; i++) {
                    var x = k + escape('[' + i + ']') + '=' + escape(query_vars[k][i]).replace(/\+/g, '%2B');
                    str.push(x);
                }
            } else {
                str.push(k + '=' + escape(query_vars[k]).replace(/\+/g, '%2B'));
            }
        }
        qs = str.join('&');
        this._log('Query string: ' + qs);
        return qs;
    },
    '_parseHeaders': function(_headers) {
        if (!_headers) {
            return;
        }
        var lines = _headers.split("\n");
        var parsed = new Array;
        for (var i = 0; i < lines.length; i++) {
            _header = this._parseHeaderLine(lines[i]);
            if (_header) {
                if (_header.name == 'Date' || _header.name == 'Last-Modified') {
                    parsed[_header.name.toLowerCase()] = new Date(_header.value);
                } else {
                    parsed[_header.name.toLowerCase()] = _header.value;
                }
            }
        }
        return parsed;
    },
    '_parseHeaderLine': function(line) {
        if (line != '' && line.indexOf && (i = line.indexOf(':'))) {
            r = {name: line.substring(0, i), value: line.substring(i + 2)}
            return r;
        }
        return false;
    },
    '_prepareResponse': function(transport, req_data) {
        try {
            response = {}
            response.request = req_data; // location, sent headers, sent data, method used
            response.status = transport.status; // HTTP status
            response.statusText = transport.statusText;
            response.headers = this._parseHeaders(transport.getAllResponseHeaders());
            response.body = transport.responseText;
            response.xml = transport.responseXML;
            response.toJSON = function() {
                try {
                    return eval('(' + this.body + ')');
                } catch(e) {
                    try {
                        console.log('Error while parsing response: ' + e);
                        console.log('Raw response: ' + this.body);
                    } catch(e) {}
                    return undefined;
                }
            }
            response.getHeader = function(header_name, default_value) {
                if (this.headers[header_name.toLowerCase()] == undefined) {
                    return default_value;
                } 
                return this.headers[header_name.toLowerCase()];
            }
            return response
        } catch(e) {
            this._log('Exception ' + e + ' while preparing response');
            return false
        }
    },
    '_doPOST': function(url, data, callback, headers, receipt) {
        this._log('Performing POST');        
        try {
            transport = this.getTransport();  
        } catch (e) {
            return false;
        }
        
        body = this._makeQueryString(data);
        
        transport.open('POST', url, true);
        transport.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        
        for(var k in headers) {
            transport.setRequestHeader('X-' + k, headers[k]);        
        }        
        transport.setRequestHeader('X-AJAX', 1);
        
        var me = this;
        transport.onreadystatechange = function() {
            me._readyStateChanged(me, transport, callback, {receipt: receipt, location: url, method: 'POST', data: data, headers: headers});
        }
        transport.send(body);
        this._dispatch({name: 'start'});
        return true;
    },
    '_doGET': function(url, callback, query_vars, headers, receipt){
        this._log('Performing GET');        
        try {
            transport = this.getTransport();  
        } catch (e) {
            return false;
        }
        
        use_url = url;
        if (query_vars) {
            use_url += '?' + this._makeQueryString(query_vars);
        }
        
        transport.open('GET', use_url, true);

        for(var k in headers) {
            this._log('Adding header "' + k + '", value: "' + headers[k] + '"');
            transport.setRequestHeader('X-' + k, headers[k]);        
        }
        transport.setRequestHeader('X-AJAX', 1);        
        me = this;
        req_data =  {receipt: receipt, location: url, method: 'GET', data: query_vars, headers: headers};
        transport.onreadystatechange = function() {
            me._readyStateChanged(me, transport, callback, req_data);
        }
        
        transport.send(null);
        this._dispatch({name: 'start'});
        return true;
    },
    '_readyStateChanged': function(me, transport, callback, req_data) {
        me._log('Ready state: "' + me._getReadyStateString(transport.readyState) + '"')
	
        if (transport.readyState == 4) {
            me._log('Got response for receipt ' + req_data.receipt + '; HTTP Status: "' + transport.status + ' ' + transport.statusText + '"');
            response = me._prepareResponse(transport, req_data);
            if (response == false) {
                return;
            }
            
            if (callback != undefined) {
                callback(response);
            }
            me._dispatch({name: 'end', target: response});
            me._busy = false;
            me._process();
        }        
    },
    '_queue': new Array(),
    '_busy': false,
    '_process': function() {
        if (this._queue.length == 0) {
            this._log('Queue empty');
            return;
        }
        if (this._busy == false){
            this._busy = true;
            var next = this._queue.shift();
            this._log('Processing next in queue: method = ' + next.method + ', url = ' + next.args.url);            
            if (next.method == 'GET') {
                this._doGET(next.args.url, next.args.callback, next.args.query_vars, next.args.headers, next.args.receipt);
            } else if (next.method == 'POST') {
                this._doPOST(next.args.url, next.args.data, next.args.callback, next.args.headers, next.args.receipt);
            }
        }
    },
    '_getReadyStateString': function(n) {
        states = new Array('uninitialized', 'open', 'sent', 'receiving', 'loaded');
        return states[n];
    },
    'stop': function() {
        this.abort();  
    }
}

make_audible(HTTPClient, ['start', 'end', 'abort']);

// Default listeners to manage the "loading" floaring box
HTTPClient.addEventListener('start', function() { HTTPClient.showLoading(); });
HTTPClient.addEventListener('end', function(e) { HTTPClient.hideLoading(); });
HTTPClient.addEventListener('abort', function(e) { HTTPClient.hideLoading(); });

if (location.search.match(/httpclientlog=1/)) {
    HTTPClient.logging = true;
}
