/**
 * Copyright 2006-2008 xujiwei
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

////////////////////////////////////////////////////////////////////////////////
// AJAXRequest
// Version: 0.8.10 Patch 3
// Author: xujiwei
// E-mail: vipxjw at 163 dot com
// Website: http://www.xujiwei.com/
//
// AJAXRequest Project Homepage:
//     http://ajax.xujiwei.com/
////////////////////////////////////////////////////////////////////////////////

/**
 * class AJAXRequest
 * @author	xujiwei
 * @copyright 2006-2008 xujiwei.
 * @constructor
 * @version     $Id: ajaxrequest.js 35 2008-12-27 05:32:21Z xujiwei $
 * @class		AJAXRequest
 * @param		{object}	[obj]					Init parameter object
 * @param		{string}	[obj.url=""]			Url to request
 * @param		{string}	[obj.content=""]		Content to send
 * @param		{string}	[obj.method="GET"]		Request method, GET or POST
 * @param		{string}	[obj.charset]			Charset of content
 * @param		{boolean}	[obj.async=true]		Async request, true or false
 * @param		{number}	[obj.timeout=3600000]	Request timeout in millisecond
 * @param		{function}	[obj.encode=encodeURIComponent]		Encoding method, default encodeURIComponent
 * @param		{function}	[obj.ontimeout]			Timeout callback
 * @param		{function}	[obj.onrequeststart]	Request start callback
 * @param		{function}	[obj.onrequestend]		Request end callback
 * @param		{function}	[obj.oncomplete]		Request complete successful callback
 * @param		{function}	[obj.onexception]		Handle request exception callback
 * @property	{string}	url						Url to request
 * @property	{string}	content					Content to send
 * @property	{string}	method					Request method, GET or POST
 * @property	{string}	charset					Charset of content
 * @property	{boolean}	async					Async request, true or false
 * @property	{number}	timeout					Request timeout in millisecond
 * @property	{function}	encode					Encoding method
 * @property	{function}	ontimeout				Timeout callback
 * @property	{function}	onrequeststart			Request start callback
 * @property	{function}	onrequestend			Request end callback
 * @property	{function}	oncomplete				Request complete successful callback
 * @property	{function}	onexception				Handle request exception callback
 * @example
 * // using default values to init AJAXRequest object.
 * var ajax1 = new AJAXRequest();
 * // using a parameter to init AJAXRequest object.
 * var ajax2 = new AJAXRequest({
 * 	url: "getdata.asp",	// get data from getdata.asp
 * 	method: "GET",		// GET method
 * 	oncomplete: function(obj) {
 * 		alert(obj.responesText);	// show data from getdata.asp
 * });
 */
