/*MooTools, My Object Oriented Javascript Tools. Copyright (c) 2006-2007 Valerio Proietti, <http://mad4milk.net>, MIT Style License.||Clientcide Copyright (c) 2006-2008, http://www.clientcide.com/wiki/cnet-libraries#license*/

var Clientcide = {
	version: '806',
	setAssetLocation: function(baseHref) {
		if (window.StickyWin && StickyWin.ui) {
			StickyWin.UI.refactor({
				options: {
					baseHref: baseHref + '/stickyWinHTML/'
				}
			});
			if (StickyWin.alert) {
				var CGFsimpleErrorPopup = StickyWin.alert.bind(window);
				StickyWin.alert = function(msghdr, msg, base) {
				    return CGFsimpleErrorPopup(msghdr, msg, base||baseHref + "/simple.error.popup");
				};
			}
			if (StickyWin.UI.Pointy) {
				StickyWin.UI.Pointy.refactor({
					options: {
						baseHref: baseHref + '/PointyTip/'
					}
				});
			}
		}
		if (window.TagMaker) {
			TagMaker = TagMaker.refactor({
			    options: {
			        baseHref: baseHref + '/tips/'
			    }
			});
		}
		if (window.ProductPicker) {
			ProductPicker.refactor({
			    options:{
			        baseHref: baseHref + '/Picker'
			    }
			});
		}

		if (window.Autocompleter) {
			var AcClientcide = {
					options: {
						baseHref: baseHref + '/autocompleter/'
					}
			};
			Autocompleter.Base.refactor(AcClientcide);
			if (Autocompleter.Ajax) {
				["Base", "Xhtml", "Json"].each(function(c){
					if(Autocompleter.Ajax[c]) Autocompleter.Ajax[c].refactor(AcClientcide);
				});
			}
			if (Autocompleter.Local) Autocompleter.Local.refactor(AcClientcide);
			if (Autocompleter.JsonP) Autocompleter.JsonP.refactor(AcClientcide);
		}

		if (window.Lightbox) {
			Lightbox.refactor({
			    options: {
			        assetBaseUrl: baseHref + '/slimbox/'
			    }
			});
		}

		if (window.Waiter) {
			Waiter.refactor({
				options: {
					baseHref: baseHref + '/waiter/'
				}
			});
		}
	},
	preLoadCss: function(){
		if (window.DatePicker) new DatePicker();
		if (window.ProductPicker) new ProductPicker();
		if (window.TagMaker) new TagMaker();
		if (window.StickyWin && StickyWin.ui) StickyWin.ui();
		if (window.StickyWin && StickyWin.pointy) StickyWin.pointy();
		Clientcide.preloaded = true;
		return true;
	},
	preloaded: false
};
(function(){
	if (!window.addEvent) return;
	var preload = function(){
		if (window.dbug) dbug.log('preloading clientcide css');
		if (!Clientcide.preloaded) Clientcide.preLoadCss();
	};
	window.addEvent('domready', preload);
	window.addEvent('load', preload);
})();
setCNETAssetBaseHref = Clientcide.setAssetLocation;
var dbug = {
	logged: [],
	timers: {},
	firebug: false, 
	enabled: false, 
	log: function() {
		dbug.logged.push(arguments);
	},
	nolog: function(msg) {
		dbug.logged.push(arguments);
	},
	time: function(name){
		dbug.timers[name] = new Date().getTime();
	},
	timeEnd: function(name){
		if (dbug.timers[name]) {
			var end = new Date().getTime() - dbug.timers[name];
			dbug.timers[name] = false;
			dbug.log('%s: %s', name, end);
		} else dbug.log('no such timer: %s', name);
	},
	enable: function(silent) { 
		var con = window.firebug ? firebug.d.console.cmd : window.console;

		if((!!window.console && !!window.console.warn) || window.firebug) {
			try {
				dbug.enabled = true;
				dbug.log = function(){
						(con.debug || con.log).apply(con, arguments);
				};
				dbug.time = function(){
					con.time.apply(con, arguments);
				};
				dbug.timeEnd = function(){
					con.timeEnd.apply(con, arguments);
				};
				if(!silent) dbug.log('enabling dbug');
				for(var i=0;i<dbug.logged.length;i++){ dbug.log.apply(con, dbug.logged[i]); }
				dbug.logged=[];
			} catch(e) {
				dbug.enable.delay(400);
			}
		}
	},
	disable: function(){ 
		if(dbug.firebug) dbug.enabled = false;
		dbug.log = dbug.nolog;
		dbug.time = function(){};
		dbug.timeEnd = function(){};
	},
	cookie: function(set){
		var value = document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');
		var debugCookie = value ? unescape(value[1]) : false;
		if((!$defined(set) && debugCookie != 'true') || ($defined(set) && set)) {
			dbug.enable();
			dbug.log('setting debugging cookie');
			var date = new Date();
			date.setTime(date.getTime()+(24*60*60*1000));
			document.cookie = 'jsdebug=true;expires='+date.toGMTString()+';path=/;';
		} else dbug.disableCookie();
	},
	disableCookie: function(){
		dbug.log('disabling debugging cookie');
		document.cookie = 'jsdebug=false;path=/;';
	}
};

(function(){
	var fb = !!window.console || !!window.firebug;
	var con = window.firebug ? window.firebug.d.console.cmd : window.console;
	var debugMethods = ['debug','info','warn','error','assert','dir','dirxml'];
	var otherMethods = ['trace','group','groupEnd','profile','profileEnd','count'];
	function set(methodList, defaultFunction) {
		for(var i = 0; i < methodList.length; i++){
			dbug[methodList[i]] = (fb && con[methodList[i]])?con[methodList[i]]:defaultFunction;
		}
	};
	set(debugMethods, dbug.log);
	set(otherMethods, function(){});
})();
if ((!!window.console && !!window.console.warn) || window.firebug){
	dbug.firebug = true;
	var value = document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');
	var debugCookie = value ? unescape(value[1]) : false;
	if(window.location.href.indexOf("jsdebug=true")>0 || debugCookie=='true') dbug.enable();
	if(debugCookie=='true')dbug.log('debugging cookie enabled');
	if(window.location.href.indexOf("jsdebugCookie=true")>0){
		dbug.cookie();
		if(!dbug.enabled)dbug.enable();
	}
	if(window.location.href.indexOf("jsdebugCookie=false")>0)dbug.disableCookie();
}
var Occlude = new Class({
	occlude: function(property, element) {
		element = $(element || this.element);
		var instance = element.retrieve(property || this.property);
		if (instance && (this.occluded === null || this.occluded)) {
			this.occluded = instance; 
		} else {
			this.occluded = false;
			element.store(property || this.property, this);
		}
		return this.occluded||false;
	}
});
var ToElement = new Class({
	toElement: function(){
		return this.element;
	}
});
var IframeShim = new Class({
	Implements: [Options, Events, Occlude, ToElement],
	options: {
		className:'iframeShim',
		display:false,
		zindex: null,
		margin: 0,
		offset: {
			x: 0,
			y: 0
		},
		browsers: (Browser.Engine.trident4 || (Browser.Engine.gecko && !Browser.Engine.gecko19 && Browser.Platform.mac))
	},
	property: 'IframeShim',
	initialize: function (element, options){
		this.element = $(element);
		if (this.occlude()) return this.occluded;
		this.setOptions(options);
		this.makeShim();
		return;
	},
	makeShim: function(){
		this.shim = new Element('iframe').store('IframeShim', this);
		if(!this.options.browsers) return;
		var pos = this.element.getStyle('position');
		if (pos == "static" || !pos) this.element.setStyle('position', 'relative');
		if(this.element.getStyle('z-Index').toInt()<1 || isNaN(this.element.getStyle('z-Index').toInt()))
			this.element.setStyle('z-Index',5);
		var z = this.element.getStyle('z-Index')-1;

		if($chk(this.options.zindex) && 
			 this.element.getStyle('z-Index').toInt() > this.options.zindex)
			 z = this.options.zindex;

 		this.shim.set({
			src: (window.location.protocol == 'https') ? '://0' : 'javascript:void(0)',
			frameborder:'0',
			scrolling:'no',
			styles: {
				position: 'absolute',
				zIndex: z,
				border: 'none',
				filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
			},
			'class':this.options.className
		});

		var inject = function(){
			this.shim.inject(this.element, 'after');
			if(this.options.display) this.show();
			else this.hide();
			this.fireEvent('onInject');
		};
		if(Browser.Engine.trident && !IframeShim.ready) window.addEvent('load', inject.bind(this));
		else inject.run(null, this);
	},
	position: function(shim){
		if(!this.options.browsers || !IframeShim.ready) return this;
		var size = this.element.measure(function(){ return this.getSize(); });
		if($type(this.options.margin)){
			size.x = size.x-(this.options.margin*2);
			size.y = size.y-(this.options.margin*2);
			this.options.offset.x += this.options.margin; 
			this.options.offset.y += this.options.margin;
		}
 		this.shim.set({
			'width': size.x,
			'height': size.y
		}).setPosition({
			relativeTo: this.element,
			offset: this.options.offset
		});
		return this;
	},
	hide: function(){
		if(this.options.browsers) this.shim.hide();
		return this;
	},
	show: function(){
		if(!this.options.browsers) return this;
		this.shim.show();
		return this.position();
	},
	dispose: function(){
		if(this.options.browsers) this.shim.dispose();
		return this;
	}
});
window.addEvent('load', function(){
	IframeShim.ready = true;
});

