/* =========================================================================

     Modello.ajax, Cross-browser, cross-domain Ajax utilities base on Modello

     Author: Ken Xu <ken@ajaxwing.com>

     For more information, see: http://modello.sourceforge.com/

   -----------------------------------------------------------------------

     Copyright 2006 by Ken Xu

			      All Rights Reserved

     Permission to use, copy, modify, and distribute this software and its
     documentation for any purpose and without fee is hereby granted, provided
     that the above copyright notice appear in all copies and that both that
     copyright notice and this permission notice appear in supporting
     documentation, and that the name of Ken Xu not be used in advertising or
     publicity pertaining to distribution of the software without specific,
     written prior permission.

     KEN XU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
     ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
     KEN XU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
     DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
     AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

   ========================================================================= */


Modello.ajax = {
    version: '0.0.6',
    date: '2006-04-18'
}


//Define('URLGET_PROXY', '/jsproxy.php');


Class.create().register('modello.ajax.Connection').get = function () {
    var _conn = null;
    try {
        _conn = new ActiveXObject('Msxml2.XMLHTTP');
    } catch (e) {
        try {
            _conn = new ActiveXObject('Microsoft.XMLHTTP');
        } catch (E) {
            _conn = null;
        }
    }
    if (!_conn && typeof XMLHttpRequest != 'undefined') {
        _conn = new XMLHttpRequest();
    }
    return _conn;
}


Class.create().register('modello.ajax.Request').construct = function () {

    this.url = '';
    this.method = '';
    this.data = '';
    this.busy = false;

    var _conn = null;
    var _emptyMethod = function () {};
    var _handler = _emptyMethod;
    var _requestHeaders = {};
    var _state = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
    var _response = null;

    this.initialize = function (url, method, data) {
        _conn = Class.get('modello.ajax.Connection').get();
        if (!_conn) {
            throw new Error(this + ' initialize error: Connection establish faild');
        }
        this.url = url || '';
        this.method = method || 'GET';
        this.data = data || '';
        for (key in _requestHeaders) {
            delete _requestHeaders[key];
        }
    }

    this.reset = function () {
        if (!this.busy) {
            for (key in _requestHeaders) {
                _conn.setRequestHeader(key, '');
            }
            _requestHeaders = {};
            for (key in _requestHeaders) {
                delete _requestHeaders[key];
            }
        }
    }

    this.abort = function () {if (this.busy) _conn.abort();}

    this.setURL = function (url) {this.url = url;}

    this.setMethod = function (method) {this.method = method;}

    this.setData = function (data) {this.data = data;}

    this.setHandler = function (handler) {_handler = handler;}

    this.setHeader = function (key, value) {_requestHeaders[key] = value;}

    this.addHeader = function (header) { var ret = header.split(/:\s*/); this.setHeader(ret[0], ret[1]); }

    this.open = function (async) {
        var async = async || false;
        if (this.busy) {
            throw new Error(this + ' open error: Network busy');
        }
        this.busy = true;
        _response = null;
        try {
            _conn.open(this.method.toUpperCase(), this.url, async);
            if (async) {
                _conn.onreadystatechange = Function.bind(this, _onStateChange);
            } else {
                _conn.onreadystatechange = _emptyMethod;
            }
            for (key in _requestHeaders) {
                _conn.setRequestHeader(key, _requestHeaders[key]);
            }
            if (this.method == 'POST') {
                _conn.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
                _conn.send(this.data);
            } else {
                _conn.send(null);
            }
        } catch (e) {
            this.onException(e);
        }
        if (!async) {
            this.busy = false;
            _response = Class.get('modello.ajax.Response').create(this);
            return _response;
        }
    }

    this.getState = function () {return _state[_conn.readyState];}

    this.getConnection = function () {return _conn;}

    this.getResponse = function () {return _response;}

    this.onException = function (e) {throw(e);}

    var _onStateChange = function () {
        var state = _state[_conn.readyState];
        try {
            if (state == 'Complete') {
                _response = Class.get('modello.ajax.Response').create(this);
                _handler(_response);
                _conn.onreadystatechange = _emptyMethod;
                this.busy = false;
            }
            if (typeof this['on' + state] == 'function') {
                this['on' + state]();
            }
        } catch (e) {
            this.onException(e);
        }
    }
}


Class.create().register('modello.ajax.Response').construct = function () {

    var _conn = null;

    this.initialize = function (request) {
        if (request.getState() != 'Complete') {
            throw new Error(this + ' initialize error: request is not complete');
        }
        _conn = request.getConnection();
    }

    this.getStatus = function () {return _conn.status;}

    this.getStatusText = function () {return _conn.statusText ? _conn.statusText : _conn.status;}

    this.getHeader = function (key) {return _conn.getResponseHeader(key);}

    this.getAllHeaders = function () {return _conn.getAllResponseHeaders();}

    this.getRawHeader = function () {return ['HTTP/1.x', this.getStatus(), this.getStatusText()].join(' ') + '\r\n' + this.getAllHeaders();}

    this.getText = function () { return _conn.responseText; }

    this.getXML = function () {return _conn.responseXML;}
}


