var Activa = {};

//DOM Getters
/**
 * did - Shorcut for getElementById
 * @param	id		The element id you are looking for
 * @return			Element with matching id
 */
function did(id) {
	return document.getElementById(id);
}
/**
 * dbn - Shorcut for getElementsByName
 * @param	name	The element name you are looking for
 * @param	parent	optional;The parent item to search within
 * @return			Collection of elements
 */
function dbn(name, parent) {
	return (parent || document).getElementsByName(name);
}
/**
 * dbt - Shorcut for getElementsByTagName
 * @param	tag		The element tagName you are looking for
 * @param	parent	optional;The parent item to search within
 * @return 			Collection of elements
 */
function dbt(tag, parent) {
	return (parent || document).getElementsByTagName(tag);
}
/**
 * dbc - Shortcut for getElementsByClassName
 * @param	className	The className you are looking for
 * @param	parent		optional;The parent item to search within
 * @return				Array of elements
 */
function dbc(className, parent) {
	className = 'dev_'+className;
	//use default provided by browser if it exists otherwise use our implementation
	if ( document.getElementsByClassName ) {
		return (parent || document).getElementsByClassName(className);
	}
	var nodes = Activa.toArray(dbt('*', parent)), elms = [];
	nodes.forEach(function(node){
		if ( Activa.DOM.hasClass(node, className) ) {
			elms.push(node);
		}
	});
	return elms;
}

Activa.Exception = function activaException(message, file, line, trace, url) {
	this.message = message;
	this.file = file;
	this.line = line;
	this.trace = trace;
	this.url = url;
};

Activa.urlencode = function urlencode(string) {
	return encodeURIComponent(string).replace(/%20/g, "+");
};

Activa.NewWindow = function NewWindow(mypage, myname, w, h, scroll) {
	var winl = (screen.width - w) / 2;
	var wint = (screen.height - h) / 2;
	winprops = 'height='+h+',width='+w+',top='+wint+',left='+winl+',scrollbars='+scroll+',resizable=yes'
	win = window.open(mypage, myname, winprops)
	if ( parseInt(navigator.appVersion) >= 4 ) { 
		win.window.focus(); 
	}
	return win;
};

