/**
 * @author		Alex Tseng
 * @link		http://www.fanzhi.com/
 * @copyright	Alex Tseng
 * @version		1.0.0
 * @lastUpdate	2009.11.16
 */


/**
 * @class Ajax
 * @constructor
 * @param {Object} options
 */
var Ajax = function(options){
	
	this.options = {
		
		/**
		 * @param string
		 */
		url: options.url || '',
		
		/**
		 * @param string
		 */ 
		data: options.data || '',

		/**
		 * @param boolean
		 */ 
		async: options.async || true,
		
		/**
		 * @param string
		 */ 
		method: options.method || 'post',
		
		/**
		 * @param object
		 */ 
		headers: options.headers || {
			'X-Requested-With': 'XMLHttpRequest',
			'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
		},
				
		/**
		 * @param object
		 */ 
		isSuccess: options.isSuccess || null,
		
		urlEncoded: options.urlEncoded || true,
		
		encoding: options.encoding || 'utf-8',
		
		evalScripts: options.evalScripts || false,
		
		evalResponse: options.evalResponse || false,
		
		noCache: options.noCache || false,
		
		initialDelay: options.initialDelay || 5000,
		
		delay: options.delay || 5000,
		
		limit: options.limit || 60000,
		
		onRequest: options.onRequest || this.$empty,
		
		onComplete: options.onComplete || this.$empty,
		
		onCancel: options.onCancel || this.$empty,
		
		onSuccess: options.onSuccess || this.$empty,
		
		onFailure: options.onFailure || this.$empty,
		
		onException: options.onException || this.$empty
		
	};
	
	this.xhr = new this.$XHR(this);
	
	this.headers = this.options.headers;
	
	this.options.isSuccess = this.options.isSuccess || this.isSuccess;
}

Ajax.prototype = {
	
	$empty: function(){},
	
	$clear: function(timer){
		clearTimeout(timer);
		clearInterval(timer);
		return null;
	},
	
	$try: function(){
		for (var i = 0, l = arguments.length; i < l; i++){
			try {
				return arguments[i]();
			} catch(e){}
		}
		return null;
	},
	
	$exec: function(text){
		if (!text) return text;
		if (window.execScript){
			window.execScript(text);
		} else {
			var script = document.createElement('script');
			script.setAttribute('type', 'text/javascript');
			script.text = text;
			document.head.appendChild(script);
			document.head.removeChild(script);
		}
		return text;
	},
	
	$contains: function(text, string, separator){
		return (separator) ? (separator + text + separator).indexOf(separator + string + separator) > -1 : text.indexOf(string) > -1;
	},
	
	$capitalize: function(string){
		return string.replace(/\b[a-z]/g, function(match){
			return match.toUpperCase();
		});
	},
	
	$stripScripts: function(text, option){
		var scripts = '';
		text = text.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
			scripts += arguments[1] + '\n';
			return '';
		});
		if (option === true) this.$exec(scripts);
		return text;
	},
	
	$XHR: function(obj){
		return obj.$try(function(){
			return new XMLHttpRequest();
		}, function(){
			return new ActiveXObject('MSXML2.XMLHTTP');
		}, function(){
			return new ActiveXObject('Microsoft.XMLHTTP');
		});
	},

	fireEvent: function(evt, args){
		this.options['on' + this.$capitalize(evt)](args);
		return this;
	},
	
	setRequestHeader: function(name, value){
		if ( !this.headers[name] || this.headers.hasOwnProperty(name) )
			this.headers[name] = value;
		return this;
	},
	
	getRequestHeader: function(name){
		var self = this;
		return this.$try(function(){
			return self.xhr.getResponseHeader(name);
		});
	},
	
	isSuccess: function(){
		return ((this.status >= 200) && (this.status < 300));
	},
	
	processScripts: function(text){
		if (this.options.evalResponse || (/(ecma|java)script/).test(this.getRequestHeader('Content-type'))) return this.$exec(text);
		return this.$stripScripts(text, this.options.evalScripts);
	},
	
	onStateChange: function(){
		if (this.xhr.readyState != 4 || !this.running) return;
		var self = this;
		this.running = false;
		this.status = 0;
		this.$try(function(){
			self.status = self.xhr.status;
		});
		this.xhr.onreadystatechange = this.$empty;
		if (this.options.isSuccess.call(this, this.status)){
			this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML};
			this.success(this.response.text, this.response.xml);
		} else {
			this.response = {text: null, xml: null};
			this.failure();
		}
	},
	
	startTimer: function(data){
		var fn = function(){
			if (!this.running) this.send({data: data});
		};
		this.timer = setTimeout(fn, this.options.initialDelay);
		this.lastDelay = this.options.initialDelay;
		this.completeCheck = function(response){
			this.$clear(this.timer);
			this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
			this.timer = setTimeout(fn, this.lastDelay);
		};
		return this.addEvent('complete', this.completeCheck);
	},

	stopTimer: function(){
		this.$clear(this.timer);
		return this.removeEvent('complete', this.completeCheck);
	},
	
	success: function(text, xml){
		this.onSuccess(this.processScripts(text), xml);
	},
	
	onSuccess: function(){
		this.fireEvent('complete', arguments).fireEvent('success', arguments);
	},
	
	failure: function(){
		this.onFailure();
	},
	
	onFailure: function(){
		this.fireEvent('complete').fireEvent('failure', this.xhr);
	},
	
	check: function(){
		if (!this.running) return true;
		return false;
	},
	
	send: function(){
		if (!this.check()) return this;
		this.running = true;
		
		var self = this;
		
		var options = this.options;
		
		var data = options.data, url = String(options.url), method = options.method.toLowerCase();
		
		if (this.options.urlEncoded && method == 'post'){
			var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
			this.setRequestHeader('Content-type', 'application/x-www-form-urlencoded' + encoding);
		}
		
		if (this.options.noCache){
			var noCache = 'noCache=' + new Date().getTime();
			data = (data) ? noCache + '&' + data : noCache;
		}
		
		var trimPosition = url.lastIndexOf('/');
		if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
		
		if (data && method == 'get'){
			url = url + (this.$contains(url, '?') ? '&' : '?') + data;
			data = null;
		}
		
		this.xhr.open(method.toUpperCase(), url, this.options.async);
		
		this.xhr.onreadystatechange = function(){
			self.onStateChange();
		};
		
		this.fireEvent('request');
		
		this.xhr.send(data);
		
		if (!this.options.async) this.onStateChange();
		
		return this;
	},
	
	cancel: function(){
		if (!this.running) return this;
		this.running = false;
		this.xhr.abort();
		this.xhr.onreadystatechange = this.$empty();
		this.xhr = new this.$XHR(this);
		this.fireEvent('cancel');
		return this;
	}
	
}