function AJAXRequest(init) {
	var objPool = [], AJAX = this, _pool = AJAXRequest.__pool__ || (AJAXRequest.__pool__ = []);
	(function(obj) {
		// init xmlhttp pool, and some consts
		var emptyFun = function() { };

		// process inti parameter
		obj = obj ? obj : {};
		var prop = ['url', 'content', 'method', 'async', 'encode',      'timeout', 'ontimeout', 'onrequeststart', 'onrequestend', 'oncomplete', 'onexception'];
		var defs = ['',    '',        'GET',     true,    _GEC('UTF-8'), 3600000,   emptyFun,    emptyFun,         emptyFun,       emptyFun,     emptyFun];
		var pc = prop.length;
		while(pc--) {
			AJAX[prop[pc]] = getp(obj[prop[pc]], defs[pc]);
		}
		// get the first xmlhttp, if failed then return false
		if(!getXHR()) { return false; }
	})(init);

	// get param or its default
	function getp(p, d) { return p != undefined ? p : d; }

	// get XMLHttpRequest from pool
	function getXHR() {
		var xhr, _vers = [window.XMLHttpRequest, "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];
		for(var i = 0; i < _pool.length; i+=1) {
			if(_pool[i].readyState == 0 || _pool[i].readyState == 4) {
				return _pool[i];
			}
		}
		for(var i = 0; i < _vers.length; i+=1) {
			try {
				xhr = (_vers[i] && typeof(_vers[i]) == "function" ? new _vers[i] : new ActiveXObject(_vers[i]));
				break;
			} catch(e) {
				xhr = false;
				continue;
			}
		}
		if(!xhr) {
			throw 'Cannot init XMLHttpRequest object!';
			return false;
		}
		else {
			_pool[_pool.length] = xhr;
			return xhr;
		}
	}

	// get element by id
	function $(id) { return document.getElementById(id); }

	// convert anything to number
	function _N(d) { var n = d * 1; return(isNaN(n) ? 0 : n); }

	// convert anything to HtmlObject
	function _VO(v) { return (typeof(v) == "string" ? (v = $(v)) ? v : false : v); }

	// get an unique number
	function _GID(){return((new Date)*1);}

	// save update object
	function _SOP(id, ct) { objPool[id + ""] = ct; }

	// load update object
	function _LOP(id) { return(objPool[id + ""]); }

	// string replace function generator
	function _SRP(pre, reps, ps){
		return (function rep(str) {
					str = pre(str);
					for(var i = 0, n = reps.length; i < n; i+=1) {
						str = str.replace(reps[i], ps[i]);
					}
					return(str);
				});
	}

	// get encode method
	function _GEC(cs){
		if(cs.toUpperCase() == "UTF-8") {
			return(encodeURIComponent);
		}
		else {
			// replace sepcial chars: +
			return(_SRP(escape, [/\+/g], ["%2B"]));
		}
	}

	// set content to HtmlObject
	function _ST(obj, txt) {
		if(!obj.nodeName) {
			return;
		}
		var nn = "|" + obj.nodeName.toUpperCase() + "|";
		if("|INPUT|TEXTAREA|OPTION|".indexOf(nn) > -1) {
			obj.value = txt;
		}
		else {
			try {
				obj.innerHTML = txt;
			} catch(e) { };
		}
	}

	// generate a callback function
	function _CB(cb) {
		if(typeof(cb) == "function") {
			return cb;
		}
		else {
			cb = _VO(cb);
			if(cb) {
				return (function(obj) {
							_ST(cb, obj.responseText);
						});
			}
			else {
				return AJAX.oncomplete;
			}
		}
	}

	// generate parameters
	// p  output parameters array
	// v  input parameter values
	// d  default values
	// f  extra process function
	function $GP(v, d, f) {
		var i = 0, p = [];
		while(i < v.length) {
			p[i] = v[i] ? (f[i] ? f[i](v[i]) : v[i]) : d[i];
			i+=1;
		}
		while(i < d.length) {
			p[i]=d[i];
			i+=1;
		}
		return p;
	}

	// send request
	function send() {
		var ct, ctf = false, xhr = getXHR();
		// process parameters
		var p = $GP(arguments,
					[AJAX.url, AJAX.content, AJAX.oncomplete, AJAX.method, AJAX.async, null],
					[null, null, _CB, null, null, null]);
		var url = p[0], content = p[1], callback = p[2], method = p[3], async = p[4], extra = p[5];

		// is use POST method?
		var isPost = method.toUpperCase() == "POST" ? true : false;

		// check if url exists
		if(!url) {
			throw 'url is null';
			return false;
		}

		// event callback argument
		var ev = {
			url: url,
			content: content,
			method: method,
			params: extra
		};

		// append a timestamp to the url and open XMLHttpRequest
		if(!isPost) {
			url += (url.indexOf("?") > -1 ? "&" : "?") + "timestamp=" + _GID();
		}
		xhr.open(method, url, async);

		// request start event
		AJAX.onrequeststart(ev);

		// POST method needs a sepcial Content-Type
		if(isPost) {
			xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
		}
		xhr.setRequestHeader("X-Request-With", "XMLHttpRequest");

		// set a timeout to cancel request when timeout
		ct = setTimeout(function() {
							ctf = true;
							xhr.abort();
						},
						AJAX.timeout);
		var rc = function() {
			if(ctf) {
				AJAX.ontimeout(ev);
				AJAX.onrequestend(ev);
			}
			else if(xhr.readyState == 4) {
				clearTimeout(ct);
				ev.status = xhr.status;
				try{
					if(xhr.status == 200) {
						callback(xhr, extra);
					}
					else {
						AJAX.onexception(ev);
					}
				}
				catch(e) {
					AJAX.onexception(ev);
				}
				AJAX.onrequestend(ev);
			}
		}
		xhr.onreadystatechange = rc;
		if(isPost) { xhr.send(content); } else { xhr.send(""); }
		if(async == false) {
			rc();
		}
		return true;
	}

	/**
	 * Set charset of content
	 *
	 * @param	{string}	charset	charset, UTF-8 or GB2312, and etc..
	 * @see AJAXRequest#charset
	 * @see AJAXRequest#encode
	 * @example
	 * var ajax = new AJAXRequest();
	 * ajax.setcharset("GB2312");
	 */
	this.setcharset = function(cs) {
        AJAX.encode = _GEC(cs);
	}

	/**
	 * Get data from sepcial url
	 *
	 * @param	{string}					[url]			Url to request
	 * @param	{function|object|string}	[oncomplete]	Successful callback, or Html object, or Html object's ID
	 * @param	{object}					[extra]			Extra data post to callback function
	 * @returns	{boolean}									Is the request successfully sent
	 * @example
	 * var ajax = new AJAXRequest();
	 * ajax.get("getdata.asp", function(obj) {
	 * 	alert(obj.responseText);	// show data from getdata.asp
	 * });
	 * ajax.get("getdata.asp", "txtData");	// show data from getdata.asp in html object "txtData"
	 */
	this.get = function(url, callback, extra) {
		return send(url, '', callback, 'GET', AJAX.async, extra);;
	}

	/**
	 * Update the sepcial object with data from request url
	 *
	 * @param	{function|object|string}	callback	oncomplete	Successful callback, or Html object, or Html object's ID
	 * @param	{string}					url			Url to request
	 * @param	{number}					interval	Interval to update
	 * @param	{number}					times		Total update times
	 * @param	{object}					[extra]		Extra data post to callback function
	 * @returns	{string}								id of update request, use to stop the update
	 * @see		AJAXRequest#stopupdate
	 * @example
	 * var ajax = new AJAXRequest();
	 * ajax.update(function(obj) {
	 * 		alert(obj.responseText);
	 * 	},
	 * 	"getdata.asp",	// get data from getdata.asp
	 * 	1000,	// update per second
	 * 	3		// total update 3 times
	 * );
	 */
	this.update = function(callback, url, interval, times, extra) {
		interval = _N(interval);
		times = _N(times);
		if(interval < 1) {
			times = 1;
		}
        else if(times < 1) {
            times = Number.POSITIVE_INFINITY;
        }
		var sendfoo = function() {
			send(url, "", callback, "GET", AJAX.async, extra);
		};
		var updateid = _GID();
		var updatefoo = function(updateCount) {
			sendfoo();
			updateCount--;
			if(updateCount > 0) {
				_SOP(updateid, setTimeout(function(){
										updatefoo(updateCount);
									}, interval));
			}
		}
		updatefoo(times);
		return updateid;
	}

	/**
	 * Stop update an object
	 *
	 * @param	{string}	update_id	update id which return by update method
	 * @see		AJAXRequest#update
	 * @example
	 * var ajax = new AJAXRequest();
	 * var up = ajax.update("txtData", "getdata.asp");
	 * ajax.stopupdate(up);
	 */
	this.stopupdate = function(id) {
		clearTimeout(_LOP(id));
	}

	/**
	 * Post data to the sepcial url
	 *
	 * @param	{string}					[url]			Url to post data
	 * @param	{string}					[content]		Data to post
	 * @param	{function|object|string}	[oncomplete]	Successful callback, or Html object, or Html object's ID
	 * @param	{object}					[extra]			Extra data post to callback function
	 * @returns	{boolean}									Is the request successfully sent
	 * @see		AJAXRequest#postf
	 * @example
	 * var ajax = new AJAXRequest();
	 * ajax.post("postdata.asp", "the data to post", function(){});
	 */
	this.post = function(url, content, callback, extra) {
		return send(url, content, callback, "POST", AJAX.async, extra);
	}

	/**
	 * Post the sepcial form to an url
	 *
	 * @param	{string|object}				formObject		The form object or its ID
	 * @param	{function|object|String}	[oncomplete]	Successful callback, or Html object, or Html object's ID
	 * @param	{object}					[extra]			Extra data post to callback function
	 * @returns	{boolean}									Is the request successfully sent
	 * @see		AJAXRequest#post
	 * @example
	 * var ajax = new AJAXRequest();
	 * ajax.postf("dataForm", function(obj) {
	 * 	alert(obj.responseText);
	 * });
	 */
	this.postf = function(formObj, callback, extra) {
		var p=[],vaf,pcbf,purl,pc,pm,ac=arguments.length,av=arguments;
		// check form legal
		formObj = formObj ? _VO(formObj) : false;
		if(!formObj || formObj.nodeName != "FORM") {
			return false;
		}
		// process form validate
		validfoo = formObj.getAttribute("onvalidate");
		validfoo = validfoo ? (typeof(validfoo)=="string" ? new Function(validfoo) : validfoo) : null;
		if(validfoo && !validfoo()) {
			return false;
		}
		// get url and method from formObj's attributes
		var url = formObj.getAttribute("action"), method = formObj.getAttribute("method");
		// check if content is empty
		var content = AJAX.formToStr(formObj);
		if(content.length == 0) {
			return false;
		}
		// send the request
		if(method.toUpperCase()=="POST") {
			return send(url, content, callback, "POST", true, extra);
		}
		else {
			url += (url.indexOf("?") > -1 ? "&" : "?") + content;
			return send(url, "", callback, "GET", true, extra);
		}
	}

	/**
	 * Translate the form object to string
	 *
	 * @author	SurfChen <surfchen@gmail.com>
	 * @link	http://www.surfchen.org/
	 * @param	{object} formObject
	 * @returns {string} The string of the form
	 * @see		AJAXRequest#postf
	 * @ignore
	 */
	this.formToStr = function(formObj) {
		var qstr = "", and = "", elem, value;
		for(var i = 0; i< formObj.length; i+=1) {
			elem = formObj[i];
			if (elem.name!='') {
                value = undefined;
				switch(elem.type) {
					case "select-one":
						if(elem.selectedIndex > -1) {
							value = elem.options[elem.selectedIndex].value;
						}
						else {
							value = "";
						}
						break;
					case "checkbox":
					case "radio":
						if (elem.checked == true) {
							value = elem.value;
						}
						break;
					default:
						value = elem.value;
				}
                if(value != undefined) {
                    value = AJAX.encode(value);
                    qstr += and + elem.name + "=" + value;
                    and = "&";
                }
			}
		}
		return qstr;
	}
}