//Cookies
Activa.createCookie = function createCookie(name, value, days) {
	if ( days ) {
		var date = new Date();
		date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
};

Activa.getCookie = function getCookie(name) {
	var start = document.cookie.indexOf(name + "=");
	var len = start + name.length + 1;
	if ( start < 0 ) {
		return null;
	}
	if ( start == 1 ) { 
		return null; 
	}
	var end = document.cookie.indexOf(';', len);
	if ( end == -1 ) { 
		end = document.cookie.length; 
	}
	return unescape( document.cookie.substring(len, end) );
};

Activa.padString = function padString(str, len) {
	if ( typeof(str) != 'string' ) {
		str = String(str);
	}
	while ( str.length < len ) {
		str = '0'+str;
	}
	return str;
};

Activa.gaTrack = function gaTrack(str) {
	try {
		if ( typeof(pageTracker) != 'undefined' ) {
			pageTracker._trackPageview(str);
		} else if ( typeof(urchinTracker) != 'undefined' ) {
			urchinTracker(str);
		}
	} catch ( err ) {}
}

/**
 * Activa.fixEvent - Returns an event object with common properties/methods normalized for easier cross browser usage.
 * @param e		object	optional;Event object to normalize
 * @return		object	Event object after normalization
 */
Activa.fixEvent = function fixEvent(e) {
	var evnt = e || window.event;
	if ( !evnt ) { 
		return null; 
	}
	if ( !evnt.target ) { 
		evnt.target = evnt.srcElement; 
	}
	evnt.preventDefault = (evnt.preventDefault)? evnt.preventDefault : function() { this.returnValue = false; };
	evnt.stopPropagation = (evnt.stopPropagation)? evnt.stopPropagation : function() { this.cancelBubble = true; };
	var scroll = Activa.Dimensions.getScrollXY();
	if ( e.pageX === undefined ) { 
		e.pageX = e.clientX + scroll.x; 
	}
	if ( e.pageY === undefined ) { 
		e.pageY = e.clientY + scroll.y; 
	}
	if ( typeof e.layerX != 'number' ) { 
		e.layerX = e.offsetX; 
	}
	if ( typeof e.layerY != 'number' ) { 
		e.layerY = e.offsetY; 
	}
	if ( !e.relatedTarget ) {
		switch ( e.type ) {
			case 'mouseover':
			case 'mouseenter':
				e.relatedTarget = e.fromElement;
				break;
			case 'mouseleave':
			case 'mouseout':
				e.relatedTarget = e.toElement;
				break;
		}
	};
	return evnt;
};

Activa.createDelegate = function createDelegate(oObject, sMethodName, data) {
	return function () {
		if ( data ) {
			var args = [];
			var i = 0;
			while ( arguments[i] ) {
				args[i] = arguments[i];
				i++;
			}
			args[i] = data;
			return oObject[sMethodName].apply(oObject, args);
		}
		return oObject[sMethodName].apply(oObject, arguments);
	};
};

Activa.registerEvent = function registerEvent(elem, event, callback, capture) {
	capture = Boolean(capture);
	elem = Activa.DOM.check(elem);
	
	if ( event == 'allchange' ) {
		Activa.registerEvent(elem, 'change', callback, capture);
		Activa.registerEvent(elem, 'click', callback, capture);
		Activa.registerEvent(elem, 'keyup', callback, capture);
		return;
	}
	
	function wrapCallback(e) { 
		callback(Activa.fixEvent(e));
	};
	
	if ( elem.addEventListener ) {
		elem.addEventListener(event, wrapCallback, capture);
	} else {
		elem.attachEvent('on'+event, wrapCallback); 
	}
	
	return new Activa.registeredEvent(elem, event, wrapCallback, capture);
};

Activa.unregisterEvent = function unregisterEvent(elem, event, callback, capture) {
	capture = Boolean(capture);
	elem = Activa.DOM.check(elem);
	
	if ( event == 'allchange' ) {
		Activa.unregisterEvent(elem, 'change', callback, capture);
		Activa.unregisterEvent(elem, 'click', callback, capture);
		Activa.unregisterEvent(elem, 'keyup', callback, capture);
		return;
	}
	
	if ( elem.removeEventListener ) {
		elem.removeEventListener(event, callback, capture);
	} else {
		elem.detachEvent('on'+event, callback); 
	}
}

Activa.registeredEvent = function registeredEvent(elem, event, callback, capture) {
	this.elem = elem;
	this.event = event;
	this.callback = callback;
	this.capture = capture;
	
	this.unregister = function() {
		Activa.unregisterEvent(this.elem, this.event, this.callback, this.capture);
	}
};

/**
 * Activa.toArray - Shortcut for Array.prototype.slice.call(obj, idx)
 * @param	obj		mixed		Object to call array.slice on
 * @param	start	integer		Index at which to begin slicing
 * @param	end		integer		Index at which to end slicing
 * @return			array		New array containing values from the idx to the end of the obj
 *		Note: Useful for transforming arguments object and collections into regular arrays
 *		ex.	function(){ var args = Activa.toArray(arguments); alert(args instanceof Array); }
 */

Activa.toArray = function toArray(obj, start, end) {
	var ret = obj;
	if ( window.ActiveXObject ) {
		if ( typeof obj.length == 'undefined' ) { 
			obj.length = Activa.getLength(obj); 
		}
		ret = Array.prototype.map.call(obj, function mapCall(item) { return item; });
	}
	var args = [(start || 0)];
	if ( end && !isNaN(Number(end)) ) {
		args.push(Number(end));
	}
	return Array.prototype.slice.apply(ret, args);
};


/**
 * getLength - Finds the total number of all non-function properties owned by the object (see hasOwnProperty)
 * @param obj    object    The object whose properties to count.
 * @return       int       The length of the object or 0 if obj was invalid
 */
Activa.getLength = function getLength(obj) {
    if ( !obj ) {
    	return 0; 
    }
    var i=0;
    for ( var key in obj ) {
        if ( obj.hasOwnProperty(key) && typeof obj[key] !='function' ) { 
        	i++; 
        }
    }
    return i;
};


//Prototypes
String.prototype.ltrim = function() {
	return this.replace(/^\s*/, '');
};

String.prototype.rtrim = function() {
	return this.replace(/\s*$/, '');
};

String.prototype.trim = function() {
	return this.rtrim().ltrim();
};

/**
 * Array and object functions
 */
/**
 * Array methods indexOf, forEach, map, filter
 * 		Added for browsers without native support
 */
Array.prototype.indexOf = (function() {
	var fn;
	if ( typeof Array.prototype.indexOf == 'function' ) {
		fn = Array.prototype.indexOf; 
	} else {
		fn = function indexOf(obj, start){
			var len = this.length;
			start = Number(start) || 0;
			start = (start < 0) ? Math.ceil(start) : Math.floor(start);
			if ( start < 0 ) { 
				start+= len; 
			}
			for ( ; start < len; start++ ) {
				if ( start in this && this[start] === obj ) { 
					return start; 
				}
			}
			return -1;
		}
	}
	var indexOf = null;
	return fn;
})();
Array.prototype.forEach = (function() {
	var fn;
	if ( typeof Array.prototype.forEach == 'function' ) {
		fn = Array.prototype.forEach;
	} else {
		fn = function forEach(fn /*, bind*/){
			var len = this.length;
			if ( typeof(fn) != 'function' ) { 
				throw new TypeError(); 
			}
			var bind = arguments[1];
			for ( var i = 0; i < len; i++ ) {
				if ( i in this ) { 
					fn.call(bind, this[i], i, this); 
				}
			}
		};
	}
	var forEach = null;
	return fn;
})();
Array.prototype.map = (function() {
	var fn;
	if ( typeof Array.prototype.map == 'function' ) {
		fn = Array.prototype.map;
	} else {
		fn = function map(fn /*, bind*/){
			var len = this.length;
			if ( typeof(fn) != 'function' ) { 
				throw new TypeError(); 
			}
			var ret = [], bind = arguments[1];
			for ( var i = 0; i < len; i++ ) {
				if ( i in this ) { 
					ret[i] = fn.call(bind, this[i], i, this); 
				}
			}
			return ret;
		};
	}
	var map = null;
	return fn;
})();
Array.prototype.filter = (function() {
	var fn;
	if ( typeof Array.prototype.filter == 'function' ) {
		fn = Array.prototype.filter;
	} else {
		fn = function filter(fn /*, bind*/){
			var len = this.length;
			if ( typeof(fn) != 'function' ) { 
				throw new TypeError(); 
			}
			var ret = [], bind = arguments[1], val=null;
			for ( var i = 0; i < len; i++ ) {
				if ( i in this ) {
					val = this[i];
					if ( fn.call(bind,val,i,this) ) { 
						ret.push(val); 
					}
				}
			}
			return ret;
		};
	}
	var filter = null;
	return fn;
})();
Array.prototype.every = (function() {
	var fn;
	if ( typeof Array.prototype.every == 'function' ) {
		fn = Array.prototype.every;
	} else {
		fn = function every(fn /*, bind*/) {
			return this.filter.apply(this, Activa.toArray(arguments)).length === this.length;
		};
	}
	var every = null;
	return fn;
})();
Array.prototype.some = (function() {
	var fn;
	if ( typeof Array.prototype.some == 'function' ) {
		fn = Array.prototype.some;
	} else {
		fn = function some(fn /*, bind*/) {
			return this.filter.apply(this, Activa.toArray(arguments)).length > 0;
		};
	}
	var some = null;
	return fn;
})();

Array.prototype.inArray = function(targ) {
	if ( this.indexOf(targ) != -1 ) {
		return true;
	} else {
		return false;
	}
};

Array.prototype.remove = function(pos) {
	if ( pos != -1 ) {
		this.splice(pos, 1);
	}	
};

Date.prototype.format = function(format) {
	var res = '';
	for ( var i = 0; i < format.length; i++ ) {
		switch ( format.charAt(i) ) {
			case 'm':
				res += Activa.padString(this.getMonth() + 1, 2);
				break;
				
			case 'd':
				res += Activa.padString(this.getDate(), 2);
				break;
				
			case 'Y':
				res += this.getFullYear();
				break;
				
			case 'H':
				res += Activa.padString(this.getHours(), 2);
				break;
				
			case 'i':
				res += Activa.padString(this.getMinutes(), 2);
				break;
				
				
			default:
				res += format.charAt(i);
				break;
				
		}
	}
	return res;
};

Function.prototype.bind = function() {
	var fn = this;
	var args = Activa.toArray(arguments);
	var reference = args.shift();
	return function binded() {
		var arglist = args.concat(Activa.toArray(arguments));
		return fn.apply(reference, arglist);
	};
};


/**
 * Create a new Class.
 * 
 * Note:
 * 	Two things are available inside methods of the instances of the class:
 * 		parent	- Gives access to the instance of the parent object. ( use: this.parent )
 * 		root	- Call the parent's identically named method. ( use: this.root(arg1, arg2, ...) ) 
 * 
 * Usage:
 *	var className = new Class({
 *		[Extends: constructor,]
 *		[property: value..,]
 *		[method: function [funcname]() { .... }, ...]
 *	});
 * 
 * @param object params Properties and methods for the class as an object literal
 * @return Class		The constructor for the new class
 */
Activa.Class = function Class(params) {
	params = (params instanceof Function) ? {init: params} : params;
	
	var obj = function construct() {
		Activa.Class._clean(this);
		var simple = arguments.callee.simple;
		if ( simple ) {
			delete arguments.callee.simple;
		}
		return (this.init && !simple) ? this.init.apply(this, arguments) : this;
	}
	
	for ( var k in this ) {
		obj[k] = this[k];
	}
	obj._implement(params);
	obj.constructor = Class;
	obj.prototype.constructor = obj;
	return obj;
}

/**
 * Add static properties/methods to the class. They are accessible via the class name.
 * 
 * Usage:
 * 	myClass.statics({
 * 		[property: value..,]
 * 		[method: function [funcname]() { .... }, ...]
 *	});
 * 
 * @param object args	Static properties and methods for the class as an object literal 
 * @return Class		The class constructor that was extended
 */
Activa.Class.prototype.statics = function statics(args) {
	for ( var k in args ) {
		this[k] = args[k];
	}
	return this;
}

/**
 * Add properties/methods to instances of the class. Instances that already exist will gain
 * these as well.
 * 
 * Usage:
 * --Add a specific property/method from an object
 * 	myClass._implement(
 *	key,
 *	{
 * 		[property: value..,]
 * 		[method: function [funcname]() { .... }, ...]
 * 	});
 * 
 * or:
 * --Add all the properties/methods from an object
 * 	myClass._implement({
 * 		[property: value..,]
 * 		[method: function [funcname]() { .... }, ...]
 * 	});
 * 
 * @param string/object key		Key name or object depending on what to add (see usage above)
 * @param object value			Object to to get the property/method from when using key (see usage above)
 * @return Class				The class constructor that implemented the new properties/methods
 */
Activa.Class.prototype._implement = function _implement(key, value) {
	if ( typeof key == 'object' && !(key instanceof Array) ) {
		if ( 'Extends' in key ) {
			var parent = key.Extends;
			parent.simple = true;
			this.prototype = new parent();
			delete key.Extends;
		}
		for ( var k in key ) {
			this._implement(k, key[k]);
		}
		return this;
	}
	if ( typeof value == 'function' ) {
		var parentMethod = (key in this.prototype) ? this.prototype[key] : null;
		this.prototype[key] = function method() {
			var _root = null, _oldroot = null;
			if ( this.root ) {
				_oldroot = this.root;
				delete this.root;
			}
			this.root = _root = (_root) ? _root :
				function root() {
					if ( parentMethod == null ) {
						throw new ReferenceError("The method '"+key+"' does not exist in the parent class.");
					}
					return parentMethod.apply(this, arguments);
				}.bind(this);
			var ret = value.apply(this, arguments);
			delete this.root;
			if ( _oldroot ) {
				this.root = _oldroot;
				delete _oldroot;
			}
			return ret;
		}
		return this;
	} else if ( typeof value == 'object' && key in this.prototype ) {
		var obj = this.prototype[key];
		this.prototype[key] = value;
		for ( var k in obj ) {
			this.prototype[key][k] = obj[k];
		}
		return this;
	}
	this.prototype[key] = value;
	return this;
}
/**
 * Clean a new instance of a class so they do not share the prototype properties.
 * 
 * @param object object		The class instance to clean
 * @return object			The clean instance of the class
 */
Activa.Class._clean = function(object){
	function destruct(obj){
		var ret, type = (obj instanceof Array ? 'array' : typeof obj);
		switch ( type ) {
			case 'object':
				ret = {};
				for ( var k in obj ) {
					ret[k] = destruct(obj[k]);
				}
				break;
			case 'array':
				ret = [];
				for ( var i = 0, z = obj.length; i < z; i++ ) {
					ret[i] = destruct(obj[i]);
				}
				break;
			default: return obj;
		}
		return ret;
	}
	
	for ( var key in object ) {
		delete object[key];
		
		var type = (object[key] instanceof Array ? 'array' : typeof object[key]);
		switch ( type ) {
			case 'object':
				if ( object[key] === null ) {
					object[key] = null;
					continue;
				}
				var n = function(){};
				n.prototype = object[key];
				var o = new n();
				object[key] = Activa.Class._clean(o);
				break;
			case 'array':
				object[key] = destruct(object[key]);
				break;
		}
	}
	return object;
};

Activa.DOM = {
	/**
	 * next - Returns the next non-whitespace sibling element
	 * @param	el	object	Element node from which to start
	 * @return		object	Next non-whitespace child element
	 */
	next: function next(el) {
		if ( !el || !el.nextSibling ) { 
			return null; 
		}
		el = el.nextSibling;
		return (el.nodeType == 1) ? el : this.next(el);
	},
	/**
	 * previous - Returns the previous non-whitespace sibling element
	 * @param	el	object	Element node from which to start
	 * @return		object	Previous non-whitespace child element
	 */
	previous: function previous(el) {
		if ( !el || !el.previousSibling ) { 
			return null; 
		}
		el = el.previousSibling;
		return (el.nodeType==1) ? el : this.previous(el);
	},
	/**
	 * first - Returns the first non-whitespace child element
	 * @param	el	object	Parent element node
	 * @return		object	First non-whitespace child element
	 */
	first: function first(el) {
		if ( !el || !el.firstChild ) { 
			return null; 
		}
		el = el.firstChild;
		return (el.nodeType==1) ? el : this.next(el);
	},
	/**
	 * last - Returns the last non-whitespace child element
	 * @param	el	object	Parent element node
	 * @return		object	Last non-whitespace child element
	 */
	last: function last(el) {
		if ( !el || !el.lastChild ) { 
			return null; 
		}
		el = el.lastChild;
		return (el.nodeType==1) ? el : this.previous(el);
	},
	/**
	 * owner - Returns the parent of the element
	 * @param	el	object	Element from which to retrieve the parent
	 * @return		object	Parent node of the element
	 */
	owner: function owner(el){
		if ( !el || !el.parentNode ) { 
			return null; 
		}
		el = el.parentNode;
		return (el.nodeType == 1) ? el : this.owner(el);
	},
	/**
	 * contains - Returns whether the given node is contained with the element
	 * @param el object Element in which to check
	 * @param node object Element to check for
	 * @return boolean True if node is a child of el, false if not
	 */
	contains: function contains(el, node){
	  return el.contains ?
	    el != node && el.contains(node) :
	    !!(el.compareDocumentPosition(node) & 16);
	},
	/**
	 * hasClass - Returns true if the element has the given class applied
	 * @param	obj			Element on which to check for the class
	 * @param	className	Class to look for on the element
	 * @return				True (if element has the class) / False (if it doesn't)
	 */
	hasClass: function hasClass(obj, className) {
		if ( !obj || className.trim() == '' ) { 
			return false; 
		}
		return (String(obj.className).split(' ').indexOf(className) != -1);
	},
	/**
	 * addClass - Adds a class to an element
	 * @param	obj			Element on which to add the class
	 * @param	className	Class to add to the element
	 * @return				True (if successfully added class) / False (if invalid object or empty classname given)
	 */
	addClass: function addClass(obj, className) {
		if ( !obj || className.trim() == '' || this.hasClass(obj, className) ) { 
			return false; 
		}
		obj.className = String(obj.className).split(' ').concat([className]).join(' ').trim();
		return true;
	},
	/**
	 * removeClass - Removes a class from an element
	 * @param	obj			Element from which to remove the class
	 * @param	className	Class to remove from the element
	 * @return				True (if successfully removed class) / False (if invalid object or empty classname given)
	 */
	removeClass: function removeClass(obj, className) {
		if ( !obj || className.trim() == '' ) { 
			return false; 
		}
		obj.className = String(obj.className).split(' ').filter(function(cls) {
			return (cls != className);
		}).join(' ');
		return true;
	},
	/**
	 * check - Ensures an element object is returned.
	 * @param string el		ID or element object
	 * @return object		Element object
	 */
	check: function check(el) {
		return (typeof el == 'object') ? el : did(el);
	},
	/**
	 * showID - Display a hidden element
	 * @param string id		ID of the element to show
	 */
	showID: function showID(id){
		var el = this.check(id);
		if ( el ) {
			el.style.display = '';
		}
	},
	/**
	 * hideID - Hide an element
	 * @param string id		ID of the element to hide
	 */
	hideID: function hideID(id){
		var el = this.check(id);
		if ( el ) {
			el.style.display = 'none';
		}
	},
	/**
	 * toggleID - Toggle an element to be shown or hidden
	 * @param string id		ID of the element to toggle
	 */
	toggleID: function toggleID(id){
		var el = this.check(id);
		if ( el ) {
			var fn = el.style.display == 'none' ? 'showID' : 'hideID';
			this[fn](el);
		}
	},
	/**
	 * ready - Register callbacks to execute when the DOM is ready
	 * 
	 * Example using the optional object and args parameters:
	 *		-keyword 'this' will be the person object
	 * 		-arg1 and arg2 become 'name' and 'job'
	 * 		-function will log:
	 * 				name: bob job: plummer
	 * 		
	 * 		var person = {'name':'bob','job':'plummer'};
	 * 		domLoader.register(function(arg1, arg2){
	 * 			console.log(arg1+': '+this[arg1]+' arg2: '+this[arg2]);
	 * 		}, person, ['name','job']);
	 * 
	 *  @param fn
	 *  @param obj
	 *  @param args
	 *  @return
	 */
	ready: function ready(fn, obj, args) {
		var rdy = arguments.callee;
		rdy.isReady = rdy.isReady === undefined ? false : rdy.isReady;
		rdy.isBound = rdy.isBound === undefined ? false : rdy.isBound;
		rdy.queue = rdy.queue === undefined ? [] : rdy.queue;
		rdy.binds = rdy.binds === undefined ? [] : rdy.binds;
		rdy.args = rdy.args === undefined ? [] : rdy.args;
		
		rdy.register = function register(fn, obj, args) {
			rdy.checkReady();
			rdy.queue = (rdy.queue instanceof Array) ? rdy.queue : [];
			if ( !fn || typeof fn != 'function' ) {
				return;
			}
			
			var offset = rdy.queue.push(fn) - 1;
			
			obj = obj || null;
			rdy.binds = (rdy.binds instanceof Array) ? rdy.binds : [];
			rdy.binds[offset] = obj;
			
			args = args ? ((args instanceof Array) ? args :  Activa.toArray(args)) : [];
			rdy.args = (rdy.args instanceof Array) ? rdy.args : [];
			rdy.args[offset] = args;
			if ( rdy.isReady ) {
				rdy.exec();
			}
			return rdy;
		}
		
		rdy.checkReady = function checkReady() {
			if ( rdy.isBound ) {
				return;
			}
			rdy.isBound = true;
			//var self = this;
			var events = {'load': window};
			// Mozilla, Opera and Safari
			if ( document.addEventListener ) {
				events['DOMContentLoaded'] = document;
			// IE
			} else if ( document.attachEvent ) {
				events['onreadystatechange'] = document;
				// If IE and not an iframe -- continually check to see if the document is ready
				if ( document.documentElement.doScroll && window == window.top ) {
					(function ieDoScrollTest(){
						if ( rdy.isReady ) {
							return;
						}
						try {
							// If IE is used, use the trick by Diego Perini -- http://javascript.nwbox.com/IEContentLoaded/
							document.documentElement.doScroll("left");
							// and execute any waiting functions
							rdy.isReady = true;
							rdy.exec();
						} catch ( error ) {
							setTimeout(arguments.callee, 0);
							return;
						}
					})();
				}
			}
			var idDoScrollTest = null;
			//Faster Safari detection
			if ( typeof navigator.taintEnabled === 'undefined' ) {
				var timer = window.setInterval(function safariReadyTest() {
					if ( /loaded|complete/.test(document.readyState) ) {
						window.clearInterval(timer);
						if ( rdy.isReady ) {
							return;
						}
						rdy.isReady = true;
						rdy.exec();
					}
				}, 10);
			}
			var safariReadyTest = null;
			for ( var name in events ) {
				if ( !events.hasOwnProperty(name) ) {
					continue;
				}
				(function scopeFix(name, obj) {
					Activa.registerEvent(obj, name, function pageLoadEventWrapper() {
						Activa.unregisterEvent(obj, name, arguments.callee);
						if ( !rdy.isReady ) {
							rdy.isReady = true;
							rdy.exec();
						}
					});
					var pageLoadEventWrapper = null;
				})(name, events[name]);
			}
			var varprotector = null;
		}
		
		rdy.exec = function exec() {
			if ( !rdy.isReady || !(rdy.queue instanceof Array) ) {
				return;
			}
			var fn, obj, args;
			while ( fn = rdy.queue.shift() ) {
				obj = rdy.binds.shift() || window;
				args = rdy.args.shift();
				fn.apply(obj, args);
			}
			
			rdy.queue = [];
			rdy.binds = [];
			rdy.args = [];
		}
		rdy.register(fn, obj, args);
	}
};

/**
 * Activa.rpc
 * 
 * Parameters:
 * path			The destination of the remote file.
 * options		An object that holds optional arguments.
 * 
 * options:
 * input		mixed		Can either be a string or an object to send as POST data
 * errorHandler		function	Set the error handling function.  Defaults to console.error if exists.
 * async		bool		Boolean to set to enable/disable async.  Defaults to false unless a callback is specified.
 * method		string		Sets whether to use POST or GET.  Defaults to POST
 * onLoad		function	Function is called once when a XHR request is loading.
 * onComplete		function	Function is called when XHR is successful.  Has one parameter for responseText.
 * 
 * Usage:
 * new Activa.rpc('testPath', {input: {a:'Testing!'}, onComplete: function(res){ alert(res); }});
 * 
 */
Activa.rpc = new Activa.Class({
	options: {
		input: null,
		errorHandler: null,
		async: false,
		method: 'POST',
		onComplete: function(){},
		onLoad: function(){}
	},
	setOptions: function setOptions(options) {
		options = options || {};
		
		if ( typeof options.onComplete == 'function' && typeof options.async == 'undefined' ) {
			options.async = true;
		}
		
		for ( var k in this.options ) {
			this.options[k] = (k in options) ? options[k] : this.options[k];
		}
	},
	loaded:false,
	init: function init(path, options) {
		this.request = Activa.rpc.xhr();
		this.path = path;
							
		this.setOptions(options);

		this.input = Activa.rpc.objectToString(this.options.input);
		
		this.request.onreadystatechange = Activa.createDelegate(this, 'onReadyStateChange');

		this.request.open(this.options.method.toUpperCase(), 'rpc/'+this.path, this.options.async);
		this.request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		Activa.rpc.instances.push(this);
		this.request.send(this.input);
	},
	onReadyStateChange: function onReadyStateChange() {
		if ( 4 == this.request.readyState && 200 == this.request.status ) {
			Activa.rpc.instances.remove(Activa.rpc.instances.indexOf(this));
			eval("var res="+this.request.responseText);
			if ( res instanceof Activa.Exception ) {
				if ( typeof(this.options.errorHandler) == 'undefined' || this.options.errorHandler == null ) {
					if ( console && console.error && console.log ) {
						console.error("Uncaught exception:");
						console.log(res);
					}
					return;
				} else {
					this.options.errorHandler(res);
					return;
				}
			}
			this.options.onComplete(res);
		} else if ( 4 == this.request.readyState && this.request.status != 0 ) {
			Activa.rpc.instances.remove(Activa.rpc.instances.indexOf(this));
			if ( typeof(this.errHandler) != "undefined" ) {
				this.options.errorHandler(new Activa.Exception("Server returned error code "+this.request.status));
			}
		} else if ( this.request.readyState != 4 && !this.loaded ) {
			this.options.onLoad();
			this.loaded = true;
		}
	},
	abort: function abort() {
		this.request.abort();
		Activa.rpc.instances.remove(Activa.rpc.instances.indexOf(this));
	}
});

Activa.rpc.statics({
	instances:[],
	callWhenReady: function callWhenReady(func) {
		if ( Activa.rpc.instances.length > 0 ) {
			window.setTimeout(function() { Activa.rpc.callWhenReady(func); }, 250);
			return;
		}
		func();
	},
	xhr: function xhr(){
		if ( window.XMLHttpRequest ) {
			return new XMLHttpRequest();
		} else if ( window.ActiveXObject ) {
			try {
				return new ActiveXObject( "Msxml2.XMLHTTP" );
			} catch ( error ) {
				try {
					return new ActiveXObject( "Microsoft.XMLHTTP" );
				} catch ( error2 ) {
					return alert("Fatal Error: No XMLHttp Interface Available");
				}
			}
		} else {
			return alert("Fatal Error: No XMLHttp Interface Available");
		}
	},
	objectToString: function objectToString(input) {
		if ( !input instanceof Object ) {
			return input;
		}
		
		var str = '';
		for ( var prop in input ) {
			if ( input[prop] instanceof Array ) {
				for ( var x = 0; x < input[prop].length; x++ ) {
					str += prop+'[]='+encodeURIComponent(input[prop][x])+'&';
				}
			} else if ( input[prop] instanceof Object ) {
				for ( var prop2 in input[prop] ) {
					if ( input[prop][prop2] instanceof Array ) {					
						for ( var x = 0; x < input[prop][prop2].length; x++ ) {
							str += prop+'['+prop2+'][]='+encodeURIComponent(input[prop][prop2][x])+'&';
						}
					} else if ( !(input[prop][prop2] instanceof Function) ) {
						str += prop+'['+prop2+']='+encodeURIComponent(input[prop][prop2])+'&';
					}
				}
			} else if ( !(input[prop] instanceof Function) ) {
				str += prop+'='+encodeURIComponent(input[prop])+'&';
			}
		}
		return str;
	},
	activaX: function activaX(element, url, input) {
		this.element = Activa.DOM.check(element);
		this.url = url;
		
		var request = Activa.rpc.xhr();
		this.request.onreadystatechange = Activa.createDelegate(this, 'callback');
		this.request.open("GET", this.url, true);
		this.request.send(null);
		
		this.callback = function() {
			this.element.innerHTML = this.request.responseText;
			this.element.innerHTML = this.element.innerHTML; //ie fix
		};		
	}
});

//Dimensions

Activa.Dimensions = {
	/**
	 * getDocSize - Get the size of the current viewable document area
	 * @param	boolean	scrollbar	Whether the width of the scrollbars should be included. ( default: false )
	 * @return	object		The width, height of the current viewable document area in a keyed object ( obj.width obj.height )
	 */
	getDocSize: function getDocSize(scrollbar) {
		scrollbar = scrollbar || false;
		var w = 0, h = 0;
		if ( typeof(window.innerWidth) == 'number' ) {
			//Non-IE
			w = window.innerWidth;
			h = window.innerHeight;
		} else {
			if ( document.compatMode == 'CSS1Compat' ) {
				w = document.documentElement.clientWidth;
				h = document.documentElement.clientHeight;
			} else {
				w = document.body.clientWidth;
				h = document.body.clientHeight;
			}
		}
		if ( !scrollbar ) {
			var sbar = this.getScrollbarWidth();
			w -= sbar.right;
			w = w < 0 ? 0 : w;
			h -= sbar.bottom;
			h = h < 0 ? 0 : h;
		}
		return {'width': w, 'height': h};
	},

	/**
	 * getMaxDocSize - Get the total document size
	 * @param	boolean	scrollbar	Whether the width of the scrollbars should be included. ( default: false )
	 * @return	object				The width, height of the total document size in a keyed object ( obj.width obj.height )
	 */
	getMaxDocSize: function getMaxDocSize(scrollbar) {
		scrollbar = scrollbar || false;
		var w = 0, h = 0, x = 0, y = 0;
		var docsize = this.getDocSize(true), sbar = this.getScrollbarWidth();
		if ( typeof window.scrollMaxY == 'number' ) {
			x = window.scrollMaxX;
			y = window.scrollMaxY;
		} else {
			var scroll = this.getScrollXY();
			x = scroll.x;
			y = scroll.y;
		}
		w = docsize.width + x - (scrollbar ? 0 : sbar.right);
		w = w < 0 ? 0 : w;
		h = docsize.height + y - (scrollbar ? 0 : sbar.bottom);
		h = h < 0 ? 0 : h;
		return {'width':w,'height':h};
	},

	/**
	 * getScrollbarWidth - Get the width of the scrollbars on the right/bottom
	 * @return	object		The width of the scrollbars on the right/bottom of the window if present ( obj.right, obj.bottom )
	 */
	getScrollbarWidth: function getScrollbarWidth() {
		var size = {'right': 0, 'bottom': 0};
		if ( !document || !document.documentElement ) {
			return size;
		}
		var docEl = document.documentElement;
		size['right'] = ((typeof window.innerWidth == 'number') ? window.innerWidth : docEl.offsetWidth) - docEl.clientWidth;
		size['bottom'] = ((typeof window.innerHeight == 'number') ? window.innerHeight : docEl.offsetHeight) - docEl.clientHeight;
		return size;
	},

	/**
	 * getScrollXY - Gets the distance the page has been scrolled vertically and horizontally
	 * @return	object		The x,y distance in a keyed object ( obj.x obj.y )
	 */
	getScrollXY: function getScrollXY() {
		var sX = 0, sY = 0;
		if ( typeof(window.pageYOffset)=='number' ){
			//Non-IE
			sY = window.pageYOffset;
			sX = window.pageXOffset;
		} else if ( document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop) ) {
			//IE 6+ in 'standards mode'
			sY = document.documentElement.scrollTop;
			sX = document.documentElement.scrollLeft;
		} else if ( document.body && (document.body.scrollLeft || document.body.scrollTop) ){
			//IE 6 in 'strict mode' & some other browsers
			sY = document.body.scrollTop;
			sX = document.body.scrollLeft;
		}
		return {'x':sX,'y':sY};
	}
};