Class.create().register('modello.ajax.Urllib').urlparse = function (url) {
    var ret = {};
    var arr = url.split('://', 2);
    if (arr.length > 1) {
        ret.scheme = arr[0];
        var host = arr[1];
        arr = host.split('/');
        if (arr.length < 2) {
            ret.host = arr[0];
            return ret;
        } else {
            var path = arr.slice(1).join('/');
            var host = arr[0];
            arr = host.split('@', 2);
            if (arr.length > 1) {
                var host = arr[1];
                var user = arr[0];
                arr = user.split(':', 2);
                ret.user = arr[0];
                if (arr.length > 1) {
                    ret.pass = arr[1];
                }
            } else {
                var host = arr[0];
            }
            arr = host.split(':', 2);
            ret.host = arr[0];
            if (arr.length > 1) {
                ret.port = arr[1];
            }
        }
    } else {
        var path = arr[0];
    }
    arr = path.split('?');
    ret.path = arr[0];
    if (typeof ret.host != 'undefined') {
        ret.path = '/' + ret.path;
    }
    if (arr.length > 1) {
        var query = arr.slice(1).join('?');
        arr = query.split('#');
        if (arr.length > 1) {
            ret.query = arr.slice(0, -1).join('#');
            ret.flagment = arr[arr.length-1];
        } else {
            ret.query = arr[0];
        }
    }
    return ret;
}


Class.get('modello.ajax.Urllib').urljoin = function (base, url) {
    var ret1 = Class.get('modello.ajax.Urllib').urlparse(base);
    var ret2 = Class.get('modello.ajax.Urllib').urlparse(url);
    if (typeof ret2.scheme != 'undefined') {
        return url;
    }
    if (!ret2.path) {
        return base;
    }
    var host = '';
    if (ret1.scheme) {
        host = ret1.scheme + '://';
    }
    if (ret1.user) {
        host += ret1.user;
        if (ret1.pass) {
            host += ':' + ret1.pass;
        }
        host += '@';
    }
    if (ret1.host) {
        host += ret1.host;
    }
    if (ret1.port) {
        host += ':' + ret1.port;
    }
    var path = ret2.path;
    if (path.charAt(0) != '/') {
        var arr = ret1.path.split('/');
        arr[arr.length-1] = path;
        path = arr.join('/');
    }
    var items = path.split('/');
    while (true) {
        var success = false;
        for (var i = 1; i < items.length; i++) {
            if (items[i] == '..' && items[i-1] && items[i-1] != '..') {
                Array.removeAt(items, i);
                Array.removeAt(items, i-1);
                success = true;
                break;
            }
        }
        if (!success) {
            break;
        }
    }
    path = items.join('/');
    if (ret2.query) {
        path += '?' + ret2.query;
    }
    if (ret2.flagment) {
        path += '#' + ret2.flagment;
    }
    return host + path;
}


Class.get('modello.ajax.Urllib').urlget = (function () {

    var _request_pool = [];

    var _reuse_request = function(chunnel) {
        if (typeof chunnel == 'string') {
            if (typeof _request_pool[chunnel] == 'undefined') {
                var request = Class.get('modello.ajax.Request').create();
                _request_pool[chunnel] = request;
            } else {
                var request = _request_pool[chunnel];
            }
            return request.busy ? false : request;
        }
        for (var i = 0; i < _request_pool.length; i++) {
            if (!_request_pool[i].busy) {
                return _request_pool[i];
            }
        }
        _request_pool[i] = Class.get('modello.ajax.Request').create();
        return _request_pool[i];
    }

    return function (url, data, callback, chunnel, headers) {
        var data = data || '';
        if (location.protocol.slice(0, 4).toLowerCase() != 'http') {
            throw new Error(this + ' urlget error: Please use this method in http environment');
        }
        var url = Class.get('modello.ajax.Urllib').urljoin(location.href, url);
        var ret1 = Class.get('modello.ajax.Urllib').urlparse(url);
        var ret2 = Class.get('modello.ajax.Urllib').urlparse(location.href);
        if (ret1.scheme.toLowerCase() != 'http') {
            throw new Error(this + ' urlget error: Only http protocol is supported');
        }
        request = _reuse_request(chunnel);
        if (!request) {
            return false;
        }

        request.reset();

        if (ret1.host == ret2.host) {
            url += url.indexOf('?') < 0 ? '?' : '&';
            request.setURL(url + '_time_stamp=' + (new Date()).getTime());
            if (data) {
                request.setData(data);
                request.setMethod('POST');
            } else  {
                request.setMethod('GET');
            }
            if (headers) {
                for (var i = 0; i < headers.length; i++) {
                    request.addHeader(headers[i]);
                }
            }
        } else {
            if (typeof URLGET_PROXY == 'undefined') {
                throw new Error(this + ' urlget error: Cross domain request must define URLGET_PROXY first');
            }
            proxy = URLGET_PROXY + (URLGET_PROXY.indexOf('?') < 0 ? '?' : '&');
            request.setURL(proxy + '_time_stamp=' + (new Date()).getTime());
            request.setMethod('POST');
            var header = '';
            if (headers) {
                header = headers.join('\r\n');
                header += '\r\n';
            }
            request.setData('url=' + encodeURIComponent(url) + '&data=' + encodeURIComponent(data) + '&header=' + encodeURIComponent(header));
        }

        if (callback && typeof callback == 'function') {
            var handler = function (response) {
                callback(response, chunnel);
            }
            request.setHandler(handler);
            request.open(true);
            return true;
        } else {
            return request.open(false);
        }
    }
})();


Function.bind = function (object, method) {
    return function () {method.apply(object, arguments);};
}