new Native({name: 'Date', initialize: Date, protect: true});
['now','parse','UTC'].each(function(method){
	Native.genericize(Date, method, true);
});
Date.$Methods = new Hash();
["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds", "Time", "TimezoneOffset", 
	"Week", "Timezone", "GMTOffset", "DayOfYear", "LastMonth", "UTCDate", "UTCDay", "UTCFullYear",
	"AMPM", "UTCHours", "UTCMilliseconds", "UTCMinutes", "UTCMonth", "UTCSeconds"].each(function(method) {
	Date.$Methods.set(method.toLowerCase(), method);
});
$each({
	ms: "Milliseconds",
	year: "FullYear",
	min: "Minutes",
	mo: "Month",
	sec: "Seconds",
	hr: "Hours"
}, function(value, key){
	Date.$Methods.set(key, value);
});
Date.implement({
	set: function(key, value) {
		key = key.toLowerCase();
		var m = Date.$Methods;
		if (m.has(key)) this['set'+m.get(key)](value);
		return this;
	},
	get: function(key) {
		key = key.toLowerCase();
		var m = Date.$Methods;
		if (m.has(key)) return this['get'+m.get(key)]();
		return null;
	},
	clone: function() {
		return new Date(this.get('time'));
	},
	increment: function(interval, times) {
		return this.multiply(interval, times);
	},
	decrement: function(interval, times) {
		return this.multiply(interval, times, false);
	},
	multiply: function(interval, times, increment){
		interval = interval || 'day';
		times = $pick(times, 1);
		increment = $pick(increment, true);
		var multiplier = increment?1:-1;
		var month = this.format("%m").toInt()-1;
		var year = this.format("%Y").toInt();
		var time = this.get('time');
		var offset = 0;
		switch (interval) {
				case 'year':
					times.times(function(val) {
						if (Date.isLeapYear(year+val) && month > 1 && multiplier > 0) val++;
						if (Date.isLeapYear(year+val) && month <= 1 && multiplier < 0) val--;
						offset += Date.$units.year(year+val);
					});
					break;
				case 'month':
					times.times(function(val){
						if (multiplier < 0) val++;
						var mo = month+(val*multiplier);
						var year = year;
						if (mo < 0) {
							year--;
							mo = 12+mo;
						}
						if (mo > 11 || mo < 0) {
							year += (mo/12).toInt()*multiplier;
							mo = mo%12;
						}
						offset += Date.$units.month(mo, year);
					});
					break;
				case 'day':
					return this.set('date', this.get('date')+(multiplier*times));
				default:
					offset = Date.$units[interval]()*times;
					break;
		}
		this.set('time', time+(offset*multiplier));
		return this;
	},
	isLeapYear: function() {
		return Date.isLeapYear(this.get('year'));
	},
	clearTime: function() {
		['hr', 'min', 'sec', 'ms'].each(function(t){
			this.set(t, 0);
		}, this);
		return this;
	},
	diff: function(d, resolution) {
		resolution = resolution || 'day';
		if($type(d) == 'string') d = Date.parse(d);
		switch (resolution) {
			case 'year':
				return d.format("%Y").toInt() - this.format("%Y").toInt();
				break;
			case 'month':
				var months = (d.format("%Y").toInt() - this.format("%Y").toInt())*12;
				return months + d.format("%m").toInt() - this.format("%m").toInt();
				break;
			default:
				var diff = d.get('time') - this.get('time');
				if (diff < 0 && Date.$units[resolution]() > (-1*(diff))) return 0;
				else if (diff >= 0 && diff < Date.$units[resolution]()) return 0;
				return ((d.get('time') - this.get('time')) / Date.$units[resolution]()).round();
		}
	},
	getWeek: function() {
		var day = (new Date(this.get('year'), 0, 1)).get('date');
		return Math.round((this.get('dayofyear') + (day > 3 ? day - 4 : day + 3)) / 7);
	},
	getTimezone: function() {
		return this.toString()
			.replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
			.replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
	},
	getGMTOffset: function() {
		var off = this.get('timezoneOffset');
		return ((off > 0) ? '-' : '+')
			+ Math.floor(Math.abs(off) / 60).zeroise(2)
			+ (off % 60).zeroise(2);
	},
	parse: function(str) {
		this.set('time', Date.parse(str));
		return this;
	},
	format: function(f) {
		f = f || "%x %X";
		if (!this.valueOf()) return 'invalid date';
		if (Date.$formats[f.toLowerCase()]) f = Date.$formats[f.toLowerCase()];
		var d = this;
		return f.replace(/\%([aAbBcdHIjmMpSUWwxXyYTZ])/g,
			function($1, $2) {
				switch ($2) {
					case 'a': return Date.$days[d.get('day')].substr(0, 3);
					case 'A': return Date.$days[d.get('day')];
					case 'b': return Date.$months[d.get('month')].substr(0, 3);
					case 'B': return Date.$months[d.get('month')];
					case 'c': return d.toString();
					case 'd': return d.get('date').zeroise(2);
					case 'H': return d.get('hr').zeroise(2);
					case 'I': return ((d.get('hr') % 12) || 12);
					case 'j': return d.get('dayofyear').zeroise(3);
					case 'm': return (d.get('mo') + 1).zeroise(2);
					case 'M': return d.get('min').zeroise(2);
					case 'p': return d.get('hr') < 12 ? 'AM' : 'PM';
					case 'S': return d.get('seconds').zeroise(2);
					case 'U': return d.get('week').zeroise(2);
					case 'W': throw new Error('%W is not supported yet');
					case 'w': return d.get('day');
					case 'x': 
						var c = Date.$cultures[Date.$culture];
						return d.format('%' + c[0].substr(0,1) +
							c[3] + '%' + c[1].substr(0,1) +
							c[3] + '%' + c[2].substr(0,1).toUpperCase());
					case 'X': return d.format('%I:%M%p');
					case 'y': return d.get('year').toString().substr(2);
					case 'Y': return d.get('year');
					case 'T': return d.get('GMTOffset');
					case 'Z': return d.get('Timezone');
					case '%': return '%';
				}
				return $2;
			}
		);
	},
	setAMPM: function(ampm){
		ampm = ampm.toUpperCase();
		if (this.format("%H").toInt() > 11 && ampm == "AM") 
			return this.decrement('hour', 12);
		else if (this.format("%H").toInt() < 12 && ampm == "PM")
			return this.increment('hour', 12);
		return this;
	}
});

Date.prototype.compare = Date.prototype.diff;
Date.prototype.strftime = Date.prototype.format;

Date.$nativeParse = Date.parse;

$extend(Date, {
	$months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
	$days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
	$daysInMonth: function(monthIndex, year) {
		if (Date.isLeapYear(year.toInt()) && monthIndex === 1) return 29;
		return [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][monthIndex];
	},
	$epoch: -1,
	$era: -2,
	$units: {
		ms: function(){return 1},
		second: function(){return 1000},
		minute: function(){return 60000},
		hour: function(){return 3600000},
		day: function(){return 86400000},
		week: function(){return 608400000},
		month: function(monthIndex, year) {
			var d = new Date();
			return Date.$daysInMonth($pick(monthIndex,d.format("%m").toInt()), $pick(year,d.format("%Y").toInt())) * 86400000;
		},
		year: function(year){
			year = year || new Date().format("%Y").toInt();
			return Date.isLeapYear(year.toInt())?31622400000:31536000000;
		}
	},
	$formats: {
		db: '%Y-%m-%d %H:%M:%S',
		compact: '%Y%m%dT%H%M%S',
		iso8601: '%Y-%m-%dT%H:%M:%S%T',
		rfc822: '%a, %d %b %Y %H:%M:%S %Z',
		'short': '%d %b %H:%M',
		'long': '%B %d, %Y %H:%M'
	},

	isLeapYear: function(yr) {
		return new Date(yr,1,29).getDate()==29;
	},

	parseUTC: function(value){
		var localDate = new Date(value);
		var utcSeconds = Date.UTC(localDate.get('year'), localDate.get('mo'),
		localDate.get('date'), localDate.get('hr'), localDate.get('min'), localDate.get('sec'));
		return new Date(utcSeconds);
	},

	parse: function(from) {
		var type = $type(from);
		if (type == 'number') return new Date(from);
		if (type != 'string') return from;
		if (!from.length) return null;
		for (var i = 0, j = Date.$parsePatterns.length; i < j; i++) {
			var r = Date.$parsePatterns[i].re.exec(from);
			if (r) {
				try {
					return Date.$parsePatterns[i].handler(r);
				} catch(e) {
					dbug.log('date parse error: ', e);
					return null;
				}
			}
		}
		return new Date(Date.$nativeParse(from));
	},

	parseMonth: function(month, num) {
		var ret = -1;
		switch ($type(month)) {
			case 'object':
				ret = Date.$months[month.get('mo')];
				break;
			case 'number':
				ret = Date.$months[month - 1] || false;
				if (!ret) throw new Error('Invalid month index value must be between 1 and 12:' + index);
				break;
			case 'string':
				var match = Date.$months.filter(function(name) {
					return this.test(name);
				}, new RegExp('^' + month, 'i'));
				if (!match.length) throw new Error('Invalid month string');
				if (match.length > 1) throw new Error('Ambiguous month');
				ret = match[0];
		}
		return (num) ? Date.$months.indexOf(ret) : ret;
	},

	parseDay: function(day, num) {
		var ret = -1;
		switch ($type(day)) {
			case 'number':
				ret = Date.$days[day - 1] || false;
				if (!ret) throw new Error('Invalid day index value must be between 1 and 7');
				break;
			case 'string':
				var match = Date.$days.filter(function(name) {
					return this.test(name);
				}, new RegExp('^' + day, 'i'));
				if (!match.length) throw new Error('Invalid day string');
				if (match.length > 1) throw new Error('Ambiguous day');
				ret = match[0];
		}
		return (num) ? Date.$days.indexOf(ret) : ret;
	},

	fixY2K: function(d){
		if (!isNaN(d)) {
			var newDate = new Date(d);
			if (newDate.get('year') < 2000 && d.toString().indexOf(newDate.get('year')) < 0) {
				newDate.increment('year', 100);
			}
			return newDate;
		} else return d;
	},

	$cultures: {
		'US': ['month', 'date', 'year', '/'],
		'GB': ['date', 'month', 'year', '/']
	},

	$culture: 'US',

	$language: 'enUS',

	$cIndex: function(unit){
		return Date.$cultures[Date.$culture].indexOf(unit)+1;
	},

	$parsePatterns: [
		{
			re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})$/,
			handler: function(bits){
				var d = new Date();
				var culture = Date.$cultures[Date.$culture];
				d.set('year', bits[Date.$cIndex('year')]);
				d.set('date', bits[Date.$cIndex('date')]);
				d.set('month', bits[Date.$cIndex('month')] - 1);
				return Date.fixY2K(d);
			}
		},
		{
			re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})\s(\d{1,2}):(\d{1,2})(\w{2})$/,
			handler: function(bits){
				var d = new Date();
				d.set('year', bits[Date.$cIndex('year')]);
				d.set('date', bits[Date.$cIndex('date')]);
				d.set('month', bits[Date.$cIndex('month')] - 1);
				d.set('hr', bits[4]);
				d.set('min', bits[5]);
				d.set('ampm', bits[6]);
				return Date.fixY2K(d);
			}
		},
		{
			re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})\s(\d{1,2}):(\d{1,2}):(\d{1,2})/,
			handler: function(bits) {
				var d = new Date();
				var culture = Date.$cultures[Date.$culture];
				d.set('year', bits[Date.$cIndex('year')]);
				d.set('date', bits[Date.$cIndex('date')]);
				d.set('month', bits[Date.$cIndex('month')] - 1);
				d.set('hours', bits[4]);
				d.set('minutes', bits[5]);
				d.set('seconds', bits[6]);
				return Date.fixY2K(d);
			}
		}
	]
});

Number.implement({
	zeroise: function(length) {
		return String(this).zeroise(length);
	}
});

String.implement({
	repeat: function(times) {
		var ret = [];
		for (var i = 0; i < times; i++) ret.push(this);
		return ret.join('');
	},
	zeroise: function(length) {
		return '0'.repeat(length - this.length) + this;
	}

});