/**
 * Create a validaton engine
 * 
 * @param string form_id			ID of the form
 * @param string alert_type		Type of alert to show the user (current values: alert(default), inline)
 * @param string message_box	ID of the element in which to insert the message (only used for inline alerts)
 * @param bool autoload			Whether to automatically register the handler for the submit event (true(default), false(not recommended))
 */
var Validate = new Activa.Class({
	forms: {},
	message: '',
	invalid: '',
	currentForm: null,
	autoload: true,
	init: function initValidate(alert_type, message_box, autoload) {
		this.message_box = message_box || '';
		// inline_each doesn't require a message_box
		if ( ( alert_type && this.message_box ) || alert_type == 'inline_each' || alert_type == 'inline_submit' ) {
			this.alert_type = alert_type;
		} else {
			this.alert_type = 'alert';
		}
		this.autoload = autoload === undefined ? true : Boolean(autoload);
	},
	addForm: function addForm(form) {
		var form_id = '';
		if ( typeof form == 'object' ) {
			form_id = form.id;
			this.forms[form.id] = form;
		} else {
			form_id = form;
			this.forms[form] = new Form(form);
		}
		this.currentForm = form_id;
	},
	validate: function validate(form_id) {
		form_id = form_id || '';
		if ( form_id ) {
			if ( this.alert_type == 'inline_each' || this.alert_type == 'inline_submit' ) {
				return Inline_Validate.validate(this.forms[form_id].rules, new Inline_Validate.Focus());
			} else {
				return this.forms[form_id].validate();
			}
		}
		var errors = false;
		for ( var id in this.forms ) {
			if ( this.alert_type == 'inline_each' || this.alert_type == 'inline_submit' ) {
				if ( !Inline_Validate.validate(this.forms[id].rules) ) {
					errors = true;
				}
			} else {
				if ( !this.forms[id].validate() ) {
					return false;
				}
			}
		}
		if ( this.alert_type == 'inline_each' || this.alert_type == 'inline_submit' ) {
			return !errors;
		}
		return true;
	},
	end: function end() {
		if ( !this.autoload ) {
			return;
		}
		// Add Dynamic Rule Checking, when each field is blur'd
		if ( this.alert_type == 'inline_each' || this.alert_type == 'inline_submit' ) {
		
			var error_on_submit = this.alert_type == 'inline_submit';
			
			for ( var form_id in this.forms ) {
				var form = this.forms[form_id];
				var success_rules = [];
				for ( i = 0; i < form.rules.length; i++ ) {
					var rule = form.rules[i];
					if ( rule instanceof ConditionalSuccess ) {
						success_rules.push(rule);
					}
				}
				(function registerSuccess(cur_form) {
					Activa.registerEvent(did(cur_form.id), 'keyup', function(e) {
						Inline_Validate.chkSuccess(success_rules, error_on_submit);
					});
					Activa.registerEvent(did(cur_form.id), 'submit', function(e) {
						Inline_Validate.chkSuccess(success_rules, error_on_submit);
					});
				}.bind(this))(form);
				for ( var i = 0, z = form.rules.length; i < z; i++ ) {
					(function registerValidate(cur_rule, error_on_submit) {
						if ( cur_rule.field ) {
							var field_id = cur_rule.field.id;
							var instance = Inline_Msg.getInstance(did(field_id), !error_on_submit);
							instance.addRule(cur_rule);
						}
					}.bind(this))(form.rules[i], error_on_submit);
				}
			}
		}
		for ( var id in this.forms ) {
			(function registerSubmit(form_id) {
				Activa.registerEvent(did(form_id), 'submit', function(e) {
					e.stopPropagation();
					if ( !this.validate(form_id) ) {
						e.preventDefault();
						this.message = this.forms[form_id].message;
						this.invalid = this.forms[form_id].invalid;
						return this.notify(this.forms[form_id], this.invalid);
					}
				}.bind(this));
			}.bind(this))(id);
		}
	},
	notify: function notify(form, field) {
		var form = did(form.id);
		if ( this.alert_type == 'inline_each' || this.alert_type == 'inline_submit' ) {
			// Do nothing here
			return;
		}
		if ( this.alert_type == 'inline' ) {
			var msgbox = did(this.message_box);
			if ( msgbox ) {
				msgbox.innerHTML = this.message;
				return false;
			}
		}
		alert(this.message);
		if ( field ) {
			field.focus();
		}
		return false;
	}
});

var RuleContainer = new Activa.Class({
	containerStack: [],
	currentContainer: null,
	addRule: function addRule(rule) {
		var args = Activa.toArray(arguments);
		rule = false;
		args.forEach(function(item) {
			if ( rule === false && (item instanceof Rule || item instanceof RuleContainer) ) {
				rule = item;
			}
		});
		if ( rule === false ) {
			return;
		}
		if ( this.currentContainer instanceof RuleContainer ) {
			this.currentContainer.addRule.apply(this.currentContainer, args);
			return false;
		}
		if ( rule instanceof RuleContainer ) {
			this.containerStack.push(rule);
			this.currentContainer = this.containerStack[this.containerStack.length-1];
		}
		return rule;
	},
	endContainer: function endContainer() {
		
		// if the currentContainer is a RuleContainer and it has a currentContainer end that container
		if ( this.currentContainer instanceof RuleContainer && this.currentContainer.currentContainer instanceof RuleContainer ) {
			this.currentContainer.endContainer();
			return;
		}
		
		this.containerStack.pop();
		if ( this.containerStack.length > 0 ) {
			this.currentContainer = this.containerStack[this.containerStack.length-1];
		} else {
			this.currentContainer = null;
		}
	}
});

/**
 * Define a form to attach validation rules to
 * 
 * @param string form_id		ID of the form
 */
var Form = new Activa.Class({
	Extends: RuleContainer,
	id: '',
	rules: [],
	message: '',
	invalid: '',
	init: function initForm(form_id) {
		this.id = form_id;
		var form = did(this.id);
		if ( !form ) {
			throw new ReferenceError('form_id "' + this.id + '" is not a valid id of a form.');
		}
	},
	addRule: function addRule(rule) {
		rule = this.root.apply(this, arguments);
		if ( !rule ) {
			return;
		}
		rule.setForm(this);
		this.rules.push(rule);
	},
	validate: function validateForm(){
		var rule;
		for ( var i = 0, z = this.rules.length; i < z; i++ ) {
			rule = this.rules[i];
			if ( !rule.validate() ) {
				this.message = rule.message;
				this.invalid = rule.field;
				return false;
			}
		}
		return true;
	}
});

/**
 * Define basic validation rule.
 * 
 * @param string field		ID of field
 * @param string message	Error message to display when validation fails
 * @param string match		Match value (available for rules to use to match a value)
 * @param bool required		Whether the rule is required for the form to successfully validate (true, false(default))
 */
var Rule = new Activa.Class({
	form: null,
	field: '',
	field_id: '',
	message: '',
	match: '',
	required: false,
	init: function initRule(field, message, match, required) {
		var ftmp = null;
		if ( typeof field == 'object' ) {
			if ( field.id ) {
				ftmp = did(field.id);
				this.field_id = field.id;
			} else if ( field.name ) {
				ftmp = dbn(field.name);
				ftmp = ftmp.length ? ftmp[0] : null;
				this.field_id = field.name;
			}
		} else {
			ftmp = did(field);
			this.field_id = field;
		}
		this.field = ftmp;	
		this.message = message || '';
		this.match = match || '';
		this.required = !!(required);
	},
	setForm: function setForm(form) {
		if ( !(form instanceof Form) ) {
			throw new TypeError('form must be a valid Form object');
		}
		this.form = form;
	},
	validate: function validateRule() {
		if ( this.field.type != 'select-multiple' ) {
			if ( !this.required ) {
				if ( this.field.value.trim() == '' ) {
					return true;
				}
			}
			return false;
		}
	}
});

/**
 * Create a container for conditional rules
 * 
 * @param string field
 */
