/** 
 * @fileoverview
 *
 * The rsow library is a full of all the common functionality that I use through out my 
 * various projects that use javascript.  This file specifically contains all of that 
 * functionality which I think belongs outside of a namespace (such as they are in javascript).
 *
 *
 <pre>
		Copyright (c) 2000-2008 Palaquest Internet Solutions and Design
		Written by Jason "Palamedes" Ellis (palamedes at rocketmail.com)
		URL:  http://www.randomstringofwords.com/
		Version: 08.07.03.003
		All rights reserved.

		Redistribution and use in source and binary forms, with or without
		modification, are permitted provided that the following conditions are met:
	
		- Redistributions of source code must retain the above copyright notice,
			this list of conditions and the following disclaimer.
		- Redistributions in binary form must reproduce the above copyright notice,
			this list of conditions and the following disclaimer in the
			documentation and/or other materials provided with the distribution.
		- Neither the name of Palaquest Internet Solutions & Design nor the names of its
			contributors may be used to endorse or promote products derived from
			this software without specific prior written permission.
 
		THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
		AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
		IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
		ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
		LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
		CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
		SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
		INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
		CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
		ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
		POSSIBILITY OF SUCH DAMAGE.
 </pre>
*/

	// Define our namespace
	if (typeof rsow == 'undefined') rsow = {};

	// Global tokens
	var token = 1;
	var timeoutToken = 0;

	// Get the query string and location information
	var queryLocation = window.location.toString();
	var queryRequest = queryLocation.indexOf('?') != -1 ? queryLocation.substring(queryLocation.indexOf('?') + 1) : '';

	// Are we using IE?
	function isIE() { return (window.ActiveXObject && !(navigator.userAgent.toLowerCase().indexOf('opera') + 1)); }

	// Are we using IE6 (its different than IE7)
	function isIE6() { return (isIE() && !window.XMLHttpRequest); }

	// Are we using Opera?
	function isOpera() { return navigator.userAgent.toLowerCase().match('opera'); }

	// Are we using Safari?
	function isSafari() { return navigator.userAgent.toLowerCase().match('webkit'); }


	/** Basic console support across browsers ~~ mostly to get .log to work everywhere.
	 * If we have console (safari and firefox with firebug) then use that, otherwise use as an alert.
	 * If we are in opera, then pretend it's a postError.
	 */
    var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
    "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
	if ((isIE() || !window.console || !console.firebug) && !isSafari()) { window.console = {};
    	for (var i = 0; i < names.length; ++i) window.console[names[i]] = (isOpera())?opera.postError:function(a) {alert(a);}
	}

	/** Add Event Listener
	 * We need to make IE play nice as Firefox, Opera and Safar does with regards to addEventListeners.
	 * Firefox does this natively -- Thank you Mozilla.  IE7 still requires attachEvent	 
	 * AddListener does the same thing, but is used by the render function below and requires
	 * an element to be sent along.
	 * And due to IE sucking, we now have to send the element and the arguments.
	 */	
	var listeners = []; 	 
	function addListener(element, event, listener, removable) {
		var listenerNumber = token++;
        var realListener = function(e) {
            if (!e) e = window.event;
            var target = null;
            if (e.target) target = e.target;
            else if (e.srcElement) target = e.srcElement;
            listener(e, target, listenerNumber);
        };
		if (removable) listeners[listenerNumber] = realListener;

        if (element.attachEvent) return element.attachEvent("on" + event, realListener);
        else return element.addEventListener(event, realListener, false);
	}
	function removeListener(element, event, listener) {
	    if (element.attachEvent) element.detachEvent("on" + event, listeners[listener]);
        else element.removeEventListener(event, listeners[listener], false);
		delete listeners[listener];
	}

	/** The all mighty $ function
	 * This function replaces the often used "document.getElementById('fooo')" with "$('fooo')"
	 * an array of elements may also be passed, and returned.	 
	 */	 	
	function $() {
		var elements = new Array();
		for (var i = 0; i < arguments.length; i++) {
			var element = arguments[i];
			if (typeof element == 'string')
				element = document.getElementById(element);
			if (arguments.length == 1)
				return element;
			elements.push(element);
		}
		return elements;
	}	
	  	
	/** object Position
	 * I'd love to prototype this to the Object but since IE doesn't support that we have to do it this way
	 * _(obj) returns the TRUE left,top position of the element
	 */	 	 	
	function _() {
		var obj = arguments[0];
		var curleft = curtop = 0;
		if (obj.offsetParent) {
			curleft = obj.offsetLeft
			curtop = obj.offsetTop
			while (obj = obj.offsetParent) {
				curleft += obj.offsetLeft
				curtop += obj.offsetTop
			}
		}
		return [curleft,curtop];
	}	

	/** set Cookie
	 * This function sets a cookie with only the NAME and VALUE fields being required.
	 * Expires is a number of days - if no days are set then it will expire at end of session
	 * Path, Domain and Secure are optional
	 */		 		 		 		
	function setCookie (name, value, expires, path, domain, secure) {
		// create a date and set it in milliseconds
		var today = new Date();
		today.setTime( today.getTime() );
		// set expiration date at 1 year from today
		if (expires) {
			expiresDay = new Date( today.getTime() + (expires * 1000 * 60 * 60 * 24) );
		}
		// set the cookie
		document.cookie = name + "=" + escape( value ) +
			(( expires ) ? ";expires=" + expiresDay.toGMTString() : "" ) +
			(( path ) ? ";path=" + path : "" ) +
			(( domain ) ? ";domain=" + domain : "" ) +
			(( secure ) ? ";secure" : "" );	  
	}
	
	/** get Cookie
	 * This function returns the value of a set cookie based on the NAME provided.
	 * S = start, L = Length, E = End... but you don't need to know that.
	 */		 		 		
	function getCookie (name) {
		var s = document.cookie.indexOf(name + '=');
		var l = s + name.length + 1;
		if ((( !s ) && ( name != document.cookie.substring(0, name.length) )) || ( s == -1 )) {
			return null;
		}
		var e = document.cookie.indexOf(";",l);
		if (e == -1) {
			e = document.cookie.length;
		} 
		return unescape(document.cookie.substring(l,e));
	}
	
	/** Get Elements By Class Name
	 * Why on Earth isn't this in the DOM already?! (actually it is in FF3)
	 * 
	 * var results = getElementsByClassName('classname', 'tags', 'parent');
	 * You can leave off tags or parent if you want
	 */		 		
	function getElementsByClassName (cN, t, p) {
		var tC = new RegExp("(^|\\s)" + cN + "(\\s|$)");
		var t = t || "*"; var p = p || document;
		var e = (t == '*' && p.all)? p.all : p.getElementsByTagName(t);
		var res = []; var c; var l = e.length;
		for(var i = 0; i < l; i++) {
			c = e[i]; if (tC.test(c.className)) res.push(c);
		}
		return res;
	}
		
		
    /** (private function) render
	 * @TODO: Write docs here to talk folks how awesome this is.. 
     *
     * @param {Object} tree
     * @return {DOMElement} Object tree transformed into DOM tree
     */

	function render(tree) {
        for (var e in tree) {
            var nE = document.createElement(e);
            if (tree[e].attributes) for (var a in tree[e].attributes) {
                var val = tree[e].attributes[a];
                if (a == 'class')   nE.className = val;
                else if (a == 'id') nE.id = val;
                else if (a == 'display') nE.style.display = val;
				else if (a == 'width') nE.style.width = val;
				else if (a == 'height') nE.style.height = val;
				else if (a == 'top') nE.style.top = val;
				else if (a == 'left') nE.style.left = val;
				else if (a != 'toJSONString') nE.setAttribute(a, val);
            }
            if (tree[e].listeners) for (var l in tree[e].listeners) addListener(nE, l, tree[e].listeners[l]);
            if (tree[e].childNodes) for (var c = 0; c < tree[e].childNodes.length; c++) {
                var val = tree[e].childNodes[c];
                if (typeof val == 'string') nE.appendChild(document.createTextNode(val));
                else nE.appendChild(render(val));
            }
            return nE;
        }
    }

    /** parseStr
     * Extract from this string into an array the sent parameters
     *
     * @param {String} string A URL style parameter list
     * @return {Array} An array of parameters
     */
    function parseStr (string) {
        var results = {};
        var parse = window.decodeURIComponent || window.unescape;
        if (string) {
            string.replace(/([^=&]*?)((?:\[\])?)(?:=([^&]*))?(?=&|$)/g,
                function ($, key, ourArray, val) {
                    if (key == '') { return; }
                    key = parse(key);
                    val = parse(val);
                    if (ourArray) {
                        if (typeof results[key] == 'object') {
                            results[key][results[key].length] = val;
                        } else {
                            results[key] = [val];
                        }
                    } else {
                        results[key] = val;
                    }
                });
            }
        return results;
	}

	// @TODO This next function is still "under construction"
	/** getStyle
	 * Get the computed style for a given element and property.
	 * Ever had object.style.property return nothing when you knew it was set?  I have.
	 * This function will get the computed style no matter what.
	 * @TODO Continue to add the switch properties both pre and post
	 */
	function getStyle(element,property) {
		var e = $(element);
		// IE refers to some things differently.. Of course
		if (isIE() || isOpera()) {
			switch (property) {
				case 'background-color': property = 'backgroundColor'; break;
				case 'font-family': property = 'fontFamily'; break;
				case 'font-size': property = 'fontSize'; break;
			}
		}
		var res = null;
		if (e.currentStyle) res = e.currentStyle[property];
		else if (window.getComputedStyle) res = document.defaultView.getComputedStyle(e,null).getPropertyValue(property);
		// Some results will require conversion
		if (property == 'background-color') {
			res = rgb2hex(res);
		}	
		return res;
	}

	// @TODO This next function is still "under construction"
	/** $S() is a style finder in the same manner that $() finds by ID.
	 * it will return all objects that are defined and let you edit their cssRules directly but
	 * Its still VERY buggy.. I hope to one day be able to say something akin to;
	 * $S('myCSSClassFoo').style.background-color = "#FFF"; and have it change the style across the board
	 * instead of attaching each element by class name.
	 *
	 * For now, don't use this its just not very good yet -- sorry.
	 */
	function $S(ss) {
		var r = null;
		var s = document.styleSheets;
		for (var i = 0; i <= s.length-1 && r == null; i++) {
			var rs = (isIE())?s[i].rules:s[i].cssRules;
			for (var j = 0; j <= rs.length-1 && r == null; j++) {
				if (rs[j].selectorText == ss) r = rs[j];
			}
		}
		return r;
	}

	// Modulus anyone? (there was a reason % wasnt working but I forget why)
	function mod(div,base) {
		return Math.round(div - (Math.floor(div/base)*base));
	}

	function hex2dec (hex) {
		hex = hex.replace(/#/,'');
		return parseInt(hex,16);
	}
	function dec2hex (dec) {
		var ret = Number(dec).toString(16);
		return ((ret == 0)?'00':ret);
	}

	// Convert #000000 to {'R':rrr,'G':ggg,'B':bbb}
	function hex2rgb (hex) {
		hex = hex.replace(/#/,'');
		return {'R':hex2dec(hex.substr(0,2)),'G':hex2dec(hex.substr(2,2)),'B':hex2dec(hex.substr(4,2))};
	}

	/** Convert RGB to Hex
	 * This function allows for the conversion of several different formats to hex
	 * A string in the form of "rgb(123, 123, 123)" as returned by getStyle in FF and Safari
	 * An array in the form of [123, 123, 123]
	 * An object in the form of {'R':rrr,'G':ggg,'B':bbb}
	 */ 
	function rgb2hex (rgb) {
		if (typeof rgb == 'string') {
			rgb = rgb.replace('rgb(','').replace(')','').replace(' ','');;
			rgb = rgb.split(',');
		}
		if (rgb[2]) rgb = {'R':parseInt(rgb[0]), 'G':parseInt(rgb[1]), 'B':parseInt(rgb[2])};
		var r = Number(rgb.R).toString(16);
		var g = Number(rgb.G).toString(16);
		var b = Number(rgb.B).toString(16);
		return '#' + ((r.length<2)?'0'+r:r) + ((g.length<2)?'0'+g:g) + ((b.length<2)?'0'+b:b);
	}

	/** Convert HSV/HSB to RGB {'H':hh,'S':ss,'V':vv} to {'R':rr,'G':gg,'B':bb}
	 * 0-360, 0-100, 0-100 to 0-255,0-255,0-255
	 */
	function hsv2rgb (hsv) {
		var h = hsv.H / 360;
		var s = hsv.S / 100;
		var v = hsv.V / 100;
		if (s == 0) {
			v = Math.round(v*255);
			return {'R':v, 'G':v, 'B':v};
		}
		if (h >= 1) h = 0;
		h = h * 6;
		f = h - Math.floor(h);
		a = Math.round(255*v*(1-s));
		b = Math.round(255*v*(1-(s*f)));
		c = Math.round(255*v*(1-(s*(1-f))));
		v = Math.round(255*v);

		switch(Math.floor(h)) {
			case 0: return {'R':v?v:0, 'G':c?c:0, 'B':a?a:0};
			case 1: return {'R':b?b:0, 'G':v?v:0, 'B':a?a:0};
			case 2: return {'R':a?a:0, 'G':v?v:0, 'B':c?c:0};
			case 3: return {'R':a?a:0, 'G':b?b:0, 'B':v?v:0};
			case 4: return {'R':c?c:0, 'G':a?a:0, 'B':v?v:0};
			case 5: return {'R':v?v:0, 'G':a?a:0, 'B':b?b:0};
		}
	}
	/** Convert RGB to HSV/HSB {'R':rr,'G':gg,'B':bb} to {'H':hh,'S':ss,'V':vv}
	 * 0-255,0-255,0-255 to 0-360, 0-100, 0-100
	 */
	function rgb2hsv (rgb) {
		var hsv = {'H':new Number(0), 'S':new Number(0), 'V':new Number(0)};
		var min = (Math.min(rgb.R, rgb.G, rgb.B));
		var max = (Math.max(rgb.R, rgb.G, rgb.B));
		var delta = (max - min);
		if (max != 0) {
			hsv.S = (delta / max);
			hsv.V = (max / 255);
			if (delta != 0) {
				switch (Number(max)) {
					case Number(rgb.R) : hsv.H = (rgb.G - rgb.B) / delta; break;
					case Number(rgb.G) : hsv.H = 2 + ((rgb.B - rgb.R) / delta); break;
					case Number(rgb.B) : hsv.H = 4 + ((rgb.R - rgb.G) / delta); break;
				}
			}
			hsv.H = hsv.H * 60;
			hsv.H = (hsv.H < 0)? hsv.H + 360 : hsv.H;
		}
		hsv.S = hsv.S * 100;
		hsv.V = hsv.V * 100;
		return hsv;
	}




// Until I write my own drager use this

	/** This is a window drag system that works fairly well.
	 * Not even sure where I got it to give credit.. (sorry guys)
	 */	 	
	var Drag = {
	
		obj : null,
	
		init : function(o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper) {
			o.onmousedown	= Drag.start;
	
			o.hmode			= bSwapHorzRef ? false : true ;
			o.vmode			= bSwapVertRef ? false : true ;
	
			o.root = oRoot && oRoot != null ? oRoot : o ;
	
			if (o.hmode  && isNaN(parseInt(o.root.style.left  ))) o.root.style.left   = "0px";
			if (o.vmode  && isNaN(parseInt(o.root.style.top   ))) o.root.style.top    = "0px";
			if (!o.hmode && isNaN(parseInt(o.root.style.right ))) o.root.style.right  = "0px";
			if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) o.root.style.bottom = "0px";
	
			o.minX	= typeof minX != 'undefined' ? minX : null;
			o.minY	= typeof minY != 'undefined' ? minY : null;
			o.maxX	= typeof maxX != 'undefined' ? maxX : null;
			o.maxY	= typeof maxY != 'undefined' ? maxY : null;
	
			o.xMapper = fXMapper ? fXMapper : null;
			o.yMapper = fYMapper ? fYMapper : null;
	
			o.root.onDragStart	= new Function();
			o.root.onDragEnd	= new Function();
			o.root.onDrag		= new Function();
		},
	
		start : function(e) {
			var o = Drag.obj = this;
			e = Drag.fixE(e);
			var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
			var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
			o.root.onDragStart(x, y);
	
			o.lastMouseX	= e.clientX;
			o.lastMouseY	= e.clientY;
	
			if (o.hmode) {
				if (o.minX != null)	o.minMouseX	= e.clientX - x + o.minX;
				if (o.maxX != null)	o.maxMouseX	= o.minMouseX + o.maxX - o.minX;
			} else {
				if (o.minX != null) o.maxMouseX = -o.minX + e.clientX + x;
				if (o.maxX != null) o.minMouseX = -o.maxX + e.clientX + x;
			}
	
			if (o.vmode) {
				if (o.minY != null)	o.minMouseY	= e.clientY - y + o.minY;
				if (o.maxY != null)	o.maxMouseY	= o.minMouseY + o.maxY - o.minY;
			} else {
				if (o.minY != null) o.maxMouseY = -o.minY + e.clientY + y;
				if (o.maxY != null) o.minMouseY = -o.maxY + e.clientY + y;
			}
	
			document.onmousemove	= Drag.drag;
			document.onmouseup		= Drag.end;
	
			return false;
		},
	
		drag : function(e) {
			e = Drag.fixE(e);
			var o = Drag.obj;
	
			var ey	= e.clientY;
			var ex	= e.clientX;
			var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
			var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
			var nx, ny;
	
			if (o.minX != null) ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX);
			if (o.maxX != null) ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX);
			if (o.minY != null) ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY);
			if (o.maxY != null) ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY);
	
			nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
			ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));
	
			if (o.xMapper)		nx = o.xMapper(y)
			else if (o.yMapper)	ny = o.yMapper(x)
	
			Drag.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
			Drag.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
			Drag.obj.lastMouseX	= ex;
			Drag.obj.lastMouseY	= ey;
	
			Drag.obj.root.onDrag(nx, ny);
			return false;
		},
	
		end : function() {
			document.onmousemove = null;
			document.onmouseup   = null;
			Drag.obj.root.onDragEnd(	parseInt(Drag.obj.root.style[Drag.obj.hmode ? "left" : "right"]), 
										parseInt(Drag.obj.root.style[Drag.obj.vmode ? "top" : "bottom"]));
			Drag.obj = null;
		},
	
		fixE : function(e) {
			if (typeof e == 'undefined') e = window.event;
			if (typeof e.layerX == 'undefined') e.layerX = e.offsetX;
			if (typeof e.layerY == 'undefined') e.layerY = e.offsetY;
			return e;
		}
	};	
	
	
