/**_______________________________________
 *
 *    byteson :: simple Ajaj in few bytes
 * ---------------------------------------
 *
 * @author		Andrea Giammarchi
 * @site		http://www.devpro.it/byteson/
 * @version		0.2b
 * @requires		anything
 * @credits		Matteo Galli (aka Ratatuia) for Safari debug
 * @compatibility	FireFox 1+, Opera 8+, Safari 2+, KDE 3.5+ and IE 5.5+
 * @note		please read byteson.txt to know more about this library
 *			[http://www.devpro.it/byteson/byteson.txt]
 * @license
 * ---------------------------------------
 * 
 * Copyright (c) 2006 Andrea Giammarchi
 *
 * Permission is hereby granted, free of charge,
 * to any person obtaining a copy of this software and associated
 * documentation files (the "Software"),
 * to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * _______________________________________
 */
byteson = new function() {

	// public methods

	/**
	 * public method
	 *
	 *	byteson.convert(params:* [, result:Instance]):*
	 *
	 * @param	*		String or Object
	 * @param	Instance	optional new generic class instance if first
	 *				parameter is an object.
	 * @return	*		new Date or new Instance with object parameters.
	 *
	 * @note	please read Extra byteson.convert method Informations to know
	 *		more about this method (bytefx.txt summary number 4).
	 */
	this.convert = function(params, result){
		switch(params.constructor){
			case	Boolean || (Number && isFinite(params)):
				result = String(params);
				break;
			case	Date:
				result = '"'.concat(
					params.getFullYear(), '-',
					common.d(params.getMonth() + 1), '-',
					common.d(params.getDate()), 'T',
					common.d(params.getHours()), ':',
					common.d(params.getMinutes()), ':',
					common.d(params.getSeconds()),
				'"');
				break;
			case	String:
				if(/^[0-9]{4}\-[0-9]{2}\-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/.test(params)){
					result = new Date;
					result.setHours(common.i(params, 11, 2));
					result.setMinutes(common.i(params, 14, 2));
					result.setSeconds(common.i(params, 17, 2));
					result.setMonth(common.i(params, 5, 2) - 1);
					result.setDate(common.i(params, 9, 2));
					result.setFullYear(common.i(params, 0, 4));
				};
				break;
			default:
				var	n, tmp = [];
				if(result) {
					for(n in params) result[n] = params[n];
				} else {
					for(n in params) {
						if(params.hasOwnProperty(n) && !!(result = byteson.encode(params[n])))
							tmp.push(byteson.encode(n).concat(':', result));
					};
					result = '{'.concat(tmp.join(','), '}');
				};
				break;
		};
		return result;
	};

	/**
	 * public method
	 *
	 *	byteson.decode(params:String):*
	 *
	 * @param	String		valid JSON encoded string
	 * @return	*		converted variable or undefined
	 *				is params is not a JSON compatible string.
	 *
	 * @example
	 *		byteson.decode('{"param":"value"}'); // object
	 *		byteson.decode('["one",two,true,false,null,{},[1,2]]'); // array
	 *
	 * @note	this method uses the same regular expression of original json.js code:
	 *		http://www.json.org/json.js
	 */
	this.decode = function(params){
		var	tmp = new RegExp('^("(\\\\.|[^"\\\\\\n\\r])*?"|[,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t])+?$');
		return tmp.test(params) ? eval('('.concat(params, ')')) : undefined;
	};

	/**
	 * public method
	 *
	 *	byteson.encode(params:*):String
	 *
	 * @param	*		Array, Boolean, Date, Number, Object, String or null variable.
	 * @return	String		JSON generic object rappresentation
	 *				or empty string if param is not compatible.
	 *
	 * @example
	 *		byteson.encode(new Array(1,"two")); // '[1,"two"]'
	 *
	 *		var	obj = new Object();
	 *		obj.param = "value";
	 *		obj.param2 = "value2";
	 *		byteson.encode(obj); // '{"param":"value","param2":"value2"}'
	 */
	this.encode = function(params){
		var	result = '';
		if(params === null)
			result = 'null';
		else if(!{'function':1,'undefined':1,'unknown':1}[typeof(params)]) {
			switch(params.constructor) {
				case	Array:
					for(var	i = 0, j = params.length, tmp = []; i < j; i++) {
						if(!!(result = byteson.encode(params[i])))
							tmp.push(result);
					};
					result = '['.concat(tmp.join(','), ']');
					break;
				case	String:
					var	n = {'\b':'b','\t':'t','\n':'n','\f':'f','\r':'r','"':'"','\\':'\\','/':'/'};
					result = '"'.concat(params.replace(
						/(\x5c|\x2F|\x22|[\x0c-\x0d]|[\x08-\x0a])/g, common.s
						).replace(
						/([\x00-\x07]|\x0b|[\x0e-\x1f])/g, common.u
					), '"');
					break;
				default:
					result = byteson.convert(params); 
					break;
			};
		};
		return result;
	};

	/**
	 * public method
	 *
	 *	byteson.request(page:String [, params:* [, options:Object ]]):Boolean
	 *
	 * @param	String		valid server side url to call
	 * @param	*		every JSON compatible variable to send
	 * @param	Object		byteson dedicated generic options object.
	 *				Please read Generic Object options Informations
	 *				to know more (Index N# 5).
	 * @return	Boolean		true if request is possible, false if browser
	 *				is not Ajaj ready (IE 4 or lower, NN4 and other old browsers)
	 *
	 * @note	If You send params argument request will send JSON value using the POST method.
	 *		You could get this JSON sent string using the "byteson" POST variable.
	 *		This will be automatically encoded using encodeURIComponent function then You
	 *		need to decode, if necessary, and then parse with your favourite JSON porting.
	 */
	this.request = function(page, params, options){
		return (function(page, params, options){
			function error(status){
				if(options.error)
					options.error(status, xhr, request);
			};
			var	timeout = 0, tmp = [], xhr = byteson.xhr(), request = {page:page, params:params, options:options};
			if(xhr) {
				tmp = [
					function(n){if(n)tmp[n]=tmp[1]},
					function(){},
					function(){if(options.send)	options.send(xhr, request);	tmp[0](2)},
					function(){if(options.loading)	options.loading(xhr, request);	tmp[0](3)},
					function(){
						if(options.timeout)
							clearTimeout(timeout);
						if({200:1,304:1}[xhr.status]){
							if(options.load)
								options.load(byteson.decode(tmp[5](xhr.responseText)), xhr, request);
						}
						else
							error('Request failure\nStatus '.concat(xhr.status));
					},
					function(s){return options.decode ? decodeURIComponent(s) : s},
					options.async !== undefined ? options.async : true,
					options.user ? options.user : '',
					options.pass ? options.pass : ''
				];
				if(params === null)
					xhr.open('get', page, tmp[6], tmp[7], tmp[8]);
				else {
					xhr.open('post', page, tmp[6], tmp[7], tmp[8]);
					xhr.setRequestHeader('Content-Length', params.length);
					xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
				};
				xhr.onreadystatechange = function(){tmp[xhr.readyState]()};
				if(options.init)
					options.init(xhr);
				if(options.timeout)
					timeout = setTimeout(function(){xhr.abort();error('timeout')}, options.timeout);
				xhr.send(params);
			};
			return xhr ? true : false;
		})(page, params === null || params === undefined ? null : 'byteson='.concat(encodeURIComponent(byteson.encode(params))), options || {});
	};
	
	 /**
	 * public method
	 *
	 *	byteson.xhr(void):*
	 *
	 * @return	*		new XMLHttpRequest (Xml H R => xhr) or null if browser is not compatible
	 */
	this.xhr = function(){
		var	xhr = null, n = 7;
		if(window.XMLHttpRequest)
			xhr = new XMLHttpRequest;
		else if(window.ActiveXObject) {
			while(!xhr){
				try{xhr = new ActiveXObject(['Msxml2','XMLHTTP', n, '0'].join('.'))}
				catch(e){--n}
			}
		};
		return xhr;
	};

	// private variable, contains common functions or variable
	var	common = {

			// characters object, useful to convert some char in a JSON compatible way
			c:{'\b':'b','\t':'t','\n':'n','\f':'f','\r':'r','"':'"','\\':'\\','/':'/'},

			// decimal function, returns a string with length === 2 for date convertion
			d:function(n){return n < 10 ? '0'.concat(n) : n},

			// integer function, returns integer value from a piece of string
			i:function(e, p, l){return parseInt(e.substr(p, l))},

			// slash function, add a slash before a common.c char
			s:function(i,d){return '\\'.concat(common.c[d])},

			// unicode function, return respective unicode string
			u:function(i,d){var n = d.charCodeAt(0).toString(16);return '\\u'.concat(n.length < 2 ? '000' : '00', n)}
	};
};