var Conditional = new Activa.Class({
	Extends: RuleContainer,
	form: '',
	rules: {},
	elses: [],
	message: '',
	condField: '',
	field: '',
	field_id: '',
	init: function initConditional(field) {
		var ftmp = null;
		if ( typeof field == 'object' ) {
			if ( field.id ) {
				ftmp = did(field.id);
				this.field_id = field.id;
			} else if ( field.name ) {
				ftmp = dbn(field.name);
				ftmp = ftmp.length ? ftmp[0] : null;
				this.field_id = field.name;
			}
		} else {
			ftmp = did(field);
			this.field_id = field;
			this.field = ftmp;
		}
		if ( !ftmp ) {
			throw new ReferenceError('field "' + this.field_id + '" must be a valid element id or name.');
		}
		this.condField = ftmp;
	},
	addRule: function addConditionalRule(matching, rule) {
		var args = Activa.toArray(arguments);
		rule = this.root.apply(null, args);
		if ( !rule ) {
			return;
		}
		if ( this.form ) {
			rule.setForm(this.form);
		}
		if ( matching === null ) {
			this.elses.push(rule);
		} else {
			if ( !this.rules[matching] ) {
				this.rules[matching] = [];
			}
			this.rules[matching].push(rule);
		}
	},
	setForm: function setConditionalForm(form) {
		if ( !(form instanceof Form) ) {
			throw new TypeException('form must be a valid Form object');
		}
		this.form = form;
		for ( var match in this.rules ) {
			this.rules[match].forEach(function(rule) {
				rule.setForm(form);
			});
		}
		this.elses.forEach(function(rule) {
			rule.setForm(form);
		});
	},
	validate: function validateConditional() {
		var validate_else = true;
		for ( var match in this.rules ) {
			var toggled = (this.condField.type == 'checkbox' || this.condField.type == 'radio');
			if ( this.condField && ((toggled && this.condField.checked) || (!toggled && this.condField.value == match)) ) {
				validate_else = false;
				var rule = null;
				for ( var i = 0, z = this.rules[match].length; i < z; i++ ) {
					rule = this.rules[match][i];
					if ( !rule.validate() ) {
						this.message = rule.message;
						this.field = rule.field;
						return false;
					}
				}
			}
		}
		if ( validate_else ) {
			var rule = null;
			for ( var i = 0, z = this.elses.length; i < z; i++ ) {
				rule = this.elses[i];
				if ( !rule.validate() ) {
					this.message = rule.message;
					this.field = rule.field;
					return false;
				}
			}
		}
		return true;
	},
	validateRecursive: function validate_recursive(focus, from_success) {
		var errors = false;
		var validate_else = true;
		from_success = from_success === undefined ? false :Boolean(from_success);
		for ( var match in this.rules ) {
			var toggled = (this.condField.type == 'checkbox' || this.condField.type == 'radio');
			var valid = (this.condField && ((toggled && this.condField.checked) || (!toggled && this.condField.value == match)));
			if ( valid ) {
				validate_else = false;
			}
			var rule = null;
			for ( var i = 0, z = this.rules[match].length; i < z; i++ ) {
				rule = this.rules[match][i];
				if ( valid ) {
					if ( !Inline_Validate.validateRule(rule, focus, from_success) ) {
						errors = true;
					}
				} else {
					Inline_Validate.validateFalse(rule);
				}
			}
		}
		var rule = null;
		for ( var i = 0, z = this.elses.length; i < z; i++ ) {
			rule = this.elses[i];
			if ( validate_else ) {
				if ( !Inline_Validate.validateRule(rule, focus, from_success) ) {
					errors = true;
				}
			} else {
				Inline_Validate.validateFalse(rule);
			}
		}
		
		return !errors;
	}
});

/**
 * Create a container for conditional rules
 * 
 * @param Rule field
 */
var ConditionalRule = new Activa.Class({
	Extends: Conditional,
	condRule: null,
	init: function initConditionalRule(rule) {
		this.condRule = rule;
		this.field = rule.field;
		if ( this.form && rule ) {
			rule.setForm(this.form);
		}
	},
	addRule: function addConditionalRuleRule(rule) {
		var args = Activa.toArray(arguments);
		// If there is more than 1 arg, we want to pass to a child RuleContainer
		if ( args.length > 1 || this.currentContainer instanceof RuleContainer ) {
			this.root.apply(null, args);
		} else {
			this.root("all", rule);
		}
	},
	setForm: function set_form(form) {
		this.root(form);
		if ( this.condRule ) {
			this.condRule.setForm(form);
		}
	},
	validate: function validateConditionalRule() {
		if ( this.condRule.validate() ) {
			for ( var i = 0; i < this.rules["all"].length; i++ ) {
				var rule = this.rules["all"][i];
				if ( !rule.validate() ) {
					this.message = rule.message;
					this.field = rule.field;
					return false;
				}
			}
		}
		return true;
	},
	validateRecursive: function validate_recursive(focus, from_success) {
		from_success = from_success === undefined ? false :Boolean(from_success);
		var errors = false;
		if ( this.condRule.validate() ) {
			for ( var i = 0; i < this.rules["all"].length; i++ ) {
				var rule = this.rules["all"][i];
				if ( !Inline_Validate.validateRule(rule, focus, from_success) ) {
					errors = true;
				} else {
					Inline_Validate.validateFalse(rule);
				}
			}
		} else {
			Inline_Validate.validateFalse(this);
		}
		return !errors;
	}
});

var ConditionalSuccess = new Activa.Class({
	Extends: Conditional,
	message: '',
	required: true,
	init: function initConditionalSuccess(field, message, required) {
		this.field = did(field);
		this.message = message;
		this.required = required === undefined? true : Boolean(required);
	},
	addRule: function addConditionalSuccessRule(type, rule) {
		var type = type === undefined? 'hide_errors' : type;
		var args = Activa.toArray(arguments);
		// If there is more than 1 arg, we want to pass to a child RuleContainer
		if ( args.length > 2 || this.currentContainer instanceof RuleContainer ) {
			this.root.apply(null, args);
		} else {
			this.root(type, rule);
		}
	},
	validate: function validate () {
		return this.validateRecursive(null);
	},
	validateRecursive: function validate_recursive(focus, from_success) {
		from_success = from_success === undefined ? false :Boolean(from_success);
		var errors = false;
		if ( this.rules && this.rules['hide_errors'] ) {
			for ( i = 0; i < this.rules['hide_errors'].length; i++ ) {
				var rule = this.rules['hide_errors'][i];
				if ( !rule.validate() && from_success ) {
					errors = true;
				} else {
					Inline_Validate.validateFalse(rule);
				}
			}
		}
		if ( this.rules && this.rules['show_errors'] ) {
			for ( x = 0; x < this.rules['show_errors'].length; x++ ) {
				var rule2 = this.rules['show_errors'][x];
				if ( !Inline_Validate.validateRule(rule2, focus, true) ) {
					errors = true;
				} else {
					Inline_Validate.validateFalse(rule2);
				}
			}
		}
		if ( this.required ) {
			return !errors;
		}
		return true;
	}
});

var ConditionalError = new Activa.Class({
	Extends: Conditional,
	message: '',
	rule_outcomes: [],
	init: function initConditionalError(field, message) {
		this.field = did(field);
		this.message = message;
	},
	addRule: function addConditionalErrorRule(type, rule, outcome) {
		var type = type === undefined? 'all' : type;
		var outcome = outcome === undefined? true : Boolean(outcome);
		//var args = Activa.toArray(arguments);
		this.root(type, rule);
		this.rule_outcomes.push(outcome);
	},
	validate: function validate() {
		return this.validateRecursive(null);
	},
	validateRecursive: function validate_recursive(focus, from_success) {
		var errors = false;
		if ( this.rules && this.rules['all'] ) {
			for ( var i = 0; i < this.rules['all'].length; i++ ) {
				var rule = this.rules['all'][i];
				if ( Inline_Validate.validateRuleTest(rule) === this.rule_outcomes[i] ) {
					errors = true;
				} else {
					Inline_Validate.validateFalse(rule);
				}
			}
		}
		Inline_Msg.getInstance(did(this.field.id)).error(this.message, errors, true);
		return !errors;
	}
});

/**
 *
 * Validation Rules
 * 
 */



var Rule_Alphanumeric = new Activa.Class({
	Extends: Rule,
	validate: function validateAlphanumeric() {
		if ( this.root() ) {
			return true;
		}
		return /^[a-zA-Z0-9]+$/.test(this.field.value);
	}
});

var Rule_Alpha = new Activa.Class({
	Extends: Rule,
	validate: function validateAlpha() {
		if ( this.root() ) {
			return true;
		}
		return /^[a-zA-Z]+$/.test(this.field.value);
	}
});

var Rule_Between = new Activa.Class({
	Extends: Rule,
	min: 0,
	max: 0,
	init: function initBetween(field, message, match, required) {
		this.root(field, message, match, required);
		var parts = match.split('-');
		this.min = parseFloat(parts[0] || 0);
		this.max = parseFloat(parts[1] || 0);
	},
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		var flen = parseFloat(this.field.value.length);
		return (flen >= this.min && flen <= this.max);
	}
});

var Rule_Captcha = new Activa.Class({
	Extends: Rule,
	validate: function validateCaptcha() {
		if ( this.root() ) {
			return true;
		}
		return this.field.value.trim() != '';
	}
});

var Rule_Checkbox = new Activa.Class({
	Extends: Rule,
	validate: function validateCheckbox() {
		if ( this.root() ) {
			return true;
		}
		return !!(this.field.checked);
	}
});

var Rule_Checkboxes = new Activa.Class({
	Extends: Rule,
	validate: function validateCheckboxes() {
		var boxes = Activa.toArray(dbn(this.field_id+'[]', this.form || null));
		var count = 0;
		boxes.forEach(function isChecked(box) {
			if ( box.checked ) {
				count++;
			}
		});
		return (count == this.match);
	}
});

var Rule_CreditCardNumber = new Activa.Class({
	Extends: Rule,
	validate: function validateCreditCardNumber() {
		if ( this.root()  ) {
			return true;
		}
		var len = (this.field.value.replace(/[^\d]/g, '')).length;
		if ( !(/^[\d -]+$/.test(this.field.value)) && (len < 13 || len > 16) ) {
			return false;
		}
		return true;
	}
});

var Rule_Email = new Activa.Class({
	Extends: Rule,
	regex: null,
	init: function initEmail(field, message, match, required) {
		this.root(field, message, match, required);
		this.regex = new RegExp(match);
	},
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		return (this.regex.test(this.field.value));
	}
});

var Rule_Equals_Field = new Activa.Class({
	Extends: Rule,
	allowempty: false,
	init: function initEqualsField(field, message, match, required, allowempty) {
		this.root(field, message, match, required);
		this.allowempty = allowempty === undefined? false : Boolean(allowempty);
	},
	validate: function validateEqualsField() {
		if ( this.root() ) {
			return true;
		}
		var field = did(this.match);
		if ( !this.allowempty ) {
			if ( this.field.value == '' || ( field && field.value == '' ) ) {
				return false;
			}
		}
		return (field && this.field.value == field.value); 
	}
});

var Rule_Equals = new Activa.Class({
	Extends: Rule,
	validate: function validateEquals() {
		if ( this.root() ) {
			return true;
		}
		return (this.field.value == this.match);
	}
});

var Rule_Greater = new Activa.Class({
	Extends: Rule,
	min: 0,
	init: function initGreater(field, message, match, required) {
		this.root(field, message, match, required);
		this.min = parseFloat(match) || 0;
	},
	validate: function validateGreater() {
		if ( this.root() ) {
			return true;
		}
		var val = parseFloat(this.field.value);
		return (!isNaN(val) && val > this.min);
	}
});

var Rule_Length = new Activa.Class({
	Extends: Rule,
	len: 0,
	init: function initLength(field, message, match, required) {
		this.root(field, message, match, required);
		this.len = parseInt(this.match) || 0;
	},
	validate: function validateLength() {
		if ( this.root() ) {
			return true;
		}
		var len = parseInt(this.field.value.length);
		return (!isNaN(len) && len == this.len);
	}
});

var Rule_Less = new Activa.Class({
	Extends: Rule,
	max: 0,
	init: function initLess(field, message, match, required) {
		this.root(field, message, match, required);
		this.max = parseFloat(match) || 0;
	},
	validate: function validateLess() {
		if ( this.root() ) {
			return true;
		}
		var val = parseFloat(this.field.value);
		return (!isNaN(val) && val < max);
	}
});

var Rule_Longer = new Activa.Class({
	Extends: Rule,
	min: 0,
	init: function initLonger(field, message, match, required) {
		this.root(field, message, match, required);
		this.min = parseInt(match) || 0;
	},
	validate: function validateLonger() {
		if ( this.root() ) {
			return true;
		}
		return (parseInt(this.field.value.length) > this.min);
	}
});

var Rule_Match = new Activa.Class({
	Extends: Rule,
	regex: null,
	init: function initMatch(field, message, match, required) {
		this.root(field, message, match, required);
		this.regex = new RegExp(match);
	},
	validate: function validateMatch() {
		if ( this.root() ) {
			return true;
		}
		return (this.regex.test(this.field.value));
	}
});

var Rule_Notequals = new Activa.Class({
	Extends: Rule,
	validate: function validateNotEquals() {
		if ( this.root() ) {
			return true;
		}
		return (this.field.value != this.match);
	}
});

var Rule_Numeric = new Activa.Class({
	Extends: Rule,
	validate: function validateNumeric() {
		if ( this.root() ) {
			return true;
		}
		return /^[0-9]+$/.test(this.field.value);
	}
});

var Rule_Phone_Int = new Activa.Class({
	Extends: Rule,
	validate: function validatePhoneInt() {
		if ( this.root() ) {
			return true;
		}
		var val = this.field.value = this.field.value.trim().replace(/[\s-\(\)]/g, '');
		return /^\+?[\d+]{10,16}$/.test(val);
	}
});

var Rule_Phone = new Activa.Class({
	Extends: Rule,
	validate: function validatePhone() {
		if ( this.root() ) {
			return true;
		}
		var val = this.field.value = this.field.value.trim().replace(/[\s-\(\)]/g, '');
		return /^\d{10}$/.test(val);
	}
});

var Rule_RadioList = new Activa.Class({
	Extends: Rule,
	validate: function validateRadioList() {
		var radios = Activa.toArray(dbn(this.field_id));
		var value = false; 
		radios.forEach(function(radio) {
			if ( radio.checked ) {
				if ( this.match && this.match != '' && this.match != '0' ) {
					value = radio.value == this.match;
				} else {
					value = true;
				}
			}
		}.bind(this));
		return value;
	}
});

var Rule_Range = new Activa.Class({
	Extends: Rule_Between,
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		var flen = parseFloat(this.field.value);
		return (flen > this.min && flen < this.max);
	}
});