Hash.implement({
	getFromPath: function(notation) {
		var source = this.getClean();
		notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match) {
			if (!source) return;
			var prop = arguments[2] || arguments[1] || arguments[0];
			source = (prop in source) ? source[prop] : null;
			return match;
		});
		return source;
	},
	cleanValues: function(method){
		method = method||$defined;
		this.each(function(v, k){
			if (!method(v)) this.erase(k);
		}, this);
		return this;
	},
	run: function(){
		var args = $arguments;
		this.each(function(v, k){
			if ($type(v) == "function") v.run(args);
		});
	}
});
String.implement({
	stripTags: function() {
		return this.replace(/<\/?[^>]+>/gi, '');
	},
	parseQuery: function(encodeKeys, encodeValues) {
		encodeKeys = $pick(encodeKeys, true);
		encodeValues = $pick(encodeValues, true);
		var vars = this.split(/[&;]/);
		var rs = {};
		if (vars.length) vars.each(function(val) {
			var keys = val.split('=');
			if (keys.length && keys.length == 2) {
				rs[(encodeKeys)?encodeURIComponent(keys[0]):keys[0]] = (encodeValues)?encodeURIComponent(keys[1]):keys[1];
			}
		});
		return rs;
	},
	tidy: function() {
		var txt = this.toString();
		$each({
			"[\xa0\u2002\u2003\u2009]": " ",
			"\xb7": "*",
			"[\u2018\u2019]": "'",
			"[\u201c\u201d]": '"',
			"\u2026": "...",
			"\u2013": "-",
			"\u2014": "--",
			"\uFFFD": "&raquo;"
		}, function(value, key){
			txt = txt.replace(new RegExp(key, 'g'), value);
		});
		return txt;
	},
	cleanQueryString: function(method){
		return this.split("&").filter(method||function(set){
			return $chk(set.split("=")[1]);
		}).join("&");
	},
	findAllEmails: function(){
			return this.match(new RegExp("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", "gi")) || [];
	}
});
Element.implement({
	tidy: function(){
		this.set('value', this.get('value').tidy());
	},
	getTextInRange: function(start, end) {
		return this.get('value').substring(start, end);
	},
	getSelectedText: function() {
		if(Browser.Engine.trident) return document.selection.createRange().text;
		return this.get('value').substring(this.getSelectionStart(), this.getSelectionEnd());
	},
	getIERanges: function(){
		this.focus();
		var range = document.selection.createRange();
		var re = this.createTextRange();
		var dupe = re.duplicate();
		re.moveToBookmark(range.getBookmark());
		dupe.setEndPoint('EndToStart', re);
		return { start: dupe.text.length, end: dupe.text.length + range.text.length, length: range.text.length, text: range.text };
	},
	getSelectionStart: function() {
		if(Browser.Engine.trident) return this.getIERanges().start;
		return this.selectionStart;
	},
	getSelectionEnd: function() {
		if(Browser.Engine.trident) return this.getIERanges().end;
		return this.selectionEnd;
	},
	getSelectedRange: function() {
		return {
			start: this.getSelectionStart(),
			end: this.getSelectionEnd()
		}
	},
	setCaretPosition: function(pos) {
		if(pos == 'end') pos = this.get('value').length;
		this.selectRange(pos, pos);
		return this;
	},
	getCaretPosition: function() {
		return this.getSelectedRange().start;
	},
	selectRange: function(start, end) {
		this.focus();
		if(Browser.Engine.trident) {
			var range = this.createTextRange();
			range.collapse(true);
			range.moveStart('character', start);
			range.moveEnd('character', end - start);
			range.select();
			return this;
		}
		this.setSelectionRange(start, end);
		return this;
	},
	insertAtCursor: function(value, select) {
		var start = this.getSelectionStart();
		var end = this.getSelectionEnd();
		this.set('value', this.get('value').substring(0, start) + value + this.get('value').substring(end, this.get('value').length));
 		if($pick(select, true)) this.selectRange(start, start + value.length);
		else this.setCaretPosition(start + value.length);
		return this;
	},
	insertAroundCursor: function(options, select) {
		options = $extend({
			before: '',
			defaultMiddle: 'SOMETHING HERE',
			after: ''
		}, options);
		value = this.getSelectedText() || options.defaultMiddle;
		var start = this.getSelectionStart();
		var end = this.getSelectionEnd();
		if(start == end) {
			var text = this.get('value');
			this.set('value', text.substring(0, start) + options.before + value + options.after + text.substring(end, text.length));
			this.selectRange(start + options.before.length, end + options.before.length + value.length);
			text = null;
		} else {
			text = this.get('value').substring(start, end);
			this.set('value', this.get('value').substring(0, start) + options.before + text + options.after + this.get('value').substring(end, this.get('value').length));
			var selStart = start + options.before.length;
			if($pick(select, true)) this.selectRange(selStart, selStart + text.length);
			else this.setCaretPosition(selStart + text.length);
		}
		return this;
	}
});
Element.Properties.inputValue = {
 
    get: function(){
			 switch(this.get('tag')) {
			 	case 'select':
					vals = this.getSelected().map(function(op){ 
						var v = $pick(op.get('value'),op.get('text')); 
						return (v=="")?op.get('text'):v;
					});
					return this.get('multiple')?vals:vals[0];
				case 'input':
					switch(this.get('type')) {
						case 'checkbox':
							return this.get('checked')?this.get('value'):false;
						case 'radio':
							var checked;
							if (this.get('checked')) return this.get('value');
							$(this.getParent('form')||document.body).getElements('input').each(function(input){
								if (input.get('name') == this.get('name') && input.get('checked')) checked = input.get('value');
							}, this);
							return checked||null;
					}
			 	case 'input': case 'textarea':
					return this.get('value');
				default:
					return this.get('inputValue');
			 }
    },
 
    set: function(value){
			switch(this.get('tag')){
				case 'select':
					this.getElements('option').each(function(op){
						var v = $pick(op.get('value'), op.get('text'));
						if (v=="") v = op.get('text');
						op.set('selected', $splat(value).contains(v));
					});
					break;
				case 'input':
					if (['radio','checkbox'].contains(this.get('type'))) {
						this.set('checked', $type(value)=="boolean"?value:$splat(value).contains(this.get('value')));
						break;
					}
				case 'textarea': case 'input':
					this.set('value', value);
					break;
				default:
					this.set('inputValue', value);
			}
			return this;
    },

	erase: function() {
		switch(this.get('tag')) {
			case 'select':
				this.getElements('option').each(function(op) {
					op.erase('selected');
				});
				break;
			case 'input':
				if (['radio','checkbox'].contains(this.get('type'))) {
					this.set('checked', false);
					break;
				}
			case 'input': case 'textarea':
				this.set('value', '');
				break;
			default:
				this.set('inputValue', '');
		}
		return this;
	}

};

Element.implement({
	measure: function(fn) {
		var restore = this.expose();
		var result = fn.apply(this);
		restore();
		return result;
	},

	expose: function(){
		if (this.getStyle('display') != 'none') return $empty;
		var before = {};
		var styles = { visibility: 'hidden', display: 'block', position:'absolute' };
		$each(styles, function(value, style){
			before[style] = this.style[style]||'';
		}, this);
		this.setStyles(styles);
		return (function(){ this.setStyles(before); }).bind(this);
	},

	getDimensions: function(options) {
		options = $merge({computeSize: false},options);
		var dim = {};
		function getSize(el, options){
			return (options.computeSize)?el.getComputedSize(options):el.getSize();
		};
		if(this.getStyle('display') == 'none'){
			var restore = this.expose();
			dim = getSize(this, options);
			restore();
		} else {
			try {
				dim = getSize(this, options);
			}catch(e){}
		}
		return $chk(dim.x)?$extend(dim, {width: dim.x, height: dim.y}):$extend(dim, {x: dim.width, y: dim.height});
	},

	getComputedSize: function(options){
		options = $merge({
			styles: ['padding','border'],
			plains: {height: ['top','bottom'], width: ['left','right']},
			mode: 'both'
		}, options);
		var size = {width: 0,height: 0};
		switch (options.mode){
			case 'vertical':
				delete size.width;
				delete options.plains.width;
				break;
			case 'horizontal':
				delete size.height;
				delete options.plains.height;
				break;
		};
		var getStyles = [];
		$each(options.plains, function(plain, key){
			plain.each(function(edge){
				options.styles.each(function(style){
					getStyles.push((style=="border")?style+'-'+edge+'-'+'width':style+'-'+edge);
				});
			});
		});
		var styles = this.getStyles.apply(this, getStyles);
		var subtracted = [];
		$each(options.plains, function(plain, key){
			size['total'+key.capitalize()] = 0;
			size['computed'+key.capitalize()] = 0;
			plain.each(function(edge){
				size['computed'+edge.capitalize()] = 0;
				getStyles.each(function(style,i){
					if(style.test(edge)) {
						styles[style] = styles[style].toInt();
						if(isNaN(styles[style]))styles[style]=0;
						size['total'+key.capitalize()] = size['total'+key.capitalize()]+styles[style];
						size['computed'+edge.capitalize()] = size['computed'+edge.capitalize()]+styles[style];
					}
					if(style.test(edge) && key!=style && 
						(style.test('border') || style.test('padding')) && !subtracted.contains(style)) {
						subtracted.push(style);
						size['computed'+key.capitalize()] = size['computed'+key.capitalize()]-styles[style];
					}
				});
			});
		});
		if($chk(size.width)) {
			size.width = size.width+this.offsetWidth+size.computedWidth;
			size.totalWidth = size.width + size.totalWidth;
			delete size.computedWidth;
		}
		if($chk(size.height)) {
			size.height = size.height+this.offsetHeight+size.computedHeight;
			size.totalHeight = size.height + size.totalHeight;
			delete size.computedHeight;
		}
		return $extend(styles, size);
	}
});

window.addEvent('domready', function(){
	var test = new Element('div').setStyles({
		position: 'fixed',
		top: 0,
		right: 0
	}).inject(document.body);
	var supported = (test.offsetTop === 0);
	test.dispose();
	Browser.supportsPositionFixed = supported;
});

Element.implement({
	pin: function(enable){
		if(!Browser.loaded) dbug.log('cannot pin ' + this + ' natively because the dom is not ready');
		if (this.getStyle('display') == 'none') {
			dbug.log('cannot pin ' + this + ' because it is hidden');
			return;
		}
		if(enable!==false) {
			var p = this.getPosition();
			if(!this.retrieve('pinned')) {
				var pos = {
					top: (p.y - window.getScroll().y),
					left: (p.x - window.getScroll().x)
				};
				if(Browser.supportsPositionFixed) {
					this.setStyle('position','fixed').setStyles(pos);
				} else {
					this.store('pinnedByJS', true);
					this.setStyles({
						position: 'absolute',
						top: p.y,
						left: p.x
					});
					this.store('scrollFixer', function(){
						if(this.retrieve('pinned')) {
							var to = {
								top: (pos.top.toInt() + window.getScroll().y),
								left: (pos.left.toInt() + window.getScroll().x)
							};
							this.setStyles(to);
						}
					}.bind(this));
					window.addEvent('scroll', this.retrieve('scrollFixer'));
				}
				this.store('pinned', true);
			}
		} else {
			var op;
			if (!Browser.Engine.trident) {
				if (this.getParent().getComputedStyle('position') != 'static') op = this.getParent();
				else op = this.getParent().getOffsetParent();
			}
			var p = this.getPosition(op);
			this.store('pinned', false);
			var reposition;
			if (Browser.supportsPositionFixed && !this.retrieve('pinnedByJS')) {
				reposition = {
					top: (p.y + window.getScroll().y),
					left: (p.x + window.getScroll().x)
				};
			} else {
				this.store('pinnedByJS', false);
				window.removeEvent('scroll', this.retrieve('scrollFixer'));
				reposition = {
					top: (p.y),
					left: (p.x)
				};
			}
			this.setStyles($merge(reposition, {position: 'absolute'}));
		}
		return this.addClass('isPinned');
	},
	unpin: function(){
		return this.pin(false).removeClass('isPinned');
	},
	togglepin: function(){
		this.pin(!this.retrieve('pinned'));
	}
});

