(function(tools) {
	
	var private = {};
	
	// ascii
	tools.ascii_remove_control_chars = function(instr) {
		var str = ""+instr;
		var result = "";
		for(var i = 0; i<str.length; i++) {
			var chr = str.charAt(i);
			if (!in_array(chr,private._ascii_control_chars())) result += chr;
		}
		return result;
	}
	tools.ascii_remove_control_chars_recursive = function(arr) {
		if (typeof arr=='string') return tools.ascii_remove_control_chars(arr);
		if (typeof arr!='object') return arr;
		var arrcopy = {};
		for(var index in arr) {
			if (typeof arr[index]=='string') { arrcopy[index] = tools.ascii_remove_control_chars( arr[index] ); }
			else if (typeof arr[index]=='object') { arrcopy[index] = tools.ascii_remove_control_chars_recursive( arr[index] ); }
			else { arrcopy[index] = arr[index]; }
		}
		return arrcopy;
	}
	private._ascii_control_chars = function() {
		var static = private._ascii_control_chars; // static		
		if (isset(static.chars)) { return static.chars; }
		static.chars = [];
		for(var i = 0 ; i<9 ; i++) { static.chars.push( chr(i) ); }		
		for(var i = 11; i<13; i++) { static.chars.push( chr(i) ); }		
		for(var i = 14; i<32; i++) { static.chars.push( chr(i) ); }		
		static.chars.push( chr(127) );
		return static.chars;
	}
	private._ascii_extended_chars = function() {
		var static = private._ascii_extended_chars; // static
		if (isset(static.chars)) { return static.chars; } 
		static.chars = [];	
		for(var i = 128; i<256; i++) { static.chars.push( chr(i) ); }
		return static.chars;
	}
	// html
	tools.html_encode = function(str) {
		return tools.html_encode_extended(tools.html_encode_reserved(str));
	}	
	tools.html_encode_extended = function(str) {
		var extended_chars = private._ascii_extended_chars();
		var result="";
		for(var i = 0;  i < strlen(str); i++) {
			var chr_ = str.charAt(i);
			if (in_array(chr_,extended_chars)) { result += private._html_encode_char(chr_); }
			else { result += chr_; }
		}
		return result;
	}
	tools.html_encode_reserved = function(str) {
		var reserved_chars = private._html_reserved_chars();
		var result="";
		for(var i = 0;  i < strlen(str); i++) {
			var chr_ = str.charAt(i);
			if (in_array(chr_,reserved_chars)) { result += private._html_encode_char(chr_); }
			else { result += chr_; }
		}
		return result;
	}
	tools.html_decode = function(str) {
		return tools.html_decode_reserved(tools.html_decode_extended(str));
	}
	tools.html_decode_extended = function(str) {
		var result = str;
		var offset = 0;
		var entity;
		while(entity = private._html_next_entity(result,offset)) {
			var chr_ = private._html_decode_entity(entity);
			if ( strlen(chr_)==1 && in_array(chr_,private._ascii_extended_chars()) ) {
				entity_start = strpos(result,entity,offset);
				entity_end = entity_start+strlen(entity);
				result = substr(result,0,entity_start)+chr_+substr(result,entity_end);
				offset = entity_start+1;
			} else {
				offset = strpos(result,entity,offset)+strlen(entity);
			}			
		}
		return result;		
	}
	tools.html_decode_reserved = function(str) {
		var result = str;
		var offset = 0;
		var entity;
		while(entity = private._html_next_entity(result,offset)) {
			var chr_ = private._html_decode_entity(entity);
			if ( strlen(chr_)==1 && in_array(chr_,private._html_reserved_chars()) ) {
				entity_start = strpos(result,entity,offset);
				entity_end = entity_start+strlen(entity);
				result = substr(result,0,entity_start)+chr_+substr(result,entity_end);
				offset = entity_start+1;
			} else {
				offset = strpos(result,entity,offset)+strlen(entity);
			}			
		}
		return result;		
	}
	private._html_reserved_chars = function() {
		var static = private._html_reserved_chars;
		if (isset(static.chars)) { return static.chars; }		
		static.chars = [
			chr(34), // "
			chr(38), // &
			chr(60), // <
			chr(62)  // >
		];
		return static.chars;
	}
	private._html_common_entities = function() {
		var static = private._html_reserved_chars;
		if (isset(static.entities_)) { return static.entities_; } 
		static.entities_ = {
			// reserved
			'&#34;' : '&quot;',
			'&#38;' : '&amp;',
			'&#60;' : '&lt;',	
			'&#62;' : '&gt;',	
			// extended
		    '&#160;' : '&nbsp;', 
			'&#161;' : '&iexcl;',
			'&#162;' : '&cent;',
			'&#163;' : '&pound;',
			'&#164;' : '&curren;',
			'&#165;' : '&yen;', 
			'&#166;' : '&brvbar;',
			'&#167;' : '&sect;',
			'&#168;' : '&uml;',
			'&#169;' : '&copy;',
			'&#170;' : '&ordf;', 
			'&#171;' : '&laquo;',
			'&#172;' : '&not;',
			'&#173;' : '&shy;',
			'&#174;' : '&reg;',
			'&#175;' : '&macr;',
			'&#176;' : '&deg;',
			'&#177;' : '&plusmn;',
			'&#178;' : '&sup2;',
			'&#179;' : '&sup3;',
			'&#180;' : '&acute;', 
	 		'&#181;' : '&micro;',
			'&#182;' : '&para;',
			'&#183;' : '&middot;',
			'&#184;' : '&cedil;',
			'&#185;' : '&sup1;', 
			'&#186;' : '&ordm;',
			'&#187;' : '&raquo;',
			'&#188;' : '&frac14;',
			'&#189;' : '&frac12;',
			'&#190;' : '&frac34;', 
			'&#191;' : '&iquest;',
			'&#192;' : '&Agrave;',
			'&#193;' : '&Aacute;', 
			'&#194;' : '&Acirc;',
			'&#195;' : '&Atilde;',
			'&#196;' : '&Auml;',
			'&#197;' : '&Aring;',
			'&#198;' : '&AElig;',
			'&#199;' : '&Ccedil;', 
			'&#200;' : '&Egrave;',
			'&#201;' : '&Eacute;',
			'&#202;' : '&Ecirc;',
			'&#203;' : '&Euml;',
			'&#204;' : '&Igrave;',
			'&#205;' : '&Iacute;', 
			'&#206;' : '&Icirc;',
			'&#207;' : '&Iuml;',
			'&#208;' : '&ETH;',
			'&#209;' : '&Ntilde;',
			'&#210;' : '&Ograve;', 
			'&#211;' : '&Oacute;',
			'&#212;' : '&Ocirc;',
			'&#213;' : '&Otilde;',
			'&#214;' : '&Ouml;',
			'&#215;' : '&times;',
			'&#216;' : '&Oslash;', 
			'&#217;' : '&Ugrave;',
			'&#218;' : '&Uacute;',
			'&#219;' : '&Ucirc;',
			'&#220;' : '&Uuml;',
			'&#221;' : '&Yacute;', 
			'&#222;' : '&THORN;',
			'&#223;' : '&szlig;',
			'&#224;' : '&agrave;',
			'&#225;' : '&aacute;',
			'&#226;' : '&acirc;', 
			'&#227;' : '&atilde;',
			'&#228;' : '&auml;',
			'&#229;' : '&aring;',
			'&#230;' : '&aelig;',
			'&#231;' : '&ccedil;', 
			'&#232;' : '&egrave;',
			'&#233;' : '&eacute;',
			'&#234;' : '&ecirc;',
			'&#235;' : '&euml;',
			'&#236;' : '&igrave;', 
			'&#237;' : '&iacute;',
			'&#238;' : '&icirc;',
			'&#239;' : '&iuml;',
			'&#240;' : '&eth;',
			'&#241;' : '&ntilde;', 
			'&#242;' : '&ograve;',
			'&#243;' : '&oacute;',
			'&#244;' : '&ocirc;',
			'&#245;' : '&otilde;',
			'&#246;' : '&ouml;', 
			'&#247;' : '&divide;',
			'&#248;' : '&oslash;',
			'&#249;' : '&ugrave;',
			'&#250;' : '&uacute;',
			'&#251;' : '&ucirc;',
			'&#252;' : '&uuml;', 
			'&#253;' : '&yacute;',
			'&#254;' : '&thorn;',
			'&#255;' : '&yuml;',
			// utf8
			'&#338;' : '&OElig;',
			'&#339;' : '&oelig;', 
			'&#352;' : '&Scaron;',
			'&#353;' : '&scaron;',
			'&#376;' : '&Yuml;',
			'&#710;' : '&circ;',
			'&#732;' : '&tilde;', 
			'&#8194;' : '&ensp;',
			'&#8195;' : '&emsp;',
			'&#8201;' : '&thinsp;',
			'&#8204;' : '&zwnj;',
			'&#8205;' : '&zwj;', 
			'&#8206;' : '&lrm;',
			'&#8207;' : '&rlm;',
			'&#8211;' : '&ndash;',
			'&#8212;' : '&mdash;',
			'&#8216;' : '&lsquo;', 
			'&#8217;' : '&rsquo;',
			'&#8218;' : '&sbquo;',
			'&#8220;' : '&ldquo;',
			'&#8221;' : '&rdquo;',
			'&#8222;' : '&bdquo;',
			'&#8224;' : '&dagger;',
			'&#8225;' : '&Dagger;',
			'&#8230;' : '&hellip;',
			'&#8240;' : '&permil;',
			'&#8249;' : '&lsaquo;',
			'&#8250;' : '&rsaquo;',
			'&#8364;' : '&euro;',
			'&#8482;' : '&trade;'
		};
		return static.entities_;	
	}
	private._html_common_entities_flipped = function() {
		var static = private._html_common_entities_flipped;
		if (isset(static.flipped)) return static.flipped;
		static.flipped = array_flip(private._html_common_entities());
		return static.flipped;
	}
	private._html_encode_char = function(chr_) {
		var common_entities = private._html_common_entities(); 
		unset(common_entities,'&#39;'); // do not use &apos; IE6 does not support it.
		var entity = '&#'+ord(chr_)+';'; 
		if (isset(common_entities[entity])) entity = common_entities[entity];
		return entity;
	}
	private._html_decode_entity = function(entity) {
		if (substr(entity,0,1)=='&' && substr(entity,1,1)!='#' && substr(entity,strlen(entity)-1,1)==';') {
			var common_entities_flipped = private._html_common_entities_flipped();
			if (isset(common_entities_flipped[entity])) entity = common_entities_flipped[entity];
		}
		if (substr(entity,0,2)=='&#' && substr(entity,strlen(entity)-1,1)==';') {
			var ord_ = substr(entity,2,strlen(entity)-3);
			if (!is_numeric(ord_)) return entity;
			var chr_ = chr(ord_);
			return chr_;
		} else {
			return entity;
		}
	}
	private._html_next_entity = function(str,start_offset) {
		if (!start_offset) start_offset = 0;
		var entity = null;
		for(var i = start_offset; i < strlen(str); i++) {
			var chr_ = str.charAt(i);
			if (chr_=='&') { entity='&'; }
			else if (entity=='&' && chr_=='#') { entity = '&#'; }
			else if (entity!=null && strpos('abcdefghijklmnopqrstuvwxyz1234567890',strtolower(chr_))!=-1) { entity += chr_; }
			else if (entity!=null && chr_==';') { entity += ";"; break; }
			else { entity=null; }
			if (i==strlen(str)-1 ) { entity=null; break; }
		}
		//alert(entity+" : "+str+" : "+start_offset);
		return entity;
	}
	// url
	tools.url_encode = function(instr) {
		var str = ""+instr;
		return tools.url_encode_extended(tools.url_encode_reserved(str));
	}
	tools.url_encode_recursive = function(arr) {
		if (typeof arr=='string') return tools.url_encode(arr);
		if (typeof arr!='object') return arr;
		var arrcopy = {};
		for(var index in arr) {
			if (typeof arr[index]=='string') { arrcopy[index] = tools.url_encode( arr[index] ); }
			else if (typeof arr[index]=='object') { arrcopy[index] = tools.url_encode_recursive( arr[index] ); }
			else { arrcopy[index] = arr[index]; }
		}
		return arrcopy;
	}
	tools.url_encode_extended = function(str) {
		var extended_chars = private._ascii_extended_chars();
		var result="";
		for(var i=0;i<strlen(str);i++) {
			var chr_ = str.charAt(i);
			if (in_array(chr_,extended_chars)) { result += private._url_encode_char(chr_); }
			else { result += chr_; }
		}
		return result;
	}
	tools.url_encode_reserved = function(str) {
		var reserved_chars = private._url_reserved_chars();
		var result="";
		for(var i=0; i<strlen(str);i++) {
			var chr_ = str.charAt(i);
			if (in_array(chr_,reserved_chars)) { result += private._url_encode_char(chr_); }
			else { result += chr_; }
		}
		return result;		
	}
	tools.url_decode = function(str) {
		return tools.url_decode_reserved(tools.url_decode_extended(str));
	}
	tools.url_decode_extended = function(str) {
		var result = str;
		var offset = 0;
		var entity;
		while(entity = private._url_next_entity(result,offset)) {
			var chr_ = private._url_decode_entity(entity);
			if ( strlen(chr_)==1 && in_array(chr_,private._ascii_extended_chars()) ) {
				var entity_start = strpos(result,entity,offset);
				var entity_end = entity_start+strlen(entity);
				result = substr(result,0,entity_start)+chr_+substr(result,entity_end);
				offset = entity_start+1;
			} else {
				offset = strpos(result,entity,offset)+strlen(entity);
			}			
		}
		return result;				
	}
	tools.url_decode_reserved = function(str) {
		var result = str;
		var offset = 0;
		var entity;
		while(entity = private._url_next_entity(result,offset)) {
			var chr_ = private._url_decode_entity(entity);
			if ( strlen(chr_)==1 && in_array(chr_,private._url_reserved_chars()) ) {
				var entity_start = strpos(result,entity,offset);
				var entity_end = entity_start+strlen(entity);
				result = substr(result,0,entity_start)+chr_+substr(result,entity_end);
				offset = entity_start+1;
			} else {
				offset = strpos(result,entity,offset)+strlen(entity);
			}			
		}
		return result;				
	}
	private._url_reserved_chars = function() {
		var static = private._url_reserved_chars;
		if (isset(static.chars)) { return static.chars; }		
		static.chars = [
			":",
			"/",
			"@",
			"?",
			"=",
			"&",
			";",
			"+",
			'#',
			"%",
			"\"",
			"<",
			">",
			"\\",
			"\r",
			"\t",
			"\n",
			" "
		];
		return static.chars;
	}
	private._url_common_entities = function() {
		var static = private._url_common_entities;
		if (isset(static.entities_)) { return static.entities_; } 
		static.entities_ = {
			'%20' : '+'
		};
		return static.entities_;
	}
	private._url_common_entities_flipped = function() {
		var static = private._url_common_entities_flipped;
		if (isset(static.flipped)) return static.flipped;
		static.flipped = array_flip(private._url_common_entities());
		return static.flipped;
	}
	private._url_encode_char = function(chr_) {
		var common_entities = private._url_common_entities();
		var ord_ = ord(chr_); 
		var hex_ = dechex(ord_); 
		var entity = '%'+padleft(strtoupper(hex_),'0',2); 
		if (isset(common_entities[entity])) entity = common_entities[entity];
		return entity;
	}
	private._url_decode_entity = function(entity) {
		var common_entities = private._url_common_entities();
		var common_entities_flipped = private._url_common_entities_flipped()
		if (isset(common_entities_flipped[entity])) entity = common_entities_flipped[entity];
		if (substr(entity,0,1)=='%' && strlen(entity)==3) {
			var hex_ = substr(entity,1,2);
			var ord_ = hexdec(hex_);
			if (!is_numeric(ord_)) return entity;
			var chr_ = chr(ord_);
			return chr_;
		} else {
			return entity;
		}
	}
	private._url_next_entity = function(str,start_offset) {
		if (!start_offset) start_offset = 0;
		var entity = null;
		for(var i=start_offset;i<strlen(str);i++) {
			var chr_ = str.charAt(i);
			if (entity==null && chr_!='%' && chr_!='+') { continue; }
			else if (entity==null && chr_=='+') { entity='+'; break; }
			else if (entity==null && chr_=='%') { entity='%'; continue; }
			else if (substr(entity,0,1)=='%') {
				if (strpos("0123456789ABCDEF",strtoupper(chr_))!==false) { entity += chr_; } else { entity = null; continue; }
				if (strlen(entity)==3) { break; } else { continue; }
			}
			else { entity = null; continue; }
		}
		return entity;
	}
	// php-like functions
	function padleft(str,padchar,length){
		for(var i=strlen(str); i<length; i++) { str = padchar+str;  }
		return str;
	}
	function hexdec(hex_) { 
		return parseInt(hex_,16).toString(10); 
	}
	function dechex(dec_) {
		return strtoupper( parseInt(dec_,10).toString(16) );
	}
	function strtoupper(str) {
		str = ""+str;
		return str.toUpperCase();
	}
	function strtolower(str) {
		str = ""+str;
		return str.toLowerCase();
	}
	function strpos(haystack,needle,offset) {
		haystack = ""+haystack;
		return haystack.indexOf(needle,offset);
	}
	function is_numeric(num) {
		return !isNaN(num);
	}
	function strlen(str) {
		str = ""+str;
		return str.length;				
	}
	function substr(str,offset,length) {
		str = ""+str;
		if (length) return str.substr(offset,length);		
		if (!length) return str.substr(offset);		
	}
	function implode(glue,obj) {
		var str = "";
		var first = true;
		for(var index in obj) {
			str += obj[index];
			if (first) { first=false; }
			else { str += glue; }
		}
		return str;
	}
	function in_array(needle,haystack) {
		for(var index in haystack) {
			var value = haystack[index];
			if (value==needle) return true;
		}
		return false;
	}
	function isset(obj) {
		return typeof obj != "undefined";
	}
	function unset(obj,key) {
		delete obj[key];
	}
	function chr(code) {
		return String.fromCharCode(code);
	}
	function ord(chr) {
		chr = ""+chr;
		return chr.charCodeAt(0);
		
	}
	function array_flip(arr) {
		var flipped = {};
		for (var index in arr) {
			var value = arr[index];
			flipped[value] = index;
		}
		return flipped;
	}
	
})(window.tools = (window.tools) ? window.tools : {});