var Rule_Shorter = new Activa.Class({
	Extends: Rule,
	max: 0,
	init: function initShorter(field, message, match, required) {
		this.root(field, message, match, required);
		this.max = parseInt(match) || 0;
	},
	validate: function validateBetween() {
		if ( this.root() ) {
			return true;
		}
		var val = parseInt(this.field.value.length);
		return (!isNaN(val) && val < this.max);
	}
});

var Rule_Text = new Activa.Class({
	Extends: Rule,
	validate: function validateText() {
		if ( this.root() ) {
			return true;
		}
		return !this.field.value.trim() == '';
	}
});



var Inline_Validate = new Activa.Class({
});

Inline_Validate.statics({
	validate: function validate(rules, focus, from_success) {
		var focus = focus;
		var errors = false;
		for ( var i = 0; i < rules.length; i++ ) {
			var rule = rules[i];
			if ( !this.validateRule(rule, focus, from_success) ) {
				errors = true;
			}
		}
		return !errors;
	},
	validateRule: function validateRule(rule, focus, from_success) {
		var errors = false;
		// From Success means its updating from a success conditional. Which is updated every keyup on the 
		// conditionals fields, and child fields.
		from_success = from_success === undefined ? false :Boolean(from_success);
		if ( rule instanceof RuleContainer ) {
			if ( !rule.validateRecursive(focus, from_success) ) {
				errors = true;
			}
		} else {
			//console.log(rule, rule.validate());
			if ( !rule.validate() ) {
				Inline_Msg.getInstance(did(rule.field.id)).error(rule.message, true, focus || from_success? true : false);
				if ( focus ) {
					focus.focusField(rule.field.id);
				}
				errors = true
				//Inline_Validate.validateFalse(rule);
			} else {
				Inline_Msg.getInstance(did(rule.field.id)).error(rule.message, false, focus || from_success? true : false);
			}
		}
		return !errors;
	},
	validateRuleTest: function validateRule(rule) {
		var error = false;
		/*
		if ( rule instanceof RuleContainer ) {
			
		} else {
			return rule.validate();
		}
		*/
		return rule.validate();
	},
	chkSuccess: function check_success(rules, error_on_submit) {
		var arr_rules = Activa.toArray(rules);
		arr_rules.forEach(function(rule) {
			var inst = Inline_Msg.getInstance(rule.field)
			var had_errors = inst.hasErrors();
			var show = rule.validateRecursive(null, true);
			if ( show && !inst.showing_success ) {
				inst.showing_success = true;
				inst.success(rule.message, true, had_errors);
			} else if ( !show ) {
				inst.showing_success = false;
				inst.success(rule.message, false);
			}
		});
	},
	validateFalse: function validate_false(rule) {
		
		if ( !(rule instanceof RuleContainer) ) {
			var inst = Inline_Msg.getInstance(did(rule.field.id));
			inst.error(rule.message, false, true);
		} else if ( rule instanceof ConditionalSuccess ) {
			// no support for nested success yet
		} else if ( rule instanceof ConditionalError ) {
			// no handles itself
		} else if ( rule instanceof ConditionalRule ) {
			if ( !rule.condRule.validate() ) {
				for ( var i = 0; i < rule.rules["all"].length; i++ ) {
                    var cur_rule = rule.rules["all"][i];
                    if ( cur_rule instanceof RuleContainer ) {
                        this.validateFalse(cur_rule);
                        continue;
                    }
                    // Hide Error
                    var inst = Inline_Msg.getInstance(did(cur_rule.field.id));
                    inst.error(cur_rule.message, false, true);
                }
			}
		} else if ( rule instanceof Conditional ) {
			var validate_else = true;
			for ( var match in rule.rules ) {
				var toggled = (rule.condField.type == 'checkbox' || rule.condField.type == 'radio');
				var valid = rule.condField && ((toggled && rule.condField.checked) || (!toggled && rule.condField.value == match));
				if ( valid ) {
					validate_else = false;
					var cur_rule = null;
					for ( var i = 0, z = rule.rules[match].length; i < z; i++ ) {
						cur_rule = rule.rules[match][i];
						if ( cur_rule instanceof RuleContainer ) {
							this.validateFalse(cur_rule);
							continue;
						}
						var inst = Inline_Msg.getInstance(did(cur_rule.field.id));
						//inst.error(cur_rule.message, !cur_rule.validate());
						inst.error(cur_rule.message, false, true);
					}
				}
			}
			if ( validate_else ) {
				var cur_rule = null;
				for ( var i = 0, z = rule.elses.length; i < z; i++ ) {
					cur_rule = rule.elses[i];
					if ( cur_rule instanceof RuleContainer ) {
						this.validateFalse(cur_rule);
						continue;
					}
					var inst = Inline_Msg.getInstance(did(cur_rule.field.id));
					//inst.error(cur_rule.message, !cur_rule.validate());
					inst.error(cur_rule.message, false, true);
				}
			}
		} else {
			var inst = Inline_Msg.getInstance(did(rule.field.id));
			inst.error(rule.message, false, true);
		}
	}
});

Inline_Validate.Focus = new Activa.Class({
	focused: false,
	field: null,
	focusField: function focus_field(field_id) {
		if ( this.focused ) {
			return false;
		}
		this.field = did(field_id);
		this.field.focus();
		this.focused = true;
	}
});

var Inline_Msg = new Activa.Class({
	field: null,
	holder: null,
	errors: {},
	showing: false,
	rules: [],
	has_focus: false,
	showing_success: false,
	do_blur: true,
	init: function construct(field, do_blur) {
		this.do_blur = do_blur === undefined ? true : Boolean(do_blur);
		this.field = field;
		var blur_type = 'blur';
		var node_name = this.field.nodeName.toUpperCase();
		this.chkHolder();
		if ( node_name == 'SELECT' || (node_name == 'INPUT' && (this.field.type.toUpperCase() == 'CHECKBOX' || this.field.type.toUpperCase() == 'radio')) ) {
			blur_type = 'change';
		}
		Activa.registerEvent(this.field, blur_type, function(e) {
			if ( this.do_blur ) {
				this.validate(true);
			}
			this.has_focus = false;
		}.bind(this));
		Activa.registerEvent(this.field, 'focus', function(e) {
			this.has_focus = true;
		}.bind(this));
	},
	hasErrors: function has_errors() {
		if ( this.holder && this.holder.childNodes ) {
			return this.holder.childNodes.length > 0
		}
		return false;
	},
	chkHolder: function check_holder(){
		// grabs the holder if it exists from php side
		var holder = Activa.DOM.next(this.field);
		if ( !holder || holder.nodeName.toUpperCase() != Inline_Msg.holder_tag || !Activa.DOM.hasClass(holder, Inline_Msg.css_class) ) {
			this.showing = false;
		} else {
			this.holder = holder;
			this.showing = true;
		}
	},
	mkHolder: function make_holder(){
		// Create the holder Element if needed
		var holder = Activa.DOM.next(this.field);
		if ( !holder || holder.nodeName.toUpperCase() != Inline_Msg.holder_tag || !Activa.DOM.hasClass(holder, Inline_Msg.css_class) ) {
			this.holder = document.createElement(Inline_Msg.holder_tag);
			Activa.DOM.addClass(this.holder, Inline_Msg.css_class);
			this.field.parentNode.insertBefore(this.holder, this.field.nextSibling);
		}
	},
	destroyHolder: function destroy_holder() {
		if ( this.holder && this.holder.parentNode ) {
			this.holder.parentNode.removeChild(this.holder);
		}
	},
	addRule: function add_rule(rule) {
		if ( this.rules.indexOf(rule) < 0 ) {
			this.rules.push(rule);
			if ( rule instanceof Conditional ) {
				if ( rule.rules ) {
					for ( var match in rule.rules ) {
						for ( var i = 0; i < rule.rules[match].length; i++ ) {
							var rule2 = rule.rules[match][i];
							Inline_Msg.getInstance(did(rule2.field.id)).addRule(rule);
						}
					}
				}
			}
		}
	},
	validate: function validate(force) {
		force = force === undefined? false : Boolean(force);
		if ( force ) {
			this.has_focus = true;
		}
		//Inline_Validate.validate(this.rules, new Inline_Validate.Focus());
		Inline_Validate.validate(this.rules, false, true);
		if ( force ) {
			this.has_focus = false;
		}
	},
	error: function error(message, show, from_submit) {
		from_submit = from_submit === undefined ? false : Boolean(from_submit);
		if ( !from_submit && !this.has_focus ) {
			return;
		}
		return this.msg(message, Inline_Msg.message_types.error, show);
	},
	success: function success(message, show, had_errors) {
		return this.msg(message, Inline_Msg.message_types.success, show, had_errors);
	},
	msg: function msg(message, type, show, had_errors) {
		var show = show === undefined ? true : Boolean(show);
		var index = message+type.css;
		if ( !this.showing && show ) {
			this.show();
		}
		/*
		if ( show ) {
			Activa.DOM.addClass(this.holder, type.holder_css);
		} else {
			Activa.DOM.removeClass(this.holder, type.holder_css);
		}
		*/
		
		// IF Error doesn't exist on this field
		if ( !Inline_Msg.exists(index, this.errors) && show ) {
			var msg = document.createElement(Inline_Msg.tag);
			msg.appendChild(document.createTextNode(message));
			Activa.DOM.addClass(msg, type.css);
			this.holder.appendChild(msg);
			this.errors[index] = msg;
			//type.callback(message, type, show, msg).apply(this);
		} else {
			var msg = this.errors[index];
		}
		
		for ( var i = 0; i <= 1; i++ ) {
			if ( i == 0 ) {
				if ( typeof type.callback != 'function' ) {
					if ( !show ) {
						this.hideMsg(message, type.css);
						this.chkHide();
					}
				} else {
					type.callback.call(this, message, type, show, msg, had_errors);
				}
			} else {
				this.updateHolderCss();
			}
		}
		return msg;
	},
	show: function show() {
		//this.holder.style.display = '';
		this.mkHolder();
		this.showing = true;
	},
	hide: function hide() {
		if ( typeof Inline_Msg.hide_inline_msg == 'function' ) {
			Inline_Msg.hide_inline_msg.call(this);
		} else {
			//this.holder.style.display = 'none';
			this.destroyHolder();
			this.showing = false;
		}
	},
	hideMsg: function hideMsg(message, type_css) {
		var index = message+type_css;
		delete this.errors[index];
		if ( !this.holder ) {
			return;
		}
		var length = this.holder.childNodes.length;
		for ( i = 0; i < length; i++ ) {
			var node = this.holder.childNodes[i];
			if ( Activa.DOM.hasClass(node, type_css) && node.innerHTML == message ) {
				this.holder.removeChild(node);
			}
		}
	},
	chkHide: function check_hide() {
		var hide = true;
		if ( !this.holder ) {
			return;
		}
		if ( this.holder.childNodes.length > 0 ) {
			for ( i = 0; i < this.holder.childNodes.length; i++ ) {
				var node = this.holder.childNodes[i];
				if ( node.nodeName && node.nodeName.toUpperCase() == Inline_Msg.tag ) {
					hide = false;
				}
			}
		}
		if ( hide ) {
			this.hide();
		}
	},
	updateHolderCss: function update_holder_css() {
		if ( !this.holder ) {
			return;
		}
		var types = {};
		for ( var key in Inline_Msg.message_types ) {
			types[key] = false;
		}
		if ( this.holder.childNodes.length > 0 ) {
			for ( i = 0; i < this.holder.childNodes.length; i++ ) {
				var node = this.holder.childNodes[i];
				if ( node.nodeName && node.nodeName.toUpperCase() == Inline_Msg.tag ) {
					for ( var key in types ) {
						if ( Activa.DOM.hasClass(node, Inline_Msg.message_types[key].css) ) {
							types[key] = true;
							Activa.DOM.addClass(this.holder, Inline_Msg.message_types[key].holder_css);
						}
					}
				}
			}
		}
		for ( var key in types ) {
			if ( types[key] == false ) {
				var type = Inline_Msg.message_types[key];
				Activa.DOM.removeClass(this.holder, type.holder_css);
			}
		}
	}
});

Inline_Msg.statics({
	/*
	css_error: 'error',
	css_success: 'success',
	css_class: 'dev_inline',
	css_holder_error: 'dev_inline_errors',
	css_holder_success: 'dev_inline_success',
	*/
	message_types: {
		error: {
			css: 'error', 
			holder_css: 'dev_inline_errors', 
			callback: null
		},
		success: {
			css: 'success', 
			holder_css: 'dev_inline_success', 
			callback: null
		}
	},
	hide_inline_msg: null,
	css_class: 'dev_inline',
	holder_tag: 'P',
	tag: 'STRONG',
	instances: {},
	getInstance: function getInstance(field, do_blur) {
		var instance = null;
		if ( instance = this.exists(field.id, this.instances) ) {
		} else {
			instance = this.instances[field.id] = new Inline_Msg(field, do_blur);
		}
		return instance;
	},
	exists: function field_exists(field_id, obj) {
		for ( var key in obj ) { 
			if ( key == field_id ) {
				return obj[key];
			}
		}
		return false;
	}
});



function updateShipping(parcel_index, url) {
	if ( did("service_" + parcel_index) ) {
		url += "/" + did("service_" + parcel_index).value;
	}
	if ( did("carrier_" + parcel_index) ) {
		url += "/" + did("carrier_" + parcel_index).value;
	}
	new Activa.rpc(url, { onComplete: updateCheckoutModifiers });
}

function doUpdateShipping(obj) {	
	if ( obj.shipping_method == 'order' ) {
		did("shipping_total").innerHTML = "$" + obj.shipping;
		did("grand_total").innerHTML = "$" + obj.grand_total;
		Activa.DOM.showID("grand_total_row");
	}
}

Activa.DOM.ready(function() {
	var keyword = 'Keyword Search';
	var search_field = did('search_field');
	var site_search = did('site_search');
	if ( search_field && search_field.value == "" ) {
		search_field.value = keyword;
	}
	if ( search_field && search_field.value != keyword && search_field.value != "" ) {
		Activa.DOM.addClass(search_field, 'typed');
	}
	if ( search_field ) {
		Activa.registerEvent(search_field, 'blur', function(e) {
			if ( search_field.value == '' ) {
				search_field.value = keyword;
				Activa.DOM.removeClass(search_field, 'typed');
			}
		});
		Activa.registerEvent(search_field, 'focus', function(e) {
			if ( search_field.value == keyword ) {
				search_field.value = '';
			}
			Activa.DOM.addClass(search_field, 'typed');
		});
	}
	if ( site_search ) {
		Activa.registerEvent(site_search, 'submit', function(e) {
			var search_field = did('search_field');
			if ( search_field.value == keyword || search_field.value == "" ) {
				window.alert("Please enter a search string.");
				e.preventDefault();
			}
		});
	}
});