Element.Properties.position = {

	set: function(options){
		this.setPosition(options);
	},

	get: function(options){
		if (options) this.setPosition(options);
		return this.getPosition();
	}

};

Element.implement({

	setPosition: function(options){
		$each(options||{}, function(v, k){ if (!$defined(v)) delete options[k]; });
		options = $merge({
			relativeTo: document.body,
			position: {
				x: 'center',
				y: 'center'
			},
			edge: false,
			offset: {x: 0, y: 0},
			returnPos: false,
			relFixedPosition: false,
			ignoreMargins: false,
			allowNegative: false
		}, options);
		var parentOffset = {x: 0, y: 0};
		var parentPositioned = false;
		var offsetParent = this.measure(function(){
			return $(this.getOffsetParent());
		});
		if (offsetParent && offsetParent != this.getDocument().body){
			parentOffset = offsetParent.measure(function(){
				return this.getPosition();
			});
			parentPositioned = true;
			options.offset.x = options.offset.x - parentOffset.x;
			options.offset.y = options.offset.y - parentOffset.y;
		}
		var fixValue = function(option){
			if ($type(option) != "string") return option;
			option = option.toLowerCase();
			var val = {};
			if (option.test('left')) val.x = 'left';
			else if (option.test('right')) val.x = 'right';
			else val.x = 'center';
			if (option.test('upper') || option.test('top')) val.y = 'top';
			else if (option.test('bottom')) val.y = 'bottom';
			else val.y = 'center';
			return val;
		};
		options.edge = fixValue(options.edge);
		options.position = fixValue(options.position);
		if (!options.edge){
			if (options.position.x == 'center' && options.position.y == 'center') options.edge = {x:'center', y:'center'};
			else options.edge = {x:'left', y:'top'};
		}

		this.setStyle('position', 'absolute');
		var rel = $(options.relativeTo) || document.body;
		var calc = rel == document.body ? window.getScroll() : rel.getPosition();
		var top = calc.y;
		var left = calc.x;

		if (Browser.Engine.trident){
			var scrolls = rel.getScrolls();
			top += scrolls.y;
			left += scrolls.x;
		}

		var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
		if (options.ignoreMargins){
			options.offset.x = options.offset.x - dim['margin-left'];
			options.offset.y = options.offset.y - dim['margin-top'];
		}
		var pos = {};
		var prefY = options.offset.y;
		var prefX = options.offset.x;
		var winSize = window.getSize();
		switch(options.position.x){
			case 'left':
				pos.x = left + prefX;
				break;
			case 'right':
				pos.x = left + prefX + rel.offsetWidth;
				break;
			default:
				pos.x = left + ((rel == document.body ? winSize.x : rel.offsetWidth)/2) + prefX;
				break;
		};
		switch(options.position.y){
			case 'top':
				pos.y = top + prefY;
				break;
			case 'bottom':
				pos.y = top + prefY + rel.offsetHeight;
				break;
			default:
				pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
				break;
		};

		if (options.edge){
			var edgeOffset = {};

			switch(options.edge.x){
				case 'left':
					edgeOffset.x = 0;
					break;
				case 'right':
					edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
					break;
				default:
					edgeOffset.x = -(dim.x/2);
					break;
			};
			switch(options.edge.y){
				case 'top':
					edgeOffset.y = 0;
					break;
				case 'bottom':
					edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
					break;
				default:
					edgeOffset.y = -(dim.y/2);
					break;
			};
			pos.x = pos.x + edgeOffset.x;
			pos.y = pos.y + edgeOffset.y;
		}
		pos = {
			left: ((pos.x >= 0 || parentPositioned || options.allowNegative) ? pos.x : 0).toInt(),
			top: ((pos.y >= 0 || parentPositioned || options.allowNegative) ? pos.y : 0).toInt()
		};
		if (rel.getStyle('position') == "fixed" || options.relFixedPosition){
			var winScroll = window.getScroll();
			pos.top = pos.top.toInt() + winScroll.y;
			pos.left = pos.left.toInt() + winScroll.x;
		}

		if (options.returnPos) return pos;
		else this.setStyles(pos);
		return this;
	}

});

Element.implement({
	isVisible: function() {
		return this.getStyle('display') != 'none';
	},
	toggle: function() {
		return this[this.isVisible() ? 'hide' : 'show']();
	},
	hide: function() {
		var d;
		try {
			if ('none' != this.getStyle('display')) d = this.getStyle('display');
		} catch(e){}
		this.store('originalDisplay', d||'block'); 
		this.setStyle('display','none');
		return this;
	},
	show: function(display) {
		original = this.retrieve('originalDisplay')?this.retrieve('originalDisplay'):this.get('originalDisplay');
		this.setStyle('display',(display || original || 'block'));
		return this;
	},
	swapClass: function(remove, add) {
		return this.removeClass(remove).addClass(add);
	},
	fxOpacityOk: function(){
		return !Browser.Engine.trident4;
	} 
});

var StyleWriter = new Class({
	createStyle: function(css, id) {
		window.addEvent('domready', function(){
			try {
				if($(id) && id) return;
				var style = new Element('style', {id: id||''}).inject($$('head')[0]);
				if (Browser.Engine.trident) style.styleSheet.cssText = css;
				else style.set('text', css);
			}catch(e){dbug.log('error: %s',e);}
		}.bind(this));
	}
});

