// developer notes to self: 
// 		1. test object-based url creation methods. make sure this works.
// 		2. make sure that synchronous requests are supported, retarded though they may be

	//// BEGIN HEADER ////////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////////////////////////
	////                                                                                          ////
	////    HTTPHANDLER TOOLS. V1.0                                                               ////
	////                                                                                          ////
	////    Jose Cao-Garcia                                                                       ////
	////                                                                                          ////
	////    This software is licensed under the Creative Commons                                  ////
	////    Attribution-ShareAlike 2.5 License:                                                   ////
	////    <http://creativecommons.org/licenses/by-sa/2.5/legalcode>                             ////
	////                                                                                          ////
	////    HELP/INFO/DEVELOPER CONTACT: jose@jcao.com, http://jcao.com                           ////
	////                                                                                          ////
	//////////////////////////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////////////////////////
	////                                                                                          ////
	////    qryHandler     class - release                                                        ////
	////    urlHandler     class - release                                                        ////
	////    cookieHandler  class - beta                                                           ////
	////    xmlhttpHandler class - release                                                        ////
	////                                                                                          ////
	//////////////////////////////////////////////////////////////////////////////////////////////////
	//// END HEADER //////////////////////////////////////////////////////////////////////////////////



	/////////////////////////////////////////////////////////////////////////////////////////////////
	//// A CLASS THAT PROVIDES SIMPLE QUERYSTRING PARSING AND CREATION TOOLS                     ////
	/////////////////////////////////////////////////////////////////////////////////////////////////
		function queryHandler(qryData) {
		// scope
			var root = this;
		// object will store name-value pairs
			root.pairs = {};
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// RETURNS COMPLETE URLENCODED QUERYSTRING FOR INSTANCE                                    ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			root.getString = function() {
				var qstr = '';
				for (var loop in root.pairs) {
					qstr += escape(loop) + '=' + escape(root.pairs[loop]) + '&';
				}
				return qstr.substring(0, qstr.length-1);
			}
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// DUMP PAIRS INTO OBJECT AS PROPERTIES                                                    ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			if (qryData) {
				switch (typeof(qryData)) {
					case 'object':
					// save properties as pairs
						for (var loop in qryData) {
							root.pairs[loop] = unescape(qryData[loop]);
						}
					break;
					case 'string':
						if (/[?||&||=]/.test(qryData)) {
						// isolate querystring segment of string, and split by ampersand.
							qryData = qryData.split('#')[0].match(/\?.+/);
							if (qryData) {
							// split into pairs
								qryData = qryData.toString().split('?')[1].split('&')
							// handle string and save pairs
								for (var loop in qryData) {
									var thisPair  = qryData[loop].split('=');
									var thisName  = thisPair[0] || 'undefined';
									var thisValue = thisPair[1] || 'undefined';
									root.pairs[unescape(thisName)] = unescape(thisValue);
								}
							}
						}
					break;
				}
			}
		}



	/////////////////////////////////////////////////////////////////////////////////////////////////
	//// A CLASS THAT PROVIDES SIMPLE COOKIE READING AND WRITING TOOLS                           ////
	/////////////////////////////////////////////////////////////////////////////////////////////////
		function cookieHandler() {
		// scope
			var root = this;
				root.url = new urlHandler(document.location.href);
		//// GETS VALUE OUT OF COOKIE, OR REFRESHES ALL COOKIE PAIRS.
			root.get = function(name) {
			// create new pairs obj
				root.pairs = []
			// get raw cookie string, and parse it
				var cookieString = document.cookie;
				var cookieArr     = cookieString.split('; ');
				if (cookieString) {
				// loop through pairs and assign them in the array
					for (var i in cookieArr) {
						var thisPair  = cookieArr[i];
						var thisName  = unescape(thisPair.split('=')[0]);
						var thisValue = unescape(thisPair.split('=')[1]);
						root.pairs[thisName] = thisValue;
					}
				}
				return (name && root.pairs[name]) ? root.pairs[name] : null;
				//return "it's alive";
			}
		//// SET COOKIE   cookieObj = {name:'name', value:'value', expires: number of days, path:'path' }
			root.set = function(cookieObj) {
				root.get();
				var cookieStr     = cookieObj.name + '=' + escape(cookieObj.value);
			// handle expiration.
				if (cookieObj.expires) {
					switch(typeof(cookieObj.expires)) {
						case 'number':
							var futureDate = new Date();
							futureDate.setTime(futureDate.getTime()+(cookieObj.expires*24*60*60*1000));
							cookieDate = futureDate.toGMTString();
						break;
						case 'string':
							var cookieDate = new Date(cookieObj.expires).toGMTString();
						break;
					}
					cookieStr += '; expires=' + cookieDate;
				}
			// add in special path/domain params.
				cookieStr += (typeof(cookieObj.path) != 'undefined') ? '; path=' + cookieObj.path : '; path=/';
//				cookieStr += (typeof(cookieObj.domain) != 'undefined') ? '; domain=' + cookieObj.domain : '; domain=' + root.url.domain;
			// set or not set security flag
//				if ((typeof(cookieObj.secure) != 'undefined' && cookieObj.secure) || root.url.protocol == 'https') { cookieStr += '; secure'; }
			// set and update pairs
				document.cookie = cookieStr;
				root.get();
			}
		//// EXPIRE COOKIE
			root.expire = function(name) {
				root.get();
				root.set({name:name, value:'false', expires:'October 24, 1929' });
				root.get();
			}
		//// RENEW COOKIE
			root.renew = function(name, expires) {
				root.get();
				if (root.pairs[name]) {
					root.set({name:name, value:root.pairs[name], expires: expires})
					return true;
				} else {
					return false;
				}
				root.get();
			}
		//// EXPIRES ALL COOKIES
			root.killAll = function() {
				root.get();
				for (var i in root.pairs) { root.expire(i); }
				root.get();
			}
		// check to see if cookies are enabled
			root.set({name:'testCookie', value:'testCookie', expires: 10})
			root.get();
			root.enabled = (typeof(root.pairs.testCookie) != 'undefined');
			if (root.enabled) { root.expire('testCookie'); }
		// initialize pair values.
			root.get();
		}



	/////////////////////////////////////////////////////////////////////////////////////////////////
	//// A CLASS THAT PROVIDES SIMPLE URL PARSING AND CREATION TOOLS                             ////
	/////////////////////////////////////////////////////////////////////////////////////////////////
		var urlHandler = function(url) {
		// scope
			var root = this;
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// FUNCTION CONVERTS RELATIVE PATHS TO ABSOLUTE BASED ON LOCATION                          ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			root.getAbsFromRel  = function(str) {
			// extract local and relative path to arrays, remove pre/antecedent slashes
				var locPathArr = root.strGetPath(document.location.href).toString();
					locPathArr = locPathArr.substring(1, (locPathArr.length-1)).split('/');
				var relPathArr = root.strGetPath(str).toString();
					relPathArr = relPathArr.substring(0, (relPathArr.length-1)).split('/');
			// modify location path array by relative path array
				for (var i in relPathArr) {
					var relNode = relPathArr[i];
					if (relNode == '.'||'')                   { continue;                 }
					if (relNode == '..' && locPathArr.length) { locPathArr.pop(relNode);  }
					if (/^[^.].*&/.test(relNode))             { locPathArr.push(relNode); }
				}
			// return final path as string
				var locPathStr = '/' + locPathArr.join('/') +  '/';
					locPathStr = locPathStr.split('//').join('/');
				return locPathStr;
			}
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// FUNCTION: MAKES SURE REGEXP RETURNS ARE STRINGS VIA CONCAT/TOSTRING ...                 ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			root.makeStr        = function(str) {
				return (typeof(str) == 'string') ? str : (
					(str) ? str.toString() : null
				);
			}
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// EXPRESSIONS FOR EVALUATING URI/PATH TYPES AND EXTRACTING THEIR COMPONENT STRINGS        ////
		//// NOTE: each expression returns null when test not relevant to string.                    ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			root.strIsUri       = function(str) { str = str.toString(); return root.makeStr(/^\w+(?=:\/\/)/.test(str)); }
			root.strIsRel       = function(str) { str = str.toString(); return root.makeStr(/^[\?#]|^[\.\w]+(?=$)|^[\.\w]+(?=[\/])/.test(str)); }
			root.strIsAbs       = function(str) { str = str.toString(); return root.makeStr(/^\//.test(str)); }
			root.strGetProtocol = function(str) { str = str.toString(); return root.makeStr(str.match(/^\w+(?=:\/\/)/)); }
			root.strGetUser     = function(str) { str = str.toString(); return root.makeStr(str.replace(/^\w+\:\/\//, '').match(/^[^\/\.\?#@]+(?=:[^\/\.\?#@]+@)|^[^\/\.\?#@]+(?=@)/)); }
			root.strGetPassword = function(str) { str = str.toString(); return root.makeStr((/^\w+:\/\/[^:\/@]+:[^:\/@]+@/.test(str)) ? str.replace(/^\w+\:\/\//, '').match(/[^:\/@]+(?=@)/) : null); }
			root.strGetDomain   = function(str) { str = str.toString(); return root.makeStr((/^\w+:\/\//.test(str)) ? str.replace(/^\w+:\/\/.+@|^\w+:\/\//, '').replace(/[:\/].*/, '') : null); }
			root.strGetPort     = function(str) { str = str.toString(); return root.makeStr(str.replace(/^\w+:\/\/?[^:\/]*/, '').replace(/\/.*/, '').match(/[\d]+$/)); }
			root.strGetPath     = function(str) { str = str.toString(); return root.makeStr(str.replace(/^\w+:\/\/[^\/]+/, '').replace(/^(?=[^\.\/])/,'./').split(/[\?#]/)[0].match(/^.+\/|\//)); }
			root.strGetFile     = function(str) { str = str.toString(); return root.makeStr(str.replace(/[\?#].*/,'').match(/[\-.\w\.]+\.[\-.\w\.]+$/)); }
			root.strGetQuery    = function(str) { str = str.toString(); return root.makeStr(str.split(/#/)[0].replace(/^[^\?]*\?|^.*$/,'').match(/^.+$/)); }
			root.strGetHash     = function(str) { str = str.toString(); return root.makeStr(str.replace(/^[^#]*#|^.*$/,'').match(/^.+$/)); }
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// FUNCTION FOR CONSTRUCTING/POPULATING URL PROPERTIES                                     ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			root.updateData = function(url) {
				var thisUrl = document.location.href;
				switch (typeof(url)) {
				// parse string into url object properties
					case 'string':
					// resource properties of url
						root.protocol    = root.strGetProtocol(url) || root.strGetProtocol(thisUrl);
						root.user        = root.strGetUser(url)     || root.strGetUser(thisUrl);
						root.password    = root.strGetPassword(url) || root.strGetPassword(thisUrl);
						root.domain      = root.strGetDomain(url)   || root.strGetDomain(thisUrl);
						root.port        = root.strGetPort(url)     || root.strGetPort(thisUrl);
						root.path        = root.strGetPath(url)     || root.strGetPath(thisUrl);
						root.file        = root.strGetFile(url)     || root.strGetFile(thisUrl);
					// data properties of url
						root.querystring = new queryHandler(url);
						root.hash        = root.strGetHash(url);
					// convert relative to absolute path
						if (root.strIsRel(root.path)) { root.path = root.getAbsFromRel(root.path); }
					break;
				// parse object into url object properties
					case 'object':
					// resource properties of url
						root.protocol    = url.protocol || root.strGetProtocol(thisUrl);
						root.user        = url.user     || root.strGetUser(thisUrl);
						root.password    = url.password || root.strGetPassword(thisUrl);
						root.domain      = url.domain   || root.strGetDomain(thisUrl);
						root.port        = url.port     || root.strGetPort(thisUrl);
						root.path        = url.path     || root.strGetPath(thisUrl);
						root.file        = url.file     || root.strGetFile(thisUrl);
					// data properties of url
						root.querystring = true;
						root.hash        = true;
					break;
				}
			// set url property from extracted data
				root.url = root.string();
			}
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// FUNCTION RETURNS COMPLETE/VALID URL AS STRING                                           ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			root.string = function() {
			// construct url string
				var url = root.protocol + '://';
					url+= (root.user) ? root.user + ((root.password) ? ':' + root.password + '@' : '@') : '';
					url+= root.domain;
					url+= (root.port) ? ':' + root.port : '';
					url+= root.path;
					url+= root.file || '';
					url+= '?' + root.querystring.getString();
					if (root.hash) { url+= '#' + root.hash; }
			// done
				return url;
			}
			// populate data initially
				root.updateData(url || document.location.href);
		}



	/////////////////////////////////////////////////////////////////////////////////////////////////
	//// A CLASS CLASS FOR HANDLING XMLHTTP REQUESTS                                             ////
	/////////////////////////////////////////////////////////////////////////////////////////////////
		function xmlHttpHandler(reqDefaults) {
		// gather scope
			var root = this;
		// set global defaults
			reqDefaults = reqDefaults || {  };
			root.defaults = {
				call     : (reqDefaults.call     || null),
				fail     : (reqDefaults.fail     || null),
				args     : (reqDefaults.args     || []),
				top      : (reqDefaults.top      || false),
				interval : (reqDefaults.interval || null),
				timeout  : (reqDefaults.timeout  || 3000)
			}
		// arrays for queing requests
			root.requests = [];          //<-- holds one-timer requests
			root.timeouts = [];          //<-- holds repeating requests
		// root.delay
			root.delay = 50;
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// SELECT/RETURN XMLHTTP OBJECT BY BROWSER CAPABILITIES                                    ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			root.xmlHttpObj = function() {
			// do not assume this is supported
				var xmlHttp = false;
			// select object by object support
				if (typeof(ActiveXObject) != 'function' && typeof(XMLHttpRequest) != 'undefined') {
				// for standards based browsers
					xmlHttp = new XMLHttpRequest();
				} else {
				// for standards-challenged browsers ...
					try {
						xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
					} catch (e) {
						xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
					}
				}
				return xmlHttp;
			}
			root.supported = (root.xmlHttpObj()) ? true : false;
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// DETERMINE CONTENT TYPE, RETURNS MIME TYPE FOR EXTENSION                                 ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			root.getType = function(request) {
				request = request.url;
			// default type is text
				var datType = 'text/txt';
			// test for some basic data types
				if      (/\.xml$/.test(request.split('?')[0]))  { datType = 'text/xml';        }
				else if (/\.js$/.test(request.split('?')[0]))   { datType = 'text/javascript'; }
				else if (/\.json$/.test(request.split('?')[0])) { datType = 'text/javascript'; }
				else if (/\.js$/.test(request.split('?')[0]))   { datType = 'text/javascript'; }
			// report back
				return datType;
			}
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// QUE NEW REQUEST FOR HANDLING                                                            ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			root.get = function(reqObj) {
				if (typeof(reqObj) == 'string') {
				// single string argument as synchronous request
					reqObj = { url: options };
				}
			// set defaults
				reqObj.call     = (reqObj.call     || root.defaults.call);
				reqObj.fail     = (reqObj.fail     || root.defaults.fail);
				reqObj.args     = (reqObj.args     || root.defaults.args);
				reqObj.top      = (reqObj.top      || root.defaults.top);
				reqObj.interval = (reqObj.interval || root.defaults.interval);
				reqObj.timeout  = (reqObj.timeout  || root.defaults.timeout);
			// object-based request validation
				if (reqObj) {
					if (!reqObj.url     || typeof(reqObj.url)      != 'string')   { throw('ajax.get(): you must specify a requst url as a string');       }
					if (reqObj.call     && typeof(reqObj.call)     != 'function') { throw('ajax.get(): reqObj.call must be a function');                  }
					if (reqObj.fail     && typeof(reqObj.fail)     != 'function') { throw('ajax.get(): reqObj.fail must be a function');                  }
					if (reqObj.args     && typeof(reqObj.args)     != 'object')   { throw('ajax.get(): reqObj.args must be an object');                   }
					if (reqObj.top      && typeof(reqObj.top)      != 'boolean')  { throw('ajax.get(): reqObj.args must be boolean true/false');          }
					if (reqObj.interval && typeof(reqObj.interval) != 'number')   { throw('ajax.get(): reqObj.interval must be specified as an integer'); }
					if (reqObj.timeout  && typeof(reqObj.timeout)  != 'number')   { throw('ajax.get(): reqObj.timeout must be specified as an integer');  }
				} else {
					throw('ajax.get(): ill-formed request!');
				}
			// act upon request object
				if (reqObj.call) {
					if (reqObj.interval) {
						root.timeouts.push(reqObj);
					} else {
						if (reqObj.top) {
							root.requests.unshift(reqObj);
						} else {
							root.requests.push(reqObj);
						}
					}
				// start queue if it is stopped
					if (root.requests.length == 1) { root.start(); }
					return root.supported;
				} else {
					// add synchronous request support later ...
				}
			}
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// MAKES HTTP REQUEST, HANDLES RESPONSE, SENDS RESPONSE TO CALLBACK FUNCTION               ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			root.sendRequest = function(reqObj, fromQueue) {
			// get the basics
				var xmlHttp = root.xmlHttpObj();
			// if supported, handle request
				if (xmlHttp) {
					var datType = root.getType(reqObj);
					if (xmlHttp.overrideMimeType && datType == 'text/xml') { xmlHttp.overrideMimeType(datType); }
				// handle request, asynchronously or synchronously
					if (reqObj.call) {
					// if callback, make asynchronous request
						xmlHttp.onreadystatechange = function() {
							if(xmlHttp.readyState == 4) {
							// handle the server response
								var serverResponse = (datType != 'text/xml')        ? xmlHttp.responseText : xmlHttp.responseXML;
							// try to return JSON as JS.
								if (datType == 'text/javascript') {
									try {
										serverResponse = eval(serverResponse);
									} catch (e) {
										try {
											eval('serverResponse = ' + serverResponse);
										} catch (e) { }
									}
								}
							// insert response into args array as first index
								reqObj.args.unshift(serverResponse);
							// create string to evaluate as arguments obj from args
								var argStr = '';
								for (var arg in reqObj.args) { argStr += 'reqObj.args[' + arg + '], ' };
								argStr = argStr.substring(0, argStr.lastIndexOf(','));
							// if request came from queue, remove it
								if (fromQueue) { root.start(); }
							// send response to callback ... eval is slow, but it provides the ability
							// to support an unlimited number of arguments in the callback function ...
								eval('reqObj.call(' + argStr + ')');
							}
						}
						xmlHttp.open('GET', reqObj.url, true);
						xmlHttp.send(null);
						return true;
					} else {
						// add support for synchronous requests here ...
					}
				} else {
					return false;
				}
			}
		/////////////////////////////////////////////////////////////////////////////////////////////////
		//// REMOVES AND FIRES A SINGLE REQUEST FROM THE REQUESTS QUEUE                              ////
		/////////////////////////////////////////////////////////////////////////////////////////////////
			root.start = function() {
				if (root.requests.length > 0) {
					var nextRequest = function() {
						var request = root.requests.shift();
						root.sendRequest(request, true);
					}
				// put slight delay on request
					root.timer = setTimeout(nextRequest, root.delay);
				}
			}
		}



		document.qstr    = new queryHandler();
		document.url     = new urlHandler();
		var cookie       = new cookieHandler();
		var ajax         = new xmlHttpHandler();


		var getCookie = function(arg1) {
			return cookie.get(arg1) || 'undefined';
		}
		var setCookie = function(arg1) { cookie.set(arg1); }