function updateCheckoutModifiers(obj) {
	var before_shipping = false;
	
	for ( var parcel_index = 0; parcel_index < obj.parcels.length; parcel_index++ ) {
	
		var tbody = did("checkout_table_" + parcel_index).tBodies[0];
	
		Activa.DOM.hideID("shipping_row_" + parcel_index);
		
		var sub_total_row_found = false;
		for ( var i = 0; i < tbody.childNodes.length; i++ ) {
			if ( tbody.childNodes[i].nodeName == 'TR' ) {
				if ( tbody.childNodes[i].id == 'grand_total_row_' + parcel_index ) {
					break;
				} else if ( tbody.childNodes[i].id == 'sub_total_row_' + parcel_index ) {
					sub_total_row_found = true;
				} else if ( sub_total_row_found ) {
					if ( tbody.childNodes[i].id != 'shipping_row_' + parcel_index ) {
						tbody.childNodes[i].parentNode.removeChild(tbody.childNodes[i]);
						i--;
					}
				}		
			}	
		}
		
		var insert_before_target = did("shipping_row_" + parcel_index) || did("grand_total_row_" + parcel_index);
		
		for ( var i = 0; i < obj.parcels[parcel_index].modifiers.length; i++ ) {
			
			if ( obj.parcels[parcel_index].modifiers[i].service ) {
			
				if ( did("carrier_" + parcel_index) ) {
				
					while ( did("carrier_" + parcel_index).options.length > 0 ) {
						did("carrier_" + parcel_index).remove(0);
					}
					
					for ( var k=0; k < obj.parcels[parcel_index].modifiers[i].carriers.length; k++ ) {
						var optn = document.createElement("option");
						optn.text = obj.parcels[parcel_index].modifiers[i].carriers[k].text;
						optn.value = obj.parcels[parcel_index].modifiers[i].carriers[k].value;
						did("carrier_" + parcel_index).options.add(optn);
					}
					
					did("carrier_" + parcel_index).value = obj.modifiers[i].carrier;
				}
				
				if ( did("service_" + parcel_index) ) {
				
					while ( did("service_" + parcel_index).options.length > 0 ) {
						did("service_" + parcel_index).remove(0);
					}
					
					for ( var k = 0; k < obj.parcels[parcel_index].modifiers[i].services.length; k++ ) {
						var optn = document.createElement("option");
						optn.text = obj.parcels[parcel_index].modifiers[i].services[k].text;
						optn.value = obj.parcels[parcel_index].modifiers[i].services[k].value;
						did("service_" + parcel_index).options.add(optn);
					}
					
					did("service_" + parcel_index).value = obj.parcels[parcel_index].modifiers[i].service;
				}
				
				if ( obj.parcels[parcel_index].modifiers[i].tbd ) {
					Activa.DOM.showID('collect-shipping_' + parcel_index);
					did("shipping_total_" + parcel_index).innerHTML = 'TBD';
				} else {
					Activa.DOM.hideID('collect-shipping_' + parcel_index);
					did("shipping_total_" + parcel_index).innerHTML = obj.parcels[parcel_index].modifiers[i].amount;
				}				
			
				insert_before_target = did("grand_total_row_" + parcel_index);	
			} else {
				var tr = document.createElement('tr');
				tr.className = 'discount';
				
				var td = document.createElement('td');
				td.colSpan = '5';
				td.appendChild(document.createTextNode(obj.parcels[parcel_index].modifiers[i].name));
				tr.appendChild(td);
				
				var td = document.createElement('td');
				td.className = ' ' + (obj.parcels[parcel_index].modifiers[i].positive ? '' : 'red');
				td.appendChild(document.createTextNode(obj.parcels[parcel_index].modifiers[i].amount));
				tr.appendChild(td);
				
				tbody.insertBefore(tr, insert_before_target);
			}
		}
		
		did("grand_total_" + parcel_index).innerHTML = "$" + obj.parcels[parcel_index].total;
		Activa.DOM.showID("shipping_row_" + parcel_index);
	}
	
	var trs = did("grand_total_row").parentNode.childNodes;
	for ( var i = 0; i < trs.length; i++ ) {
		if ( trs[i].nodeName == "TR" && trs[i].className == 'discount' ) {
			did("grand_total_row").parentNode.removeChild(trs[i]);
			i--;
		}
	}
	
	for ( var i = 0; i < obj.modifiers.length; i++ ) {	
		var tr = document.createElement('tr');
		tr.className = 'discount';
		
		var td = document.createElement('td');
		td.colSpan = '5';
		td.appendChild(document.createTextNode(obj.modifiers[i].name));
		tr.appendChild(td);
		
		var td = document.createElement('td');
		td.className = ' ' + (obj.modifiers[i].positive ? '' : 'red');
		td.appendChild(document.createTextNode(obj.modifiers[i].amount));
		tr.appendChild(td);
		
		did("grand_total_row").parentNode.insertBefore(tr, did("grand_total_row"));
	}
	
	if ( obj.payment_method == 'creditcard' ) {
		Activa.DOM.showID("checkout_form");
		Activa.DOM.hideID("checkout_promocode_form");
		Activa.DOM.hideID("checkout_giftcert_form");
		processPaymentType();
	} else {
		Activa.DOM.hideID("checkout_form");
		Activa.DOM.hideID("checkout_promocode_form");
		Activa.DOM.hideID("checkout_giftcert_form");
		if ( obj.payment_method == 'promocode' ) {
			Activa.DOM.showID("checkout_promocode_form");
		} else if ( obj.payment_method == 'giftcert' ) {
			Activa.DOM.showID("checkout_giftcert_form");
		}
	}
		
	if ( obj.promo_error ) {
		did('promo_code_error').innerHTML = obj.promo_error;
		Activa.DOM.showID('promo_code_error');
	} else {
		did('promo_code_error').innerHTML = '';
		Activa.DOM.hideID('promo_code_error');
	}
	
	did("grand_total").innerHTML = "$" + obj.grand_total;

	if ( did('amount') ) {
		did('amount').value = obj.grand_total;
	}
}


/**
 * rgbToHex - Converts an rgb color value into a hexidecimal value (without the leading '#')
 * @param	integer		r		Value of the color red in range 0-255;
 * @param	integer		g		Value of the color green in range 0-255;
 * @param	integer		b		Value of the color blue in range 0-255;
 * @return	string				Hexidecimal representation of the the color
 */
function rgbToHex(r,g,b){
	var rgb = slice(arguments,0,3), hex = '0123456789ABCDEF';
	for(var i=0;i < 3;i++){
		rgb[i] = isNaN(parseInt(rgb[i],10)) ? 0 : parseInt(rgb[i],10);
		rgb[i] = Math.round(Math.max(Math.min(255,rgb[i]),0));
		rgb[i] = hex.charAt( (rgb[i]-rgb[i]%16)/16 ) + hex.charAt( rgb[i]%16 );
	}
	return rgb.join('');
}

function getPosition(elm) {
	var pos = { x: 0, y: 0 };
	if ( elm.offsetParent ) {
		do {
			pos.x += elm.offsetLeft;
			pos.y += elm.offsetTop;
		} while ( elm = elm.offsetParent );
	}
	return pos;
};

function getScroll(elm) {
	var pos = { x: 0, y: 0 };
	if ( elm && elm.tagName != 'BODY' ) {
		pos.y += elm.scrollTop;
		pos.x += elm.scrollLeft;
		elm = elm.parentNode;
	}
	return pos;
}

/**
 * getStyle - Retrieve the value of the current style property
 * @param	mixed		obj		Element id or object
 * @param	string		prop	Name of style property to retrieve
 * @return	mixed				Value of the style property. *Note: Unit values will be returned as integers. (ie. 'px' will be stripped off)*
 */
function getStyle(obj,prop){
	obj = (typeof obj == 'object') ? obj : did(obj);
	if(!obj || !prop){ return null; }
	var regRGB = /rgb\((\d+),\s(\d+),\s(\d+)\)/i, color;
	var compVal = obj.currentStyle ? obj.currentStyle[prop] : window.getComputedStyle(obj,null).getPropertyValue(prop);
	if((color = regRGB.exec(compVal)) != null){ color.shift(); compVal = '#'+rgbToHex.apply(null,color); }
	return isNaN(parseFloat(compVal)) ? compVal : parseFloat(compVal);
}

/**
 * getStyles - Retrieve the current value of several styles
 * @param	mixed	obj		Element id or object
 * @param	string	prop	Name of style property to retrieve (pass another parameter for each style property)
 * @return	object			Object whose keys are the style property and values are the style property's value
 */
function getStyles(obj, prop) {
	var args = slice(arguments,1);
	var styles = {};
	args.forEach(function(arg){
		this[arg] = getStyle(obj,arg);
	},styles);
	return styles;
}

var Fx = {};

/**
 * Fx.Base
 * Note:  This class is intended to be extended by other effects.  Not for actual use.
 * 
 * Parameters:
 * options		object		Holds every optional parameter
 * 
 * options:
 * fps			int			Frames per second
 * duration		int			Length of animation
 * wait			bool		If set to true, it will ignore the requested effect until the current is over.  Defaults to false.
 * onStart		function	Triggers when the effect is started
 * onComplete	function	Triggesr when the effect is over (if aborted or finished).
 */
Fx.Base = new Activa.Class({
	playing: false,
	timer: null,
	time: 0,
	from: 0,
	to: 0,
	options: {
		fps: 50,
		duration: 500,
		wait: false,
		transition: null,
		onStart: function(){},
		onComplete: function(){}
	},
	init: function init(options) {
		this.setOptions(options);
	},	
	setOptions: function setOptions(options) {
		options = options || {};
		for ( var k in this.options ) {
			this.options[k] = (k in options) ? options[k] : this.options[k];
		}
	},
	start: function start(from, to, overrideWait) {
		if ( this.playing && this.options.wait ) {
			return false;
		}
		clearInterval(this.timer);
		this.from = from;
		this.to = to;
		this.time = 0;
		this.startTimer();
		this.transition = this.getTransition();
		return this;
	},
	getTransition: function getTransition() {
		if ( this.options.transition === false ) {
			return false;
		}
		return this.options.transition || Fx.Transitions.Sine.easeInOut;
	},
	frame: function frame() {
		var time = Number(new Date().getTime());
		if ( time < this.time + this.options.duration ) {
			if ( !this.options.transition ) {
				var d = (time-this.time)/this.options.duration;
			} else {
				var d = this.transition((time-this.time)/this.options.duration);
			}
			this.set(this.compute(this.from, this.to, d));
		} else {
			clearInterval(this.timer);
			this.set(this.compute(this.from, this.to, 1));
			this.stop();
			this.finished();
		}
	},
	set: function set(value) {
		return value;
	},
	stop: function stop() {
		this.playing = false;
		clearInterval(this.timer);
		this.options.onComplete();
	},
	finished: function finished() {
		return null;
	},
	compute: function compute(from,to,d) {
		return Fx.Base.compute(from, to, d);
	},
	startTimer: function startTimer() {
		this.playing = true;
		this.time = new Date().getTime();
		
		this.timer = setInterval(this.frame.bind(this), (1000 / this.options.fps));
		
		this.options.onStart();
	}
});

Fx.Base.statics({
	compute:function(from, to, d) {
		return (to-from) * d + from;
	}
});

Fx.Transition = new Activa.Class({
	init: function init(transition, params) {
		transition.easeIn = function(pos){
			return transition(pos, params);
		};
		transition.easeOut = function(pos) {
			return 1 - transition(1 - pos, params);
		};
		transition.easeInOut = function(pos) {
			return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2;
		};
		return transition;
	}
})


Fx.Transitions = {
	Sine: function trans(p) {
		return 1 - Math.sin((1 - p) * Math.PI / 2);
	},
	Circ: function circ(p){
		return 1 - Math.sin(Math.acos(p));
	},
	Expo: function expo(p){
		return Math.pow(2, 8 * (p - 1));
	},
	Bounce: function(p){
		var value;
		for (var a = 0, b = 1; 1; a += b, b /= 2){
			if (p >= (7 - 4 * a) / 11){
				value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
				break;
			}
		}
		return value;
	}
};

for ( var i in Fx.Transitions ) { 
	Fx.Transitions[i] = new Fx.Transition(Fx.Transitions[i]); 
};


/**
 * Fx.Style
 * 
 * Parameters:
 * elm			element			Element to apply the effect on.
 * options		object			Object to hold optional parameters.
 * 
 * options (Inherits Fx.Base's options):
 * unit			string			Unit used by the style. Ex: px, %, pt
 */