var StickyWin = new Class({
	Implements: [Options, Events, StyleWriter, ToElement],
	options: {
		closeClassName: 'closeSticky',
		pinClassName: 'pinSticky',
		content: '',
		zIndex: 10000,
		className: '',
		width: false,
		height: false,
		timeout: -1,
		allowMultipleByClass: false,
		allowMultiple: true,
		showNow: true,
		useIframeShim: true,
		iframeShimSelector: '',
		inject: {
			where: 'bottom' 
		}
	},
	css: '.SWclearfix:after {content: "."; display: block; height: 0; clear: both; visibility: hidden;}'+
			 '.SWclearfix {display: inline-table;}'+
			 '* html .SWclearfix {height: 1%;}'+
			 '.SWclearfix {display: block;}',
	initialize: function(options){
		this.setOptions(options);
		this.options.inject.target = this.options.inject.target || document.body;
		this.id = this.options.id || 'StickyWin_'+new Date().getTime();
		this.makeWindow();

		if(this.options.content) this.setContent(this.options.content);
		if(this.options.timeout > 0) {
			this.addEvent('onDisplay', function(){
				this.hide.delay(this.options.timeout, this)
			}.bind(this));
		}
		if(this.options.showNow) this.show();
		this.createStyle(this.css, 'StickyWinClearFix');
	},
	makeWindow: function(){
		this.destroyOthers();
		if(!$(this.id)) {
			this.win = new Element('div', {
				id:		this.id
			}).addClass(this.options.className).addClass('StickyWinInstance').addClass('SWclearfix').setStyles({
			 	display:'none',
				position:'absolute',
				zIndex:this.options.zIndex
			}).inject(this.options.inject.target, this.options.inject.where).store('StickyWin', this);
		} else this.win = $(this.id);
		this.element = this.win;
		if(this.options.width && $type(this.options.width.toInt())=="number") this.win.setStyle('width', this.options.width.toInt());
		if(this.options.height && $type(this.options.height.toInt())=="number") this.win.setStyle('height', this.options.height.toInt());
		return this;
	},
	show: function(suppressEvent){
		this.showWin();
		if (!suppressEvent) this.fireEvent('onDisplay');
		if(this.options.useIframeShim) this.showIframeShim();
		this.visible = true;
		return this;
	},
	showWin: function(){
		if(!this.positioned) this.position();
		this.win.show();
	},
	hide: function(suppressEvent){
		if(!suppressEvent) this.fireEvent('onClose');
		this.hideWin();
		if(this.options.useIframeShim) this.hideIframeShim();
		this.visible = false;
		return this;
	},
	hideWin: function(){
		this.win.setStyle('display','none');
	},
	destroyOthers: function() {
		if(!this.options.allowMultipleByClass || !this.options.allowMultiple) {
			$$('div.StickyWinInstance').each(function(sw) {
				if(!this.options.allowMultiple || (!this.options.allowMultipleByClass && sw.hasClass(this.options.className))) 
					sw.retrieve('StickyWin').destroy();
			}, this);
		}
	},
	setContent: function(html) {
		if(this.win.getChildren().length>0) this.win.empty();
		if($type(html) == "string") this.win.set('html', html);
		else if ($(html)) this.win.adopt(html);
		this.win.getElements('.'+this.options.closeClassName).each(function(el){
			el.addEvent('click', this.hide.bind(this));
		}, this);
		this.win.getElements('.'+this.options.pinClassName).each(function(el){
			el.addEvent('click', this.togglepin.bind(this));
		}, this);
		return this;
	},
	position: function(options){
		this.positioned = true;
		this.setOptions(options);
		this.win.setPosition({
			allowNegative: true,
			relativeTo: this.options.relativeTo,
			position: this.options.position,
			offset: this.options.offset,
			edge: this.options.edge
		});
		if(this.shim) this.shim.position();
		return this;
	},
	pin: function(pin) {
		if(!this.win.pin) {
			dbug.log('you must include element.pin.js!');
			return this;
		}
		this.pinned = $pick(pin, true);
		this.win.pin(pin);
		return this;
	},
	unpin: function(){
		return this.pin(false);
	},
	togglepin: function(){
		return this.pin(!this.pinned);
	},
	makeIframeShim: function(){
		if(!this.shim){
			var el = (this.options.iframeShimSelector)?this.win.getElement(this.options.iframeShimSelector):this.win;
			this.shim = new IframeShim(el, {
				display: false,
				name: 'StickyWinShim'
			});
		}
	},
	showIframeShim: function(){
		if(this.options.useIframeShim) {
			this.makeIframeShim();
			this.shim.show();
		}
	},
	hideIframeShim: function(){
		if(this.shim) this.shim.hide();
	},
	destroy: function(){
		if (this.win) this.win.dispose();
		if(this.options.useIframeShim && this.shim) this.shim.dispose();
		if($('modalOverlay'))$('modalOverlay').dispose();
	}
});
StickyWin.UI = new Class({
	Implements: [Options, ToElement, StyleWriter],
	options: {
		width: 300,
		css: "div.DefaultStickyWin div.body{font-family:verdana; font-size:11px; line-height: 13px;}"+
			"div.DefaultStickyWin div.top_ul{background:url({%baseHref%}full.png) top left no-repeat; height:30px; width:15px; float:left}"+
			"div.DefaultStickyWin div.top_ur{position:relative; left:0px !important; left:-4px; background:url({%baseHref%}full.png) top right !important; height:30px; margin:0px 0px 0px 15px !important; margin-right:-4px; padding:0px}"+
			"div.DefaultStickyWin h1.caption{clear: none !important; margin:0px !important; overflow: hidden; padding:0 !important; font-weight:bold; color:#555; font-size:14px !important; position:relative; top:8px !important; left:5px !important; float: left; height: 22px !important;}"+
			"div.DefaultStickyWin div.middle, div.DefaultStickyWin div.closeBody {background:url({%baseHref%}body.png) top left repeat-y; margin:0px 20px 0px 0px !important;	margin-bottom: -3px; position: relative;	top: 0px !important; top: -3px;}"+
			"div.DefaultStickyWin div.body{background:url({%baseHref%}body.png) top right repeat-y; padding:8px 30px 8px 0px !important; margin-left:5px !important; position:relative; right:-20px !important;}"+
			"div.DefaultStickyWin div.bottom{clear:both}"+
			"div.DefaultStickyWin div.bottom_ll{background:url({%baseHref%}full.png) bottom left no-repeat; width:15px; height:15px; float:left}"+
			"div.DefaultStickyWin div.bottom_lr{background:url({%baseHref%}full.png) bottom right; position:relative; left:0px !important; left:-4px; margin:0px 0px 0px 15px !important; margin-right:-4px; height:15px}"+
			"div.DefaultStickyWin div.closeButtons{text-align: center; background:url({%baseHref%}body.png) top right repeat-y; padding: 0px 30px 8px 0px; margin-left:5px; position:relative; right:-20px}"+
			"div.DefaultStickyWin a.button:hover{background:url({%baseHref%}big_button_over.gif) repeat-x}"+
			"div.DefaultStickyWin a.button {background:url({%baseHref%}big_button.gif) repeat-x; margin: 2px 8px 2px 8px; padding: 2px 12px; cursor:pointer; border: 1px solid #999 !important; text-decoration:none; color: #000 !important;}"+
			"div.DefaultStickyWin div.closeButton{width:13px; height:13px; background:url({%baseHref%}closebtn.gif) no-repeat; position: absolute; right: 0px; margin:10px 15px 0px 0px !important; cursor:pointer;top:0px}"+
			"div.DefaultStickyWin div.dragHandle {	width: 11px;	height: 25px;	position: relative;	top: 5px;	left: -3px;	cursor: move;	background: url({%baseHref%}drag_corner.gif); float: left;}",
		cornerHandle: false,
		cssClass: '',
		baseHref: 'http://www.cnet.com/html/rb/assets/global/stickyWinHTML/',
		buttons: [],
		cssId: 'defaultStickyWinStyle',
		cssClassName: 'DefaultStickyWin',
		closeButton: true
	},
	initialize: function() {
		var args = this.getArgs(arguments);
		this.setOptions(args.options);
		this.legacy();
		var css = this.options.css.substitute({baseHref: this.options.baseHref}, /\\?\{%([^}]+)%\}/g);
		if (Browser.Engine.trident4) css = css.replace(/png/g, 'gif');
		this.createStyle(css, this.options.cssId);
		this.build();
		this.setContent(args.caption, args.body);
	},
	getArgs: function(){
		return StickyWin.UI.getArgs.apply(this, arguments);
	},
	legacy: function(){
		var opt = this.options;
		if(opt.confirmTxt) opt.buttons.push({text: opt.confirmTxt, onClick: opt.onConfirm || $empty});
		if(opt.closeTxt) opt.buttons.push({text: opt.closeTxt, onClick: opt.onClose || $empty});
	},
	build: function(){
		var opt = this.options;

		var container = new Element('div', {
			'class': opt.cssClassName
		});
		if (opt.width) container.setStyle('width', opt.width);
		this.element = container;
		this.element.store('StickyWinUI', this);
		if(opt.cssClass) container.addClass(opt.cssClass);
		var bodyDiv = new Element('div').addClass('body');
		this.body = bodyDiv;

		var top_ur = new Element('div').addClass('top_ur');
		this.top_ur = top_ur;
		this.top = new Element('div').addClass('top').adopt(
				new Element('div').addClass('top_ul')
			).adopt(top_ur);
		container.adopt(this.top);

		if(opt.cornerHandle) new Element('div').addClass('dragHandle').inject(top_ur, 'top');
		container.adopt(new Element('div').addClass('middle').adopt(bodyDiv));
		if(opt.buttons.length > 0){
			var closeButtons = new Element('div').addClass('closeButtons');
			opt.buttons.each(function(button){
				if(button.properties && button.properties.className){
					button.properties['class'] = button.properties.className;
					delete button.properties.className;
				}
				var properties = $merge({'class': 'closeSticky'}, button.properties);
				new Element('a').addEvent('click',
					button.onClick || $empty).appendText(
					button.text).inject(closeButtons).setProperties(properties).addClass('button');
			});
			container.adopt(new Element('div').addClass('closeBody').adopt(closeButtons));
		}
		container.adopt(
			new Element('div').addClass('bottom').adopt(
					new Element('div').addClass('bottom_ll')
				).adopt(
					new Element('div').addClass('bottom_lr')
			)
		);
		if (this.options.closeButton) container.adopt(new Element('div').addClass('closeButton').addClass('closeSticky'));
		return this;
	},
	makeCaption: function(caption) {
		if (!caption) return this.destroyCaption();
		this.caption = caption;
		var opt = this.options;
		var h1Caption = new Element('h1').addClass('caption');
		if (opt.width) h1Caption.setStyle('width', (opt.width-(opt.cornerHandle?55:40)-(opt.closeButton?10:0)));
		if($(this.caption)) h1Caption.adopt(this.caption);
		else h1Caption.set('html', this.caption);
		this.top_ur.adopt(h1Caption);
		this.h1 = h1Caption;
		if(!this.options.cornerHandle) this.h1.addClass('dragHandle');
		return this;
	},
	destroyCaption: function(){
		if (this.h1) {
			this.h1.destroy();
			this.h1 = null;
		}
		return this;
	},
	setContent: function(){
		var args = this.getArgs.apply(this, arguments);
		var caption = args.caption;
		var body = args.body;
		if (this.h1) this.destroyCaption();
		this.makeCaption(caption);
		if ($(body)) this.body.empty().adopt(body);
		else this.body.set('html', body);
		return this;
	}
});
StickyWin.UI.getArgs = function(){
	var input = $type(arguments[0]) == "arguments"?arguments[0]:arguments;
	var cap = input[0], bod = input[1];
	var args = Array.link(input, {options: Object.type});
	if (input.length == 3 || (!args.options && input.length == 2)) {
		args.caption = cap;
		args.body = bod;
	} else if (($type(bod) == 'object' || !bod) && cap && $type(cap) != 'object'){
		args.body = cap;
	}
	return args;
};

StickyWin.ui = function(caption, body, options){
	return $(new StickyWin.UI(caption, body, options))
};
StickyWin.UI.Pointy = new Class({
	Extends: StickyWin.UI,
	options: {
		theme: 'dark',
		themes: {
			dark: {
				bgColor: '#333',
				fgColor: '#ddd',
				imgset: 'dark'
			},
			light: {
				bgColor: '#ccc',
				fgColor: '#333',
				imgset: 'light'
			}
		},
		css: "div.DefaultPointyTip {position: relative}"+
		"div.DefaultPointyTip div.body{background: {%bgColor%}; color: {%fgColor%}; right: 0px !important;padding:  0px 10px !important;margin-left: 0px !important;font-family: verdana;font-size: 11px;line-height: 13px;position: relative;}"+
		"div.DefaultPointyTip div.top {position: relative;height: 25px; overflow: visible}"+
		"div.DefaultPointyTip div.top_ul{background: url({%baseHref%}{%imgset%}_back.png) top left no-repeat;width: 8px;height: 25px; position: absolute; left: 0px;}"+
		"div.DefaultPointyTip div.top_ur{background: url({%baseHref%}{%imgset%}_back.png) top right !important;margin: 0 0 0 8px !important;height: 25px;position: relative;left: 0px !important;padding: 0;}"+
		"div.DefaultPointyTip h1.caption{color: {%fgColor%};left: 0px !important;top: 4px !important;clear: none !important;overflow: hidden;font-weight: 700;font-size: 12px !important;position: relative;float: left;height: 22px !important;margin: 0 !important;padding: 0 !important;}"+
		"div.DefaultPointyTip div.middle,div.DefaultPointyTip div.closeBody{background:  {%bgColor%};margin: 0 0px 0 0 !important;position: relative;top: 0 !important;}"+
		"div.DefaultPointyTip div.bottom {clear: both;} "+
		"div.DefaultPointyTip div.bottom_ll{font-size:1; background: url({%baseHref%}{%imgset%}_back.png) bottom left no-repeat;width: 6px;height: 6px;position: absolute; left: 0px;}"+
		"div.DefaultPointyTip div.bottom_lr{font-size:1; background: url({%baseHref%}{%imgset%}_back.png) bottom right;height: 6px;margin: 0 0 0 6px !important;position: relative;left: 0 !important;}"+
		"div.DefaultPointyTip div.noCaption{height: 6px; overflow: hidden}"+
		"div.DefaultPointyTip div.closeButton{width:13px; height:13px; background:url({%baseHref%}{%imgset%}_x.png) no-repeat; position: absolute; right: 0px; margin:4px 0px 0px !important; cursor:pointer; z-index: 1; top: 0px;}",
		baseHref: 'http://cnetjavascript.googlecode.com/svn/trunk/Assets/PointyTip/',
		divot: '{%baseHref%}{%imgset%}_divot.png',
		divotSize: 22,
		direction: 12,
		cssId: 'defaultPointyTipStyle',
		cssClassName: 'DefaultPointyTip'
	},
	initialize: function() {
		var args = this.getArgs(arguments);
		this.setOptions(args.options);
		$extend(this.options, this.options.themes[this.options.theme]);
		this.options.css = this.options.css.substitute(this.options, /\\?\{%([^}]+)%\}/g);
		if (args.options && args.options.theme) {
			while (!this.id) {
				var id = $random(0, 999999999);
				if (!StickyWin.UI.Pointy[id]) {
					StickyWin.UI.Pointy[id] = this;
					this.id = id;
				}
			}
			this.options.css = this.options.css.replace(/div\.DefaultPointyTip/g, "div#pointy_"+this.id);
			this.options.cssId = "pointyTipStyle_" + this.id;
		}
		if ($type(this.options.direction) == 'string') {
			var map = {
				left: 9,
				right: 3,
				up: 12,
				down: 6
			};
			this.options.direction = map[this.options.direction];
		}

		this.options.divot = this.options.divot.substitute(this.options, /\\?\{%([^}]+)%\}/g);
		if (Browser.Engine.trident4) this.options.divot = this.options.divot.replace(/png/g, 'gif');

		this.parent(args.caption, args.body, this.options);
		if (this.id) $(this).set('id', "pointy_"+this.id);
	},
	build: function(){
		this.parent();
		var opt = this.options;
		this.pointyWrapper = new Element('div', {
			'class': 'pointyWrapper'
		}).inject($(this));
		$(this).getChildren().each(function(el){
			if (el != this.pointyWrapper) this.pointyWrapper.grab(el);
		}, this);

		var w = opt.divotSize;
		var h = w;
		var left = (opt.width - opt.divotSize)/2;
		var orient = function(){
			switch(opt.direction) {
				case 12: case 1: case 11:
					return {
						height: h/2
					};
				case 5: case 6: case 7:
					return {
						height: h/2,
						backgroundPosition: '0 -'+h/2+'px'
					};
				case 8: case 9: case 10:
					return {
						width: w/2
					};
				case 2: case 3: case 4:
					return {
						width: w/2,
						backgroundPosition: '100%'
					};
			};
		};
		this.pointer = new Element('div', {
			styles: $extend({
				background: "url("+opt.divot+") no-repeat",
				width: w,
				height: h,
				overflow: 'hidden'
			}, orient()),
			'class': 'pointyDivot pointy_'+opt.direction
		}).inject(this.pointyWrapper);
		this.positionPointer();
	},
	expose: function(){
		if ($(this).getStyle('display') != 'none' && $(document.body).hasChild($(this))) return $empty;
		$(this).setStyles({
			visibility: 'hidden',
			position: 'absolute'
		});
		var dispose;
		if (!document.body.hasChild($(this))) {
			$(this).inject(document.body);
			dispose = true;
		}
		return (function(){
			if (dispose) $(this).dispose();
			$(this).setStyles({
				visibility: 'visible',
				position: 'relative'
			});
		}).bind(this);
	},
	positionPointer: function(options){
		if (!this.pointer) return;

		var opt = this.options;
		var pos;
		var d = opt.direction;
		switch (d){
			case 12: case 1: case 11:
				pos = {
					edge: {x: 'center', y: 'bottom'},
					position: {
						x: d==12?'center':d==1?'right':'left', 
						y: 'top'
					},
					offset: {
						x: (d==12?0:d==1?-1:1)*opt.divotSize,
						y: 1
					}
				};
				break;
			case 2: case 3: case 4:
				pos = {
					edge: {x: 'left', y: 'center'},
					position: {
						x: 'right', 
						y: d==3?'center':d==2?'top':'bottom'
					},
					offset: {
						x: -1,
						y: (d==3?0:d==4?-1:1)*opt.divotSize
					}
				};
				break;
			case 5: case 6: case 7:
				pos = {
					edge: {x: 'center', y: 'top'},
					position: {
						x: d==6?'center':d==5?'right':'left', 
						y: 'bottom'
					},
					offset: {
						x: (d==6?0:d==5?-1:1)*opt.divotSize,
						y: -1
					}
				};
				break;
			case 8: case 9: case 10:
				pos = {
					edge: {x: 'right', y: 'center'},
					position: {
						x: 'left', 
						y: d==9?'center':d==10?'top':'bottom'
					},
					offset: {
						x: 1,
						y: (d==9?0:d==8?-1:1)*opt.divotSize
					}
				};
				break;
		};
		var putItBack = this.expose();
		this.pointer.setPosition($extend({
			relativeTo: this.pointyWrapper
		}, pos, options));
		putItBack();
	},
	setContent: function(){
		this.parent.apply(this, arguments);
		this.top[this.h1?'removeClass':'addClass']('noCaption');
		if (Browser.Engine.trident4) $(this).getElements('.bottom_ll, .bottom_lr').setStyle('font-size', 1);
		this.positionPointer();
		return this;
	},
	makeCaption: function(){
		this.parent.apply(this, arguments);
		if (this.options.width && this.h1) this.h1.setStyle('width', (this.options.width-(this.options.closeButton?25:15)));
	}
});

StickyWin.ui.pointy = function(caption, body, options){
	return $(new StickyWin.UI.Pointy(caption, body, options));
};
StickyWin.PointyTip = new Class({
	Extends: StickyWin,
	options: {
		point: "left",
		pointyOptions: {}
	},
	initialize: function(){
		var args = this.getArgs(arguments);
		this.setOptions(args.options);
		var popts = this.options.pointyOptions;
		var d = popts.direction;
		if (!d) {
			var map = {
				left: 9,
				right: 3,
				up: 12,
				down: 6
			};
			d = map[this.options.point];
			if (!d) d = this.options.point;
			popts.direction = d;
		}
		if (!popts.width) popts.width = this.options.width;
		this.pointy = new StickyWin.UI.Pointy(args.caption, args.body, popts);
		this.options.content = null;

		this.setOptions(this.getPositionSettings(), args.options);
		this.parent(this.options);
		this.win.empty().adopt(this.pointy);
		this.attachHandlers(this.win);
		this.position();
	},
	getArgs: function(){
		return StickyWin.UI.getArgs.apply(this, arguments);
	},
	getPositionSettings: function(){
		var s = this.pointy.options.divotSize;
		var d = this.options.point;
		switch(d) {
			case "left": case 8: case 9: case 10:
				return {
					edge: {
						x: 'left', 
						y: d==10?'top':d==8?'bottom':'center'
					},
					position: {x: 'right', y: 'center'},
					offset: {
						x: s
					}
				};
			case "right": case 2:  case 3: case 4:
				return {
					edge: {
						x: 'right', 
						y: d==2?'top':d==4?'bottom':'center'
					},
					position: {x: 'left', y: 'center'},
					offset: {x: -s}
				};
			case "up": case 11: case 12: case 1:
				return {
					edge: { 
						x: d==11?'left':d==1?'right':'center', 
						y: 'top' 
					},
					position: {	x: 'center', y: 'bottom' },
					offset: {
						y: s,
						x: d==11?-s:d==1?s:0
					}
				};
			case "down": case 5: case 6: case 7:
				return {
					edge: {
						x: d==7?'left':d==5?'right':'center', 
						y: 'bottom'
					},
					position: {x: 'center', y: 'top'},
					offset: {y: -s}
				};
		};
	},
	setContent: function() {
		var args = this.getArgs(arguments);
		this.pointy.setContent(args.caption, args.body);
		[this.pointy.h1, this.pointy.body].each(this.attachHandlers, this);
		if (this.visible) this.position();
		return this;
	},
	showWin: function(){
		this.parent.apply(this, arguments);
		this.pointy.positionPointer();
	},
	position: function(){
		this.parent.apply(this, arguments);
		this.pointy.positionPointer();
	},
	attachHandlers: function(content) {
		if (!content) return;
		content.getElements('.'+this.options.closeClassName).addEvent('click', function(){ this.hide(); }.bind(this));
		content.getElements('.'+this.options.pinClassName).addEvent('click', function(){ this.togglepin(); }.bind(this));
	}
});
var InputValidator = new Class({
	Implements: [Options],
	options: {
		errorMsg: 'Validation failed.',
		test: function(field){return true;}
	},
	initialize: function(className, options){
		this.setOptions(options);
		this.className = className;
	},
	test: function(field, props){
		if($(field)) return this.options.test($(field), props||this.getProps(field));
		else return false;
	},
	getError: function(field, props){
		var err = this.options.errorMsg;
		if($type(err) == "function") err = err($(field), props||this.getProps(field));
		return err;
	},
	getProps: function(field){
		if (!$(field)) return {};
		return field.get('validatorProps');
	}
});

Element.Properties.validatorProps = {

	set: function(props){
		return this.eliminate('validatorProps').store('validatorProps', props);
	},

	get: function(props){
		if (props) this.set(props);
		if (this.retrieve('validatorProps')) return this.retrieve('validatorProps');
		if(this.getProperty('validatorProps')){
			try {
				this.store('validatorProps', JSON.decode(this.getProperty('validatorProps')));
			}catch(e){ return {}}
		} else {
			var vals = this.get('class').split(' ').filter(function(cls) {
				return cls.test(':');
			});
			if (!vals.length) {
				this.store('validatorProps', {});
			} else {
				props = {};
				vals.each(function(cls){
					var split = cls.indexOf(':');
					props[cls.substring(0, split)] = JSON.decode(cls.substring(split+1, cls.length))
				});
				this.store('validatorProps', props);
			}
		}
		return this.retrieve('validatorProps');
	}

};