Fx.Style = new Activa.Class({
	Extends:Fx.Base,
	options:{
		unit:'px'
	},
	init:function init(elm, options){
		this.elm = elm;
		this.unit = '';
		this.parser = {};
		this.uniquid = (new Date().getTime()*(Math.round(Math.random() * 100)));
		this.root(options);
	},
	set: function set(value){
		if ( typeof value == 'object' ) {
			for ( var k in value ) {
				this.setStyle(k, value[k]);
			}
			return;
		}
		this.setStyle(value);
	},
	compute: function compute(from, to, d) {
		if ( typeof from == 'object' ) {
			var values = {};
			for ( k in from ) {	
				values[k] = this.parser[to[k]].call(this, from[k], to[k], d);
			}
			return values;
		}
		
		return this.parser[to].call(this, from, to, d);
	},
	start: function start(style, from, to) {
		
		if ( typeof style == 'object' ) {
			var from = {}, to = {}, parser = {};
			for ( k in style ) {
				to[k] = style[k];
				this.setParser(to[k]);
				from[k] = this.getStyle(k);
			}
		}

		if ( typeof style != 'object' && !to ) {
			this.style = style;
			var to = from;
			var from = this.getStyle();
			this.unit = this.getUnit(this.style);
			this.setParser(to);
		}
				
		this.root(from, to);
	},
	setParser: function getParser(value) {
		for ( var parser in Fx.Style.Parsers ) {
			if ( Fx.Style.Parsers[parser].isParser(value) ) {
				this.parser[value] = Fx.Style.Parsers[parser].compute;
				return;
			}
		}
		this.parser[value] = Fx.Base.compute;
	},
	getStyle:function STgetStyle(style){
		style = style || this.style;
		if ( !window.ActiveXObject ) {
			style = Fx.Style.cssStyle(style);
		}
		
		if ( style == 'opacity' ) {
			return this.getOpacity();
		}
		
		var result = getStyle(this.elm, style);
		if ( Fx.Style.Parsers.Color.isParser(result) || style == 'position' ) {
			return result;
		}
		return isNaN(parseInt(result)) ? 0 : parseInt(result);
	},
	setStyle:function setStyle(style, value){

		if ( typeof value == 'undefined' ) {
			var value = style;
			style = this.style;
		}
		
		if ( !this.unit ) {
			this.unit = this.getUnit(style);
		}
		
		if ( style == 'opacity' ) {
			return this.setOpacity(value);
		}
		if ( isNaN(value) ) {
			return;
		}
		this.elm.style[style] = value + this.unit;
	},
	getOpacity:function getOpacity(){
		if ( typeof Fx.Style.opacities[this.uniquid] != 'undefined' ) {
			return Fx.Style.opacities[this.uniquid];
		}
		var style = getStyle(this.elm, 'opacity');
		return style || 1;
	},
	setOpacity:function setOpacity(value) {
		value = Math.abs(value);
		if ( window.ActiveXObject ) {
			this.elm.style.filter = 'alpha(opacity = '+Math.round(value*100)+')';
			this.elm.style.zoom = '1';
		} else {
			this.elm.style.opacity = Math.round(value*100)/100;
		}
		Fx.Style.opacities[this.uniquid]=value;
	},
	getUnit:function getUnit(style){
		if ( /color/i.test(style) ) {
			return this.unit = '';
		};
		switch(style){
			default:
				return this.options.unit;
			break;
		};
	}
});