var FormValidator = new Class({
	Implements:[Options, Events],
	options: {
		fieldSelectors:"input, select, textarea",
		ignoreHidden: true,
		useTitles:false,
		evaluateOnSubmit:true,
		evaluateFieldsOnBlur: true,
		evaluateFieldsOnChange: true,
		serial: true,
		stopOnFailure: true,
		scrollToErrorsOnSubmit: true,
		warningPrefix: function(){
			return FormValidator.resources[FormValidator.language].warningPrefix || 'Warning: ';
		},
		errorPrefix: function(){
			return FormValidator.resources[FormValidator.language].errorPrefix || 'Error: ';
		}
	},
	initialize: function(form, options){
		this.setOptions(options);
		this.form = $(form);
		this.form.store('validator', this);
		this.warningPrefix = $lambda(this.options.warningPrefix)();
		this.errorPrefix = $lambda(this.options.errorPrefix)();

		if(this.options.evaluateOnSubmit) this.form.addEvent('submit', this.onSubmit.bind(this));
		if(this.options.evaluateFieldsOnBlur) this.watchFields();
	},
	toElement: function(){
		return this.form;
	},
	getFields: function(){
		return this.fields = this.form.getElements(this.options.fieldSelectors);
	},
	watchFields: function(){
		this.getFields().each(function(el){
				el.addEvent('blur', this.validateField.pass([el, false], this));
			if(this.options.evaluateFieldsOnChange)
				el.addEvent('change', this.validateField.pass([el, true], this));
		}, this);
	},
	onSubmit: function(event){
		if(!this.validate(event) && event) event.preventDefault();
		else this.reset();
	},
	reset: function() {
		this.getFields().each(this.resetField, this);
		return this;
	}, 
	validate: function(event) {
		var result = this.getFields().map(function(field) { 
			return this.validateField(field, true);
		}, this).every(function(v){ return v;});
		this.fireEvent('onFormValidate', [result, this.form, event]);
		if (this.options.stopOnFailure && !result && event) event.preventDefault();
		if (this.options.scrollToErrorsOnSubmit && !result) {
			var par = this.form.getParent();
			var isScrolled = function(p){
				return p.getScrollSize().y != p.getSize().y
			};
			var scrolls;
			while (par != document.body && !isScrolled(par)) {
				par = par.getParent();
			};
			var fx = par.retrieve('fvScroller');
			if (!fx && window.Fx && Fx.Scroll) {
				fx = new Fx.Scroll(par, {
					transition: 'quad:out',
					offset: {
						y: -20
					}
				});
				par.store('fvScroller', fx);
			}
			var failed = this.form.getElement('.validation-failed');
			if (failed) {
				if (fx) fx.toElement(failed);
				else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
			}
		}
		return result;
	},
	validateField: function(field, force){
		if (this.paused) return true;
		field = $(field);
		var passed = !field.hasClass('validation-failed');
		var failed, warned;
		if (this.options.serial && !force) {
			failed = this.form.getElement('.validation-failed');
			warned = this.form.getElement('.warning');
		}
		if(field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
			var validators = field.className.split(" ").some(function(cn){
				return this.getValidator(cn);
			}, this);
			var validatorsFailed = [];
			field.className.split(" ").each(function(className){
				if (!this.test(className,field)) validatorsFailed.include(className);
			}, this);
			passed = validatorsFailed.length === 0;
			if (validators && !field.hasClass('warnOnly')){
				if(passed) {
					field.addClass('validation-passed').removeClass('validation-failed');
					this.fireEvent('onElementPass', field);
				} else {
					field.addClass('validation-failed').removeClass('validation-passed');
					this.fireEvent('onElementFail', [field, failed]);
				}
			}
			if(!warned) {
				var warnings = field.className.split(" ").some(function(cn){
					if(cn.test('^warn-') || field.hasClass('warnOnly')) 
						return this.getValidator(cn.replace(/^warn-/,""));
					else return null;
				}, this);
				field.removeClass('warning');
				var warnResult = field.className.split(" ").map(function(cn){
					if(cn.test('^warn-') || field.hasClass('warnOnly')) 
						return this.test(cn.replace(/^warn-/,""), field, true);
					else return null;
				}, this);
			}
		}
		return passed;
	},
	getPropName: function(className){
		return 'advice'+className;
	},
	test: function(className, field, warn){
		field = $(field);
		if(field.hasClass('ignoreValidation')) return true;
		warn = $pick(warn, false);
		if(field.hasClass('warnOnly')) warn = true;
		var isValid = true;
		var validator = this.getValidator(className);
		if(validator && this.isVisible(field)) {
			isValid = validator.test(field);
			if(!isValid && validator.getError(field)){
				if(warn) field.addClass('warning');
				var advice = this.makeAdvice(className, field, validator.getError(field), warn);
				this.insertAdvice(advice, field);
				this.showAdvice(className, field);
			} else this.hideAdvice(className, field);
			this.fireEvent('onElementValidate', [isValid, field, className]);
		}

		if(warn) return true;
		return isValid;
	},
	getAllAdviceMessages: function(field, force) {
		var advice = [];
		if (field.hasClass('ignoreValidation') && !force) return advice;
		var validators = field.className.split(" ").some(function(cn){
			var warner = cn.test('^warn-') || field.hasClass('warnOnly');
			if (warner) cn = cn.replace(/^warn-/,"");
			var validator = this.getValidator(cn);
			if (!validator) return;
			advice.push({
				message: validator.getError(field),
				warnOnly: warner,
				passed: validator.test(),
				validator: validator
			});
		}, this);
		return advice;
	},
	showAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if(advice && !field.retrieve(this.getPropName(className))
			 && (advice.getStyle('display') == "none" 
			 || advice.getStyle('visiblity') == "hidden" 
			 || advice.getStyle('opacity')==0)){
			field.store(this.getPropName(className), true);
			if(advice.reveal) advice.reveal();
			else advice.setStyle('display','block');
		}
	},
	hideAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if(advice && field.retrieve(this.getPropName(className))) {
			field.store(this.getPropName(className), false);
			if(advice.dissolve) advice.dissolve();
			else advice.setStyle('display','none');
		}
	},
	isVisible : function(field) {
		if (!this.options.ignoreHidden) return true;
		while(field != document.body) {
			if($(field).getStyle('display') == "none") return false;
			field = field.getParent();
		}
		return true;
	},
	getAdvice: function(className, field) {
		return field.retrieve('advice-'+className);
	},
	makeAdvice: function(className, field, error, warn){
		var errorMsg = (warn)?this.warningPrefix:this.errorPrefix;
				errorMsg += (this.options.useTitles) ? field.title || error:error;
		var cssClass = (warn)?'warning-advice':'validation-advice';
		var advice = this.getAdvice(className, field);
		if(advice) {
			advice = advice.clone(true).set('html', errorMsg).replaces(advice);
		} else {
			advice = new Element('div', {
				html: errorMsg,
				styles: { display: 'none' },
				id: 'advice-'+className+'-'+this.getFieldId(field)
			}).addClass(cssClass);
		}
		field.store('advice-'+className, advice);
		return advice;
	},
	insertAdvice: function(advice, field){
		var props = field.get('validatorProps');
		if (!props.msgPos || !$(props.msgPos)) {
			switch (field.type.toLowerCase()) {
				case 'radio':
					var p = field.getParent().adopt(advice);
					break;
				default: 
					advice.inject($(field), 'after');
			};
		} else {
			$(props.msgPos).grab(advice);
		}
	},
	getFieldId : function(field) {
		return field.id ? field.id : field.id = "input_"+field.name;
	},
	resetField: function(field) {
		field = $(field);
		if(field) {
			var cn = field.className.split(" ");
			cn.each(function(className) {
				if(className.test('^warn-')) className = className.replace(/^warn-/,"");
				var prop = this.getPropName(className);
				if(field.retrieve(prop)) this.hideAdvice(className, field);
				field.removeClass('validation-failed');
				field.removeClass('warning');
				field.removeClass('validation-passed');
			}, this);
		}
		return this;
	},
	stop: function(){
		this.paused = true;
		return this;
	},
	start: function(){
		this.paused = false;
		return this;
	},
	ignoreField: function(field, warn){
		field = $(field);
		if(field){
			this.enforceField(field);
			if(warn) field.addClass('warnOnly');
			else field.addClass('ignoreValidation');
		}
		return this;
	},
	enforceField: function(field){
		field = $(field);
		if(field) field.removeClass('warnOnly').removeClass('ignoreValidation');
		return this;
	}
});

FormValidator.resources = {
	enUS: {
		required:'This field is required.',
		minLength:'Please enter at least {minLength} characters (you entered {length} characters).',
		maxLength:'Please enter no more than {maxLength} characters (you entered {length} characters).',
		integer:'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
		numeric:'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
		digits:'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
		alpha:'Please use letters only (a-z) with in this field. No spaces or other characters are allowed.',
		alphanum:'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.',
		dateSuchAs:'Please enter a valid date such as {date}',
		dateInFormatMDY:'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
		email:'Please enter a valid email address. For example "fred@domain.com".',
		url:'Please enter a valid URL such as http://www.google.com.',
		currencyDollar:'Please enter a valid $ amount. For example $100.00 .',
		oneRequired:'Please enter something for at least one of these inputs.',
		errorPrefix: 'Error: ',
		warningPrefix: 'Warning: '
	}
};
FormValidator.language = "enUS";
FormValidator.getMsg = function(key, language){
	return FormValidator.resources[language||FormValidator.language][key];
};

FormValidator.adders = {
	validators:{},
	add : function(className, options) {
		this.validators[className] = new InputValidator(className, options);
		if(!this.initialize){
			this.implement({
				validators: this.validators
			});
		}
	},
	addAllThese : function(validators) {
		$A(validators).each(function(validator) {
			this.add(validator[0], validator[1]);
		}, this);
	},
	getValidator: function(className){
		return this.validators[className.split(":")[0]];
	}
};
$extend(FormValidator, FormValidator.adders);
FormValidator.implement(FormValidator.adders);

FormValidator.add('IsEmpty', {
	errorMsg: false,
	test: function(element) { 
		if(element.type == "select-one"||element.type == "select")
			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != "");
		else
			return ((element.get('value') == null) || (element.get('value').length == 0));
	}
});

FormValidator.addAllThese([
	['required', {
		errorMsg: function(){
			return FormValidator.getMsg('required');
		},
		test: function(element) { 
			return !FormValidator.getValidator('IsEmpty').test(element); 
		}
	}],
	['minLength', {
		errorMsg: function(element, props){
			if($type(props.minLength))
				return FormValidator.getMsg('minLength').substitute({minLength:props.minLength,length:element.get('value').length });
			else return '';
		}, 
		test: function(element, props) {
			if($type(props.minLength)) return (element.get('value').length >= $pick(props.minLength, 0));
			else return true;
		}
	}],
	['maxLength', {
		errorMsg: function(element, props){
			if($type(props.maxLength))
				return FormValidator.getMsg('maxLength').substitute({maxLength:props.maxLength,length:element.get('value').length });
			else return '';
		}, 
		test: function(element, props) {
			return (element.get('value').length <= $pick(props.maxLength, 10000));
		}
	}],
	['validate-integer', {
		errorMsg: FormValidator.getMsg.pass('integer'),
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || /^-?[1-9]\d*$/.test(element.get('value'));
		}
	}],
	['validate-numeric', {
		errorMsg: FormValidator.getMsg.pass('numeric'), 
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || 
				/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/.test(element.get('value'));
		}
	}],
	['validate-digits', {
		errorMsg: FormValidator.getMsg.pass('digits'), 
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
		}
	}],
	['validate-alpha', {
		errorMsg: FormValidator.getMsg.pass('alpha'), 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||  /^[a-zA-Z]+$/.test(element.get('value'))
		}
	}],
	['validate-alphanum', {
		errorMsg: FormValidator.getMsg.pass('alphanum'), 
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || !/\W/.test(element.get('value'))
		}
	}],
	['validate-date', {
		errorMsg: function(element, props) {
			if (Date.parse) {
				var format = props.dateFormat || "%x";
				return FormValidator.getMsg('dateSuchAs').substitute({date:new Date().format(format)});
			} else {
				return FormValidator.getMsg('dateInFormatMDY');
			}
		},
		test: function(element, props) {
			if(FormValidator.getValidator('IsEmpty').test(element)) return true;
			if (Date.parse) {
				var format = props.dateFormat || "%x";
				var d = Date.parse(element.get('value'));
				var formatted = d.format(format);
				if (formatted != "invalid date") element.set('value', formatted);
				return !isNaN(d);
			} else {
			var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
			if(!regex.test(element.get('value'))) return false;
			var d = new Date(element.get('value').replace(regex, '$1/$2/$3'));
			return (parseInt(RegExp.$1, 10) == (1+d.getMonth())) && 
			(parseInt(RegExp.$2, 10) == d.getDate()) && 
			(parseInt(RegExp.$3, 10) == d.getFullYear() );
			}
		}
	}],
	['validate-email', {
		errorMsg: FormValidator.getMsg.pass('email'), 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) || /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(element.get('value'));
		}
	}],
	['validate-url', {
		errorMsg: FormValidator.getMsg.pass('url'), 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) || /^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i.test(element.get('value'));
		}
	}],
	['validate-currency-dollar', {
		errorMsg: FormValidator.getMsg.pass('currencyDollar'), 
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||  /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(element.get('value'));
		}
	}],
	['validate-one-required', {
		errorMsg: FormValidator.getMsg.pass('oneRequired'), 
		test: function (element) {
			var p = element.parentNode;
			return p.getElements('input').some(function(el) {
				if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
				return el.get('value');
			});
		}
	}]
]);

Element.Properties.validator = {

	set: function(options){
		var validator = this.retrieve('validator');
		if (validator) validator.setOptions(options);
		return this.store('validator:options');
	},

	get: function(options){
		if (options || !this.retrieve('validator')){
			if (options || !this.retrieve('validator:options')) this.set('validator', options);
			this.store('validator', new FormValidator(this, this.retrieve('validator:options')));
		}
		return this.retrieve('validator');
	}

};