Fx.Style.statics({
	opacities: {},
	cssStyle: function(style) {
		if ( window.ActiveXObject ) { return this.jsStyle(style); };
		return style.replace(/([A-Z])/g,function(a) { return '-'+a; }).toLowerCase();
	},
	jsStyle: function(style) {
		return style.replace(/(-[a-z])/g,function(a) { return a.replace('-', '').toUpperCase(); });
	},
	Parsers: {
		Color: {
			isParser: function isColor(string) {
				return /^(#([a-f0-9]{3,6})|rgb\((\d+),\s(\d+),\s(\d+)\))$/i.test(String(string));
			},
			hexToRGB: function hexToRGB(hex) {
				var arr = String(hex).match(/#?(\w{1,2})(\w{1,2})(\w{1,2})/i);
				if ( !arr ) {
					return [255,255,255];
				}
				arr.shift();
				return arr.map(function hexMap(a) {
					if ( a.length == 1 ) {
						a += a;
					}
					return parseInt(a, 16);
				});
			},
			compute: function colorCompute(from, to, d) {
				from = Fx.Style.Parsers.Color.hexToRGB(from); to = Fx.Style.Parsers.Color.hexToRGB(to);
				return '#'+rgbToHex.apply(null,[0, 1, 2].map(function map(i){
					return Fx.Base.compute(from[i], to[i], d)
				}));
			}
		}
	}
});

/**
 * Fx.Fade
 * 
 * Parameters:
 * elm			element		The HTML node to perform the fade on
 * options		object		An object to hold optional values
 * 
 * Options (Inherited from Fx.Base and Fx.Style)
 * 
 * Example Usage:
 * new Fx.Fade(did('divFade'), { duration: 800, onComplete: function(){ alert('Done!'); } }).fadeOut();
 */
Fx.Fade = new Activa.Class({
	Extends: Fx.Style,
	init:function init(elm,options){
		this.root(elm,options);//parent call
		this.style = 'opacity';
	},
	start: function start() {
		var args = Activa.toArray(arguments);
		args.unshift('opacity');
		this.root.apply(this,args);
	},
	hide: function hide() {
		return this.set(0);
	},
	show: function show() {
		return this.set(1);
	},	
	fadeIn: function fadeIn() {
		return this.start(1);
	},
	fadeOut: function fadeOut() {
		return this.start(0);
	},
	toggle: function toggle() {
		if ( this.getOpacity() == 1 ) {
			this.inout = 'out';
			this.fadeOut();
		}else{
			this.inout = 'in';
			this.fadeIn();
		}
	}
});	

/**
 * Fx.Slide
 *  
 * Parameters:
 * elm			element		A HTML node to perform the effect on.
 * options		object		An object to hold the optional values.
 * 
 * options (Inherits Fx.Base options and Fx.Style):
 * mode			string		Whether to slide horizontally or vertically.  Accepts 'horizontal' or 'vertical'(default).
 * collapse		bool		If set to true the element will actually collapse during the fade.  If false, the space of the element is maintained.  Defaults to false.
 * 
 * Example usage:
 * var slide = new Fx.Slide(did('elm'), { mode: 'horizontal', duration: 5000 });
 * 		slide.slideIn(); 
 */
Fx.Slide = new Activa.Class({
	Extends: Fx.Style,
	options: {
		mode: 'vertical',
		collapse: false
	},
	set: function set(value) {
		this.elm.style[this.style] = value[0]+'px';
		if ( this.options.collapse ) {
			this.wrapper.style[this.layout] = value[1]+'px';
		}
	},
	compute: function compute(from, to, d ) {
		return [0,1].map(function computeMap(i) {
			return Fx.Base.compute(from[i], to[i], d);
		});
	},
	init: function init(elm, options) {
		this.wrapper = document.createElement('div');		
		this.wrapper.style.overflow = 'hidden';
		this.wrapper.className = 'wrapper';
		this.root(elm, options);
		this.container = this.elm.parentNode;
		if ( !Activa.DOM.hasClass(this.container, 'wrapper') ) { 
			this.container.insertBefore(this.wrapper, this.elm);
			this.wrapper.appendChild(this.elm);
		} else {
			this.wrapper = this.container;
			this.container = this.wrapper.parentNode;
		}
		this.container.style.position = 'static';
		
		switch ( this.options.mode ) {
			case 'vertical':
				this.type = 'offsetHeight';
				this.style = 'marginTop';
				this.layout = 'height';
			break;
			case 'horizontal':
				this.type = 'offsetWidth';
				this.style = 'marginLeft';
				this.layout = 'width';
			break;
		};
		this.sizeBox();
	},
	sizeBox: function sizeBox() {
		this.elm.style.display = 'block';
		this.wrapper.style.width = this.elm.offsetWidth+'px';
		this.wrapper.style.height = this.elm.offsetHeight+'px';
	},
	start: function start(direction) {
		this.sizeBox();
							
		var margin = parseInt( getStyle(this.elm, Fx.Style.cssStyle(this.style)) ),
			layout = parseInt( getStyle(this.elm, Fx.Style.cssStyle(this.layout)) );
		
		if ( isNaN(margin) ) {
			margin = 0;
		}
	
		if ( direction == 'toggle' ) {
			direction = margin == 0 ? 'out' : 'in';
		}
		
		var starting = this.elm[this.type];
		
		var which = direction == 'out' ? [-starting, 0] : [0, starting];
									
		var from = [margin, layout];
		this.root(false, from, which);
	},
	slideIn: function slideIn() {
		this.start('in');
	},
	slideOut: function slideOut() {
		this.start('out');
	},
	show: function show() {
		this.set(this.compute([0,0],[0,0],1));
	},
	hide: function hide() {
		var finished = -this.elm[this.type];
		this.set(this.compute([0,0],[finished,finished],1));
	},
	toggle: function toggle() {
		this.start('toggle');
	}
});

/**
 * Fx.Move
 * Requires an element to be absolutely or relatively positioned.  If neither, it forces absolute.
 * 
 * Parameters:
 * elm				element		Element to move
 * options			object		Options object.  Inherited from Style and Base
 * 
 * Example Usage:
 * new Fx.Move(did('element'), { duration: 5000 }).start({ x: 400, y: 300 });
 * new Fx.Move(did('element'), { duration: 5000 }).start({ y: 150 });
 */
Fx.Move = new Activa.Class({
	Extends: Fx.Style,
	start: function start(x, y) {
		if ( !['relative','absolute'].inArray(this.getStyle('position')) ){
			this.elm.style.position = 'absolute';
		}
		
		var coords = { x: x, y: y };
		if ( typeof x == 'object' ) {
			var coords = x;
		}
		
		this.root({ left: coords.x, top: coords.y });
	}
});

/**
 * Fx.Scroll
 * 
 * Parameters:
 * elm				element		Element to scroll.  Defaults to window.
 * options			object		Options object.  Inherited from Style and Base
 * 
 * Example Usage:
 * new Fx.Scroll().start(0,100);//Scrolls down the entire page to 100
 * new Fx.Scroll(did('element')).start(0,100); //Scrolls a specific element down to 100
 */

Fx.Scroll = new Activa.Class({
	Extends: Fx.Base,
	init: function init(elm, options) {
		this.elm = elm || window;
		this.root(options);
	},
	set: function set(value) {
		if ( this.elm == window ) {
			return window.scrollTo(value[0], value[1]);
		}
		this.elm.scrollLeft = value[0];
		this.elm.scrollTop = value[1];
	},
	compute: function compute(from, to, d) {
		return [0,1].map(function computeMap(i) {
			return Fx.Base.compute(from[i], to[i], d);
		});
	},
	start: function start(x, y) {
		if ( this.elm == window ) {
			var s = Activa.Dimensions.getScrollXY();
		} else {
			var s = { x: this.elm.scrollLeft, y: this.elm.scrollTop };
		}
		
		return this.root([s.x, s.y], [x, y]);
	},
	toTop: function toTop() {
		this.start(0, 0);
	},
	toBottom: function toBottom() {
		this.start(0, (this.elm.scrollHeight - this.elm.offsetHeight));
	}
});

var Slider = new Activa.Class({
	fx: null,
	container: null,
	slider: null,
	direction: null,
	options: {
	}, 
	
	init: function init(container, slider, options) {
		this.container = did(container);
		this.slider = did(slider);
		this.setOptions(options);
		this.fx = new Fx.Scroll(this.container);
		this.fx.options.onComplete = function() {
			this.fx.options.transition = Fx.Transitions.Sine.easeOut;
			var move = 50;
			this.calcDuration(move*2);
			if ( this.direction == 'left' ) {
				this.fx.start(this.getMove(move), 0);	
			} else if ( this.direction == 'right' ) {
				this.fx.start(this.getMove(-move), 0);
			}
			this.fx.options.transition = Fx.Transitions.Sine.easeInOut;
		}.bind(this);
	},
	setOptions: function setOptions(options) {
		options = options || {};
		for ( var k in this.options ) {
			this.options[k] = (k in options) ? options[k] : this.options[k];
		}
	},
	start: function start(pos) {
		if ( pos == 'left' ) {
			this.direction = 'left';
			this.calcDuration(this.container.scrollLeft, true);
			move = this.getScroll();
			this.fx.start(move, 0);
		} else {
			this.direction = 'right';
			this.calcDuration(this.container.scrollLeft);
			this.fx.start(0, 0);
		}
	},
	stop: function stop() {
		this.fx.stop();
		this.direction = null;
	},
	getMove: function(jump) {
		var cur = this.container.scrollLeft;
		var move = cur + jump;
		if ( move < 0 ) {
			return 0;
		} else if ( move >= this.getScroll() ) {
			return this.getScroll();
		} else {
			return move;
		}
	},
	calcDuration: function calcDuration(int, left) {
		if ( !left ) {
			left = false;
		}
		if ( int == 0 ) {
			int = this.getScroll();
		} else if ( left ) {
			int = this.getScroll() - int
		}
		//console.log(this.getScroll(), int, this.container.scrollLeft);
		var move = int * 5;
		if ( move < 400 ) {
			move = 500;
		}
		this.fx.options.duration = move;
	},
	getScroll: function() {
		return this.container.scrollWidth - this.container.offsetWidth;
	}
});

/**
 * LightBox - Builds and manages a set of elements that makeup a lightbox
 * options:
 * box					mixed		Element id or object that holds the content
 * overlay				mixed		Element id or object that acts as the overlay
 * closeButton			mixed		Element id or object that acts as a close button
 * boxClass				string		CSS class that can be added to the box
 * overlayClass			string		CSS class that can be added to the overlay
 * closeClass			string		CSS class that can be added to the close button (default: lbClose)
 * imgClass				string		CSS class used to identify images to preload
 * contentClass			string		CSS class used for content wrapper (default: lbContentWrapper)
 * frameClass			string		CSS class used for iframe wrapper (default: lbContentFrame)
 * autoRegisterEvents	boolean		Determines if click events for displaying images should be automatically created
 * makeCloseButton		boolean		Determines if a close button is created if one is not specified
 * hideOverlay			boolean		Prevents overlay from displaying when the lightbox is shown
 * onshow				function	Function to execute when the lightbox is shown
 * onhide				function	Function to execute when the lightbox is hidden
 * pinTop				boolean		Determines if the lightbox should pin it's top position in place (default: false)
 * pinLeft				boolean		Determines if the lightbox should pin it's left position in place (default: false)
 * autopin				boolean		Determines if the lightbox will automatically pin itself when it is larger than the viewable area. (default: true)
 *
 * @param	options		object		optional;Object specifying options
 * @return				LightBox	New LightBox instance
 */
var LightBox = new Activa.Class({
	_isShowing: false,
	_initialized: false,
	_isIE: false,
	_images: {},
	box: null,
	overlay: null,
	closeButton: null,
	options: {
		box: null,
		overlay: null,
		closeButton: null,
		boxClass: '',
		overlayClass: '',
		closeClass: 'lbClose',
		imgClass: '',
		contentClass: 'lbContentWrapper',
		frameClass: 'lbContentFrame',
		makeCloseButton: true,
		hideOverlay: false,
		autoRegisterEvents: true,
		pinTop: false,
		pinLeft: false,
		autopin: true
	},
	events: {
		onshow: null,
		onhide: null
	},
	init: function initLightBox(options) {
		options = options || {};
		this.setOptions(options);
		Activa.DOM.ready(function initialize() {
			this._isIE = window.ActiveXObject ? (window.XMLHttpRequest ? 7 : 6) : false;
			
			function isString(str) {
				return str && (typeof str == 'string' || (typeof str == 'object' && typeof str.valueOf() == 'string'));
			}
			
			var opts = this.options;
			var docbody = dbt('body')[0];
			
			//Get or create the main box
			if ( opts.box && isString(opts.box) ) {
				this.box = did(opts.box);
			} else if ( opts.box && typeof opts.box == 'object' ) {
				this.box = opts.box;
			}
			if ( !this.box ) {
				this.box = document.createElement('div');
				if ( opts.box ) {
					opts.box.id = opts.box;
				}
				docbody.insertBefore(this.box, Activa.DOM.first(docbody));
			}
			Activa.DOM.addClass(this.box, opts.boxClass);
			this.box.style.display = 'none';
			
			//Get or create the close button
			if ( opts.closeButton && isString(opts.closeButton) ) {
				this.closeButton = did(opts.closeButton);
			} else {
				this.closeButton = opts.closeButton;
			}
			if ( !this.closeButton && this.options.makeCloseButton ) {
				this.closeButton = document.createElement('a');
				if ( opts.closeButton ) {
					this.closeButton.id = opts.closeButton;
				}
				this.closeButton.innerHTML = 'Close X';
				this.box.appendChild(this.closeButton);
			}
			if ( this.closeButton ) {
				Activa.DOM.addClass(this.closeButton, this.options.closeClass);
			}
			
			//Get or create the overlay
			if ( opts.overlay && isString(opts.overlay) ) {
				this.overlay = did(opts.overlay);
			} else if ( opts.overlay && typeof opts.overlay == 'object' ) {
				this.overlay = opts.overlay;
			}
			if ( !this.overlay ) {
				this.overlay = document.createElement('div');
				if ( opts.overlay ) {
					this.overlay.id = opts.overlay;
				}
				docbody.insertBefore(this.overlay, this.box);
			}
			Activa.DOM.addClass(this.overlay, this.options.overlayClass);
			this.overlay.style.display = 'none';
			
			//Create the iframe shim to hide controls if we're using IE
			if ( this._isIE ) {
				this._shim = document.createElement('iframe');
				var overz = this.getStyle(this.overlay, 'z-index') - 1;
				this._shim.style.cssText = 'display: none;position: absolute;top: 0px;left: 0px;filter:progid:DXImageTransform.Microsoft.alpha(opacity=0);z-index:'+overz;
				this._shim.frameBorder = 0;
				this._shim.scrolling = 'no';
				this._shim.src = dbt('base')[0].href + 'blankpage';
				docbody.insertBefore(this._shim, this.overlay);
			}
			
			//Register necessary events
			Activa.registerEvent(window, 'resize', this._createCaller(this, this._resize));
			Activa.registerEvent(window, 'scroll', this._createCaller(this, this._resize));
			Activa.registerEvent(this.overlay, 'click', this._createCaller(this, this.hide));
			if ( this.closeButton ) {
				Activa.registerEvent(this.closeButton, 'click', this._createCaller(this, this.hide));
			}
			
			//Register lightbox custom events
			for ( var eName in this.events ) {
				if ( this.events.hasOwnProperty(eName) && typeof options[eName] == 'function' ) {
					this.events[eName] = options[eName];
					this.listen(eName, this.events[eName]);
				}
			}
			
			//Preload images.
			this.preloadImages();
			this.initialized = true;
		}, this);
	},
	setOptions: function setOptions(options) {
		options = options || {};
		for ( var k in this.options ) {
			if ( k in options ) {
				if ( /Class/i.test(k) ) {
					options[k] = String(options[k]).trim();
				}
				this.options[k] = options[k];
			}
		}
	},
	showHTML: function showHTML(html) {
		if ( !this.initialized ) {
			return false;
		}
		this._createWrapper(html);
		this.show();
		return this;
	},
	showDOM: function showDOM(node, clone) {
		if ( !this.initialized ) {
			return false;
		}
		this._createWrapper();
		clone = clone == null ? true : clone;
		if ( node && node.nodeType == 1 && !Activa.DOM.contains(this.content, node) ) {
			if ( clone ) {
				var nodeClone = node.cloneNode(true);
				this.content.appendChild(nodeClone);
				if ( nodeClone.style.display == 'none' ) {
					nodeClone.style.display = '';
				}
			} else {
				this.content.appendChild(node);
			}
		}
		this.show();
		this._resize();
		return this;
	},
	showIMG: function showIMG(src, width, height, alt) {
		if ( !this.initialized ) {
			return false;
		}
		this._createWrapper('<div>Loading Image...</div>');
		
		this.preloadImage(src, function displayImage(e) {
			var img = e.target;
			var attributes = {'width':(width || img.width), 'height':(height || img.height), 'alt':(alt || '')};
			for ( var atr in attributes ) {
				if ( attributes.hasOwnProperty(atr) ) {
					img.setAttribute(atr, attributes[atr]);
				}
			}
			this.content.innerHTML = '';
			this.showDOM(img, false);
		});
		return this;
	},
	showURL: function showURL(url) {
		if ( !this.initialized ){
			return false;
		}
		if ( this.content ){
			this.content.style.display = 'none';
		}
		if ( !this.frame ) {
			this.options.frameClass = this.options.frameClass || 'lbContentFrame';
			this.frame = document.createElement('iframe');
			this.frame.className = this.options.frameClass;
			this.frame.frameBorder = 0;
			this.box.appendChild(this.frame);
		}
		
		this.frame.src = url;
		this.frame.style.display = '';
		this.show();
		return this;
	},
	clear: function clear() {
		this.content.innerHTML = '';
		return this;
	},
	show: function show() {
		this._resize();
		this.box.style.display = '';
		if ( !this.options.hideOverlay ) {
			this.overlay.style.display = '';
		}
		if ( this._shim ) {
			this._shim.style.display = '';
		}
		if(!this._isShowing) {
			this._triggerEvent(this.box, 'focus', false, false);
		}
		this._isShowing = true;
		return this;
	},
	hide: function hide() {
		this.box.style.display = 'none';
		this.overlay.style.display = 'none';
		if ( this._shim ) {
			this._shim.style.display = 'none';
		}
		if ( this._isShowing ) {
			this._triggerEvent(this.box, 'blur', false, false);
		}
		this._isShowing = false;
		return this;
	},
	toggle: function toggle() {
		var fn = this._isShowing ? 'hide':'show';
		this[fn]();
	},
	pin: function pin() {
		var val = Activa.toArray(arguments).shift();
		switch(val) {
			case 'top':
				this.options.pinTop = true;
				break;
			case 'left':
				this.options.pinLeft = true;
				break;
			default:
				this.options.pinTop = true;
				this.options.pinLeft = true;
		}
	},
	unpin: function unpin() {
		var val = Activa.toArray(arguments).shift();
		switch(val) {
			case 'top':
				this.options.pinTop = false;
				break;
			case 'left':
				this.options.pinLeft = false;
				break;
			default:
				this.options.pinTop = false;
				this.options.pinLeft = false;
		}
	},
	preloadImages: function preloadImages(className) {
		className = (className ? String(className) : this.options.imgClass).trim();
		if(className == '') {
			return false;
		}
		var nodes = Activa.toArray(dbc(className));
		nodes.forEach(this.preloadImage, this);
	},
	preloadImage: function preloadImage(src, fn) {
		var node;
		var isobj = (typeof ((this._isIE) ? src : src.valueOf()) == 'object');
		if ( isobj && src.nodeType == 1 ) {
			node = src;
			var tag = node.tagName.toLowerCase();
			if ( tag == 'a' && node.href != '' ) {
				src = node.href;
			} else if ( tag == 'img' && node.src != '' ) {
				src = node.src;
			}
		}
		src = (src ? String(src) : '').trim();
		var fname = src.replace(/^(?:https?:\/\/|(\/))?(.*)$/i, '$1$2');
		if(!src || !fname) {
			return null;
		}
		
		if ( !this._images[fname] ) {
			this._images[fname] = new Image();
			if ( fn && typeof fn == 'function' ) {
				var self = this;
				Activa.registerEvent(this._images[fname], 'load', function imgLoad(e){
					if ( !e.target ) {
						e = {target: this._images[fname]};
					}
					Activa.unregisterEvent(e.target, 'load', arguments.callee);
					fn.call(this, e);
				}.bind(this));
			}
			this._images[fname].src = src;
		} else if ( typeof fn == 'function' ) {
			fn.call(this, {target: this._images[fname]});
		}
		if ( this.options.autoRegisterEvents && node ) {
			Activa.registerEvent(node, 'click', this._createCaller(this, this.showIMG, true, fname, '', '', ''));
		}
		return (fname in this._images) ? this._images[fname] : null;
	},
	_createWrapper: function _createWrapper(html) {
		if ( !this.initialized ){
			return false;
		}
		if ( this.frame ){ this.frame.style.display = 'none'; }
		if ( !this.content ) {
			this.options.contentClass = this.options.contentClass || 'lbContentWrapper';
			this.content = document.createElement('div');
			this.content.className = this.options.contentClass;
			this.box.appendChild(this.content);
		}
		html = html || '';
		if ( html != '' ) {
			this.content.innerHTML = String(html);
		}
		this.content.style.display = '';
	},
	_resize: function _resize() {
		var max = Activa.Dimensions.getMaxDocSize();
		if ( !this.options.hideOverlay ) {
			['overlay','shim'].forEach(function(item){
				if(this[item]) {
					this[item].style.width = max['width']+'px';
					this[item].style.height = max['height']+'px';
				}
			}, this);
		}
		
		var docsize = Activa.Dimensions.getDocSize(), scroll = Activa.Dimensions.getScrollXY();
		if ( !this._isShowing ) {
			this.box.style.visibility = 'hidden';
			this.box.style.display = '';
		}
		//var boxWidth = this._isIE ? this.box.offsetWidth : getStyle(this.box,'width')
		//var boxHeight = this._isIE ? this.box.offsetHeight : getStyle(this.box,'height');
		var boxWidth = this.box.offsetWidth;
		var boxHeight = this.box.offsetHeight;
		if ( !this._isShowing ) {
			this.box.style.display = 'none';
			this.box.style.visibility = 'visible';
		}
		boxWidth = isNaN(boxWidth) ? 0 : boxWidth;
		boxHeight = isNaN(boxHeight) ? 0 : boxHeight;
		var topleft = {
			'x':Math.round((docsize['width']-boxWidth)/2)+scroll['x'],
			'y':Math.round((docsize['height']-boxHeight)/2)+scroll['y']
		};
		if ( boxWidth > docsize['width'] ) {
			topleft.x = 0;
		}
		var pintop = this.options.autopin ? (boxHeight > docsize['height']) : false;
		var pinleft = this.options.autopin ? (boxWidth > docsize['width']) : false;
		this._setPosition(topleft.x, topleft.y, pinleft, pintop);
		return this;		 
	},
	_setPosition: function _setPosition(x, y, pinx, piny) {
		var pin = false;
		if ( this.options.autopin && !(pinx && piny) ) {
			pin = (pinx ? 'top' : (piny ? 'left' : false));
			this.unpin(pin);
		}
		x = (x || 0) < 0 ? 0 : x;
		y = (y || 0) < 0 ? 0 : y; 
		if ( !this.options.pinLeft && x != 'auto' ) {
			this.box.style.left = parseInt(x)+'px';
		}
		if ( !this.options.pinTop && y != 'auto' ) {
			this.box.style.top = parseInt(y)+'px';
		}
		if ( this.options.autopin && (pinx || piny) ) {
			pin = (pinx && piny) ? true : (pinx ? 'left' : (piny ? 'top': false));
			this.pin(pin);
		}
		if ( this.options.hideOverlay && this._shim ) {
			this._shim.style.position = 'absolute';
			['width','height','left','top'].forEach(function(prop) {
				var value = this.box.style[prop];
				if ( !value ) {
					value = this.getStyle(this.box, prop);
				}
				this._shim.style[prop] = value;
			}, this);
		}
	},
	getStyle: function getStyle(obj, prop) {
		obj = (typeof obj == 'object') ? obj : did(obj);
		if ( !obj || !prop ) {
			return null;
		}
		var compVal = obj.currentStyle ? obj.currentStyle[prop] : window.getComputedStyle(obj,null).getPropertyValue(prop);
		return isNaN(parseFloat(compVal)) ? compVal : parseFloat(compVal);
	},
	listen: function listen(event, fn) {
		if ( typeof fn != 'function' ) {
			return false;
		}
		var eventWrapper = (function eventWrapper(func, stopDefault, stopBubble) {
			return (function wrapping(e) {
				if(stopDefault) {
					e.preventDefault();
				}
				if(stopBubble) {
					e.stopPropagation();
				}
				func.call(this, e);
			}).bind(this);
		}).bind(this);
		switch(event) {
			case 'onshow':
				Activa.registerEvent(this.box, 'focus', eventWrapper(fn, true, true));
				break;
			case 'onhide':
				Activa.registerEvent(this.box, 'blur', eventWrapper(fn, true, true));
				break;
			default:
				break;
		}
	},
	_createCaller: function _createCaller(obj, fn, stopDefault){
		var args = Activa.toArray(arguments, 3);
		return function callMethod(e) {
			if ( stopDefault ) {
				e.preventDefault();
			}
			fn.apply(obj, args.concat([e]));
		}
	},
	_triggerEvent: function _triggerEvent(el, type, bubbles, cancelable) {
		try {
			el = Activa.DOM.check(el);
			bubbles = bubbles || true;
			cancelable = cancelable || true;
			if ( document.createEvent  ) {
				var groups = {
					UIEvents: ['focusin', 'focusout', 'activate', 'deactivate'],
					MouseEvents: ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove'],
					HTMLEvents: ['load', 'unload', 'abort', 'error', 'select', 'change', 'submit', 'reset', 'focus', 'blur', 'resize', 'scroll']
				};
				var groupName = null;
				for ( var group in groups ) {
					if ( groups[group].indexOf(type) != -1 ) {
						groupName = group;
						break;
					}
				}
				if ( groupName ) {
					var event = document.createEvent(groupName);
					event.initEvent(type, bubbles, cancelable);
					//safari 3 doesn't have window.dispatchEvent()
					(el == window && !el.dispatchEvent ? document : el).dispatchEvent(event);
					return true;
				}
			} else if ( document.createEventObject ) {
				var event = document.createEventObject();
				// IE6,IE7 thinks window==document and doesn't have window.fireEvent()
				// IE6,IE7 cannot properly call document.fireEvent()
				(el == document ? document.documentElement : el).fireEvent("on"+type, event);
				return true;
			}
		} catch ( e ) {
		}
		return false;
	}
});