Element.implement({
	validate: function(options){
		this.set('validator', options);
		return this.get('validator', options).validate();
	}
});

FormValidator.Tips = new Class({
	Extends: FormValidator,
	options: {
		pointyTipOptions: {
			point: "left"
		}
	},
	showAdvice: function(className, field){
		var advice = this.getAdvice(field);
		if(advice && !advice.visible){
			advice.show();
			advice.position();
			advice.pointy.positionPointer();
		}
	},
	hideAdvice: function(className, field){
		var advice = this.getAdvice(field);
		if(advice && advice.visible) advice.show();
	},
	getAdvice: function(className, field) {
		var params = Array.link(arguments, {field: Element.type});
		return params.field.retrieve('PointyTip');
	},
	advices: [],
	makeAdvice: function(className, field, error, warn){
		if (!error && !warn) return;
		var advice = field.retrieve('PointyTip');
		if(!advice){
			var cssClass = warn?'warning-advice':'validation-advice';
			var msg = new Element('ul', {
				styles: {
					margin: 0,
					padding: 0,
					listStyle: 'none'
				}
			});
			var li = this.makeAdviceItem(className, field);
			if (li) msg.adopt(li);
			field.store('validationMsgs', msg);
			advice = new StickyWin.PointyTip(this.options.tipCaption, msg, $merge(this.options.pointyTipOptions, {
				showNow: false,
				relativeTo: field
			}));
			this.advices.push(advice);
			advice.msgs = {};
			field.store('PointyTip', advice);
			$(advice).addClass(cssClass).set('id', 'advice-'+className+'-'+this.getFieldId(field));
		}
		field.store('advice-'+className, advice);
		this.appendAdvice(className, field, error, warn);
		advice.pointy.positionPointer();
		return advice;
	},
	validateField: function(field, force){
		var advice = this.getAdvice(field);
		var anyVis = this.advices.some(function(a){ return a.visible; });
		if (anyVis && this.options.serial) {
			if (advice && advice.visible) {
				var passed = this.parent(field, force);
				if (!field.hasClass('validation-failed')) advice.hide();
			}
			return passed;
		}
		var msgs = field.retrieve('validationMsgs');
		if (msgs) msgs.getChildren().hide();
		if (field.hasClass('validation-failed') || field.hasClass('warning')) if (advice) advice.show();
		if (this.options.serial) {
			var fields = this.form.getElements('.validation-failed, .warning');
			if (fields.length) {
				fields.each(function(f, i) {
					var adv = this.getAdvice(f);
					adv.hide();
				}, this);
			}
		}
		return this.parent(field, force);
	},
	makeAdviceItem: function(className, field, error, warn){
		if (!error && !warn) return;
		var advice = this.getAdvice(field);
		var errorMsg = this.makeAdviceMsg(field, error, warn);
		if (advice && advice.msgs[className]) return advice.msgs[className].set('html', errorMsg);
		return new Element('li', {
			html: errorMsg,
			display: 'none'
		});
	},
	makeAdviceMsg: function(field, error, warn){
		var errorMsg = (warn)?this.warningPrefix:this.errorPrefix;
			errorMsg += (this.options.useTitles) ? field.title || error:error;
		return errorMsg;
	},
	appendAdvice: function(className, field, error, warn) {
		var advice = this.getAdvice(field);
		if (advice.msgs[className]) return advice.msgs[className].set('html', this.makeAdviceMsg(field, error, warn)).show();
		var li = this.makeAdviceItem(className, field, error, warn);
		if (!li) return;
		li.inject(field.retrieve('validationMsgs'));
		li[li.reveal?'reveal':'show']();
		advice.msgs[className] = li;
	},
	insertAdvice: function(advice, field){
		var props = field.get('validatorProps');
		if (!props.msgPos || !$(props.msgPos)) {
			switch (field.type.toLowerCase()) {
				case 'radio':
					var p = field.getParent().adopt(advice);
					break;
				default: 
					$(advice).inject($(field), 'after');
			};
		} else {
			$(props.msgPos).grab(advice);
		}
		advice.position();
	}
});

$extend(FormValidator.resources.enUS, {
	noSpace: 'There can be no spaces in this input.',
	reqChkByNode: 'No items are selected.',
	requiredChk: 'This field is required.',
	reqChkByName: 'Please select a {label}.',
	match: 'This field needs to match the {matchName} field',
	startDate: 'the start date',
	endDate: 'the end date',
	currendDate: 'the current date',
	afterDate: 'The date should be the same or after {label}.',
	beforeDate: 'The date should be the same or before {label}.',
	startMonth: 'Please select a start month',
	sameMonth: 'These two dates must be in the same month - you must change one or the other.'
});

FormValidator.addAllThese([
  ['validate-enforce-oncheck', {
    test: function(element, props) {
      if (element.checked) {
        (props.toEnforce || $(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item) {
          FV.enforceField(item);
        });
      }
      return true;
    }
  }],
  ['validate-ignore-oncheck', {
    test: function(element, props) {
      if (element.checked) {
        (props.toIgnore || $(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item) {
          FV.ignoreField(item);
          FV.resetField(item);
        });
      }
      return true;
    }
  }],
  ['validate-nospace', {
    errorMsg: function(){
				return FormValidator.getMsg('noSpace');
			},
     test: function(element, props) {
					return !element.get('value').test(/\s/);
     }
  }],
  ['validate-toggle-oncheck', {
    test: function(element, props) {
	dbug.log(props);
      var parentForm = element.getParent('form').retrieve('validator');
      var eleArr = props.toToggle || $(props.toToggleChildrenOf).getElements('input, select, textarea');
	dbug.log(eleArr);
      if (!element.checked) {
        eleArr.each(function(item) {
          parentForm.ignoreField(item);
          parentForm.resetField(item);
        });
      } else {
				eleArr.each(function(item) {
					parentForm.enforceField(item);
				});
			}
      return true;
    }
  }],
  ['validate-reqchk-bynode', {
    errorMsg: function(){
			return FormValidator.getMsg('reqChkByNode');
		},
    test: function(element, props) {
      return ($(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
        return item.checked;
      });
    }
  }],
  ['validate-required-check', {
    errorMsg: function(element, props) {
      return props.useTitle ? element.get('title') : FormValidator.getMsg('requiredChk');
    },
    test: function(element, props) {
      return !!element.checked;
    }
  }],
  ['validate-reqchk-byname', {
    errorMsg: function(element, props) {
      return FormValidator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
    },
    test: function(element, props) {
      var grpName = props.groupName || element.get('name');
      var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
        return item.checked;
      });
      var fv = element.getParent('form').retrieve('validator');
      if (oneCheckedItem && fv) fv.resetField(element);
      return oneCheckedItem;
    }
  }],
  ['validate-match', {
    errorMsg: function(element, props) {
			return FormValidator.getMsg('match').substitute({matchName: props.matchName || $(props.matchInput).get('name')});
    }, 
    test: function(element, props){
      var eleVal = element.get('value');
      var matchVal = $(props.matchInput) && $(props.matchInput).get('value');
      return eleVal && matchVal ? eleVal == matchVal : true;
    }
  }],
  ['validate-after-date', {
    errorMsg: function(element, props) {
			return FormValidator.getMsg('afterDate').substitute({
				label: props.afterLabel || (props.afterElement ? FormValidator.getMsg('startDate') : FormValidator.getMsg('currentDate'))
			});
		},
    test: function(element, props) {
      var start = $(props.afterElement) ? Date.parse($(props.afterElement).get('value')) : new Date();
      var end = Date.parse(element.get('value'));
			return end && start ? end >= start : true;
    }
  }],
  ['validate-before-date', {
    errorMsg: function(element, props) {
			return FormValidator.getMsg('beforeDate').substitute({
				label: props.beforeLabel || (props.beforeElement ? FormValidator.getMsg('endDate') : FormValidator.getMsg('currentDate'))
			});
		},
    test: function(element, props) {
      var start = Date.parse(element.get('value'));
      var end = $(props.beforeElement) ? Date.parse($(props.beforeElement).get('value')) : new Date();
			return end && start ? end >= start : true;
    }
  }],
  ['validate-custom-required', {
    errorMsg: function(){
			return FormValidator.getMsg('required');
		},
    test: function(element, props) {
      return element.get('value') != props.emptyValue;
    }
  }],
  ['validate-same-month', {
    errorMsg: function(element, props) {
      var startMo = $(props.sameMonthAs) && $(props.sameMonthAs).get('value');
      var eleVal = element.get('value');
      if (eleVal != '') {
        if (!startMo) { return FormValidator.getMsg('startMonth');}
        else {
          return FormValidator.getMsg('sameMonth');
        }
      }
    },
    test: function(element, props) {
      var d1 = Date.parse(element.get('value'));
      var d2 = Date.parse($(props.sameMonthAs) && $(props.sameMonthAs).get('value'));
      return d1 && d2 ? d1.format("%B") == d2.format("%B") : true;
    }
  }]
]);
FormValidator.resources.IT = {
	required:'Il campo &egrave; richiesto.',
	minLength:'Si prega di inserire un minimo di {minLength} caratteri (Lei ne ha inseriti {length}).',
	maxLength:'Si prega di non inserire più di {maxLength} caratteri (Lei ne ha inseriti {length}).',
	integer:'Si prega di inserire un numero intero. Numeri con decimali (es.: 1.25) non sono permessi.',
	numeric:'Si prega di inserire esclusivamente valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
	digits:'Si prega di usare esclusivamente valori numerici e punteggiatura in questo campo di testo (per esempio, un numero di telefono con trattini o barre non può essere accettato).',
	alpha:'Si prega di utilizzare esclusivamente lettere (a-z) in questo campo di testo. Spazi o altri caratteri non sono accettatti.',
	alphanum:'Si prega di utilizzare esclusivamente lettere (a-z) o numeri (0-9) in questo campo di testo. Spazi o altri caratteri non sono	accettatti.',
	dateSuchAs:'Si prega di inserire correttamente la data: {date}', 
	dateInFormatMDY:'Si prega di inserire la data nel formato: GG/MM/AAAA (es.: "31/12/1999")',
	email:'Si prega di inserire un indirizzo e-mail valido. Per esempio: "mionome@tuodominio.com".',
	url:'Si prega di inserire una URL valida. Per esempio: "http://www.miodominio.com".',
	currencyDollar:'Si prega di inserire una valuta $ corretta. Per esempio: "$100.00".',
	oneRequired:'Si prega di riempire almeno uno dei campi richiesti.',
	warningPrefix: 'Avvertimento: ',
	errorPrefix: 'Errore: '
};
FormValidator.resources.itIT = FormValidator.resources.IT
/*if (Date.$culture == "US") {
	try {
		console.log("WARNING: this validator (Catalan) is designed to use European style formatting, but Date.$culture is set to US. Consider changing this value to 'GB' for DD/MM/YYYY.")
	} catch(e){}
}*/