var jc = {
	init:function(o){
		jc.settings = $.extend({
			slideShow_interval:3000,
			slideShow_indexPosition:'bottom right'
		},o);
		jc.form.init();
		jc.ui.init();
	},
	util:{
		dump:function(o){
			x = function(obj,prefix){
				prefix = (typeof prefix == 'undefined')?'':prefix;
				var str = '';
				for (var i in obj) {
					str += prefix+''+i+' = '+obj[i]+'\n';
					str += x(obj[i],prefix+'   ');
				}
				return str;
			}	
			alert(x(o));
		},
		string:{
			regexCharFilters:{
				email:'[^a-zA-Z0-9@-_.~]', // 'abcdefghijklmnopqrstuvwxyz0123456789@-_.~'
				alpha:'[^a-zA-Z]', // 'abcdefghijklmnopqrstuvwxyz'
				noSymbols:'[^a-zA-Z0-9 ]', // 'abcdefghijklmnopqrstuvwxyz0123456789 '
				noSpace:'[^a-zA-Z0-9]', // 'abcdefghijklmnopqrstuvwxyz0123456789'
				integer:'[^0-9]', // '0123456789'
				numeric:'[^0-9-.]', // '0123456789-.'
				dollar:'[^0-9., -]' // '0123456789., -'
			},
			isClean:function(str,regexFilter){
				regex = jc.util.string.regexCharFilters[regexFilter];
				var reg = new RegExp(regex,'gi');
				return !reg.test(str);
			},
			isCharCodeClean:function(charCode,regexFilter){
				var str = String.fromCharCode(charCode);
				return jc.util.string.isClean(str,regexFilter);
			},
			clean:function(str,regexFilter){
				regex = jc.util.string.regexCharFilters[regexFilter];
				var reg = new RegExp(regex,'gi');
				return str.replace(reg,'');
			},
			passwordStrength:function(pwd){
				var strong = new RegExp("^(?=.{8,})(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*\\W).*$", "g");
				var medium = new RegExp("^(?=.{7,})(((?=.*[A-Z])(?=.*[a-z]))|((?=.*[A-Z])(?=.*[0-9]))|((?=.*[a-z])(?=.*[0-9]))).*$", "g");
				var weak = new RegExp("(?=.{6,}).*", "g");
				var r = '';
				if (!weak.test(pwd)) r = 'incomplete';
				else if (strong.test(pwd)) r = 'strong';
				else if (medium.test(pwd)) r = 'medium';
				else if (weak.test(pwd)) r = 'weak';
				return r;
			},
			isInt:function(n){
				var reInt = new RegExp(/^-?\d+$/);
				if (!reInt.test(n)) {
					return false;
				}
				return true;
			},
			isEmail:function(str) {
				var filter  = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
				return (filter.test(str));
			}
		},
		/*object:{
			toList:function(obj){
				var list = '';
				if(typeof(obj) == 'object'){
					for (i in obj){
						if (typeof(obj[i]) == 'object') {
							obj[i] = jc.util.object.toList(obj[i]);
						}
						if (list != '')
							list += ",";
						list += obj[i].replace(',', '&#44;');
					}
				} else {
					list = obj;
				}
				return list;
			},
		},*/
		number:{
			commaFormat:function(nStr){
				nStr += '';
				x = nStr.split('.');
				x1 = x[0];
				x2 = x.length > 1 ? '.' + x[1] : '';
				var rgx = /(\d+)(\d{3})/;
				while (rgx.test(x1)) x1 = x1.replace(rgx, '$1' + ',' + '$2');
				return x1 + x2;
			},
			dollarFormat:function(n){
				if (typeof n != 'number') return '0.00';
				var n1 = n.toFixed(2);
				var n2 = jc.util.number.commaFormat(n1);
				return n2;
			},
			padZeros:function(num,zeros){
				var str = ''+num;
				while (str.length < zeros) str = '0'+str;
				return str;
			}
			/*,
			format:function(n,d,t){
				if (typeof n != 'number' || n == '') return ''; // number
				if (typeof d == 'undefined') d = 2; // decimal places
				if (typeof t == 'undefined') t = ','; // thousand delimiter
				
				if (n%1>0) {
					l = 10^d;
					n = Math.round(n*l)/l;
				}
				
				return n;
			}*/
		}
	},
	ui:{
		init:function(){
			$.each($('div.jc-slideShow'),function(i,e){ jc.ui.slideShow.init($(e)) });
			$.each($('a.jc-ddMenu'),function(i,e){ jc.ui.ddMenu.init($(e)) });
			$.each($('.jc-countdown'),function(i,e){ jc.ui.countdown.init($(e)) });
		},
		slideShow:{
			init:function(ss){
				$(ss).find('.jc-slide').width($(ss).width()).height($(ss).height()); //.click(function(){ jc.ui.slideShow.next($(this).closest('.jc-slideShow')); }); // set inividual slide dimensions
				if (ss.find('.jc-slide-active').length == 0) ss.find('.jc-slide').first().addClass('jc-slide-active'); // set first slide to be active
				jc.ui.slideShow.restartTimer(ss);
				var slideCount = $(ss).find('.jc-slide').length;
				// add index controls
				var control = $('<div class="jc-slide-control" />').appendTo(ss);
				// position
				if (jc.settings.slideShow_indexPosition.indexOf('bottom') >= 0) control.css('bottom','5px');
				else if (jc.settings.slideShow_indexPosition.indexOf('top') >= 0) control.css('top','5px');
				if (jc.settings.slideShow_indexPosition.indexOf('left') >= 0) control.css('left','5px');
				else if (jc.settings.slideShow_indexPosition.indexOf('right') >= 0) control.css('right','5px');
				// slides
				$.each($(ss).find('.jc-slide'),function(i,e){
					$('<div class="jc-slide-control-node" />').appendTo(control).click(function(){ jc.ui.slideShow.slideTo($(e)); });
					$(e).attr('jc-index',i);
				});
				control.css('width',(slideCount*(12+5))+'px');
				// marker
				$('<div class="jc-slide-control-marker" />').appendTo(control);
			},
			next:function(ss){
				var e1 = ss.find('.jc-slide-active');
				var e2 = e1.next('.jc-slide');
				if (e2.length == 0) e2 = ss.find('.jc-slide').first();
				jc.ui.slideShow.slideTo(e2);
			},
			previous:function(ss){
				var e1 = ss.find('.jc-slide-active');
				var e2 = e1.previous('jc-slide');
				if (e2.length == 0) e2 = ss.find('jc-slide').last();
				jc.ui.slideShow.slideTo(e2);
			},
			slideTo:function(e2){
				var s = $(e2).closest('.jc-slides');
				$(s).animate({left:'-'+e2.position().left+'px'}, function(){
					s.find('.jc-slide-active').removeClass('jc-slide-active');
					$(e2).addClass('jc-slide-active');
					jc.ui.slideShow.restartTimer($(e2).closest('.jc-slideShow'));
				});
				var m = $(s).closest('.jc-slideShow').find('.jc-slide-control-marker');
				var i = $(e2).attr('jc-index');
				$(m).animate({left:(i*16)+'px'});
			},
			restartTimer:function(ss){
				var t = $(ss).data('timer');
				if (typeof t != 'undefined')
					if (t != null)
						clearTimeout(t);
				$(ss).data('timer',setTimeout(function(){ jc.ui.slideShow.next(ss); },jc.settings.slideShow_interval));
			}
		},
		ddMenu:{
			init:function(a){
				var dd = $(a).siblings('div.jc-ddMenu'); // find and drop down
				$(a).data('jc-ddClone',dd.clone()); // clone/store it for use later
				dd.remove(); // remove original drop down HTML from DOM (waste of memory)
				$(a).mouseover(function(){ jc.ui.ddMenu.show($(this)); });
			},
			show:function(a){
				var dd = a.data('jc-ddClone');
				dd.appendTo(document.body);
				var p = a.offset();
				dd.css({top:(p.top+a.height())+'px',left:p.left+'px'});
				dd.slideDown('fast');
				$(document).click(function(e){
					if ($(e.target).closest('.jc-ddMenu').length == 0)
						jc.ui.ddMenu.hide(dd);
				});
				$('.jc-ddMenu').mouseout(function(e){
					if ($(e.relatedTarget).closest('.jc-ddMenu').length == 0) 
						jc.ui.ddMenu.hide(dd);
				});
			},
			hide:function(dd){
				dd.slideUp('fast',function(){ $(dd).remove(); });
			}
		},
		countdown:{
			init:function(e){
				var date1 = $(e).find('.jc-countdown-date-start').html();
				if (date1 != '') date1 = new Date(date1); else return;
				
				var date2 = $(e).find('.jc-countdown-date-end').html();
				if (date2 != '') date2 = new Date(date2); else return;
				
				var advance = $(e).find('.jc-countdown-advance').html();
				advance = (!isNaN(parseInt(advance)))?parseInt(advance):0;
				
				var complete_el = $(e).find('.jc-countdown-complete');
				if (complete_el.length == 0) complete_el = null;
				
				var day_el = $(e).find('.jc-countdown-days');
				var hr_el = $(e).find('.jc-countdown-hours');
				var min_el = $(e).find('.jc-countdown-minutes');
				var sec_el = $(e).find('.jc-countdown-seconds');
				if (day_el.length == 0 || hr_el.length == 0 || min_el.length == 0 || sec_el.length == 0) return;
				
				jc.ui.countdown.interval(date1, date2, advance, day_el, hr_el, min_el, sec_el, complete_el);
			},
			interval:function(date1,date2,advance,day_el,hr_el,min_el,sec_el,complete_el){
				var oneSec_ms = 1000;
				var oneMin_ms = oneSec_ms*60;
				var oneHr_ms = oneMin_ms*60;
				var oneDay_ms = oneHr_ms*24;
				var date1_ms = date1.getTime();
				var date2_ms = date2.getTime();
				var advance_ms = advance*oneMin_ms;
				
				if (date1_ms < (date2_ms-advance_ms)) {
					date1_ms += oneSec_ms;
					var diff = (date2_ms-advance_ms)-date1_ms;
									
					var days = Math.floor(diff/oneDay_ms); // days till date2
					diff -= (days*oneDay_ms);
					var hours = Math.floor(diff/oneHr_ms); // hours till end of today
					diff -= (hours*oneHr_ms);
					var mins = Math.floor(diff/oneMin_ms); // hours till end of hour
					diff -= (mins*oneMin_ms);
					var secs = Math.floor(diff/oneSec_ms); // seconds till end of min
					
					// populate html
					$(day_el).html(jc.util.number.padZeros(days,2));
					$(hr_el).html(jc.util.number.padZeros(hours,2));
					$(min_el).html(jc.util.number.padZeros(mins,2));
					$(sec_el).html(jc.util.number.padZeros(secs,2));
					
					date1 = new Date(date1_ms);
					date2 = new Date(date2_ms);
					
					// call this function again in 1 second
					setTimeout(function(){ jc.ui.countdown.interval(date1, date2, advance, day_el, hr_el, min_el, sec_el, complete_el); },1000);
				} else {
					$(day_el).html('00');
					$(hr_el).html('00');
					$(min_el).html('00');
					$(sec_el).html('00');
					if (complete_el != null) {
						var count = $(complete_el).parent('.jc-countdown').get(0);
						var dup = $(complete_el).clone().css({display:'block'});
						$(count).css({display:'none'}).after(dup);
					}
				}
			}
		}
	},
	form:{
		callback_onFieldError:function(e,msg){},
		callback_onFieldSuccess:function(e){},
		callback_onFormError:function(e,msg){},
		callback_onFormSuccess:function(e){},
		isBatchValidation:0,
		init:function(sel, formError, formSuccess, fieldError, fieldSuccess){
			if (typeof sel == 'undefined') sel = document.body; // search entire DOM by default
			if (typeof fieldError != 'function') fieldError = jc.form.callback_onFieldError;
			if (typeof fieldSuccess != 'function') fieldSuccess = jc.form.callback_onFieldSuccess;
			if (typeof formError != 'function') formError = jc.form.callback_onFormError;
			if (typeof formSuccess != 'function') formSuccess = jc.form.callback_onFormSuccess;
			
			// form handlers
			$(sel).find('form').submit(function(ev){
				var canSubmit = jc.form.validate($(this));
				if (canSubmit) formSuccess($(this));
				else { 
					formError($(this),'There are errors in the form.');
					ev.stopImmediatePropagation();
				}
			});
			
			// initialize all form fields
			jc.form.field.init(sel, fieldError, fieldSuccess);
		},
		fireFn:function(input, test, fnError, errorMessage, fnSuccess){
			var er = '';
			// is required
			if (input.hasClass('required') && jc.form.field.isEmpty(input))
				er += 'This field is required. ';
			// match value test
			if (input.data('matchValue') != null) {
				if (input.val() != input.data().matchValue.val())
					if (input.hasClass('password'))
						er += 'The passwords do not match. ';
					else
						er += 'The value do not match. ';
			}
			
			// custom input type test
			if (!test)
				er += errorMessage+' ';
			
			if (er != '' && typeof fnError == 'function') fnError(input,er,jc.form.isBatchValidation);
			else if (er == '' && typeof fnSuccess == 'function') fnSuccess(input,jc.form.isBatchValidation);
			
			return (er == '');
		},
		// loop through a form and validates all the fields
		validate:function(form){
			// track errors
			var errors = 0;
			jc.form.isBatchValidation = 1;
			// loop through all form fields
			$.each($(form).find('input, select, textarea'),function(i,e){
				// call validation (what would usually happen onblur)
				var validate = $(this).data().validate;
				if (typeof validate == 'function') errors += Number(!validate($(this)));		
			});
			jc.form.isBatchValidation = 0;
			return (errors == 0);
		},
		unvalidate:function(formSel){
			// loop through all form fields
			$(formSel).find('input, select').each(function(i,e){
				// call validation (what would usually happen onblur)
				var onSuccess = $(this).data().onSuccess;
				if (typeof onSuccess == 'function') onSuccess($(this));		
			});
			return true;
		},
		restrictInput:{
			apply:function(e,type){
				$(e).keypress(function(ev){ ev.returnValue = jc.form.restrictInput.validateKeyPress(ev.keyCode,type); });
				$(e).blur(function(ev){ jc.form.restrictInput.cleanInput(e,type); });
			},
			validateKeyPress:function(charCode,type){
				return jc.util.string.isCharCodeClean(charCode,type);
			},
			cleanInput:function(e,type){
				var origStr = $(e).val();
				var cleanedStr = jc.util.string.clean(origStr,type);
				$(e).val(cleanedStr);
			}
		},
		field:{
			init:function(sel, fnError, fnSuccess){
				if (typeof sel == 'undefined') sel = document.body; // search entire DOM by default
				if (typeof fnError == 'undefined') fnError = null;
				if (typeof fnSuccess == 'undefined') fnSuccess = null;
				// common input bindings
				$.each($(sel).find('input, select, textarea'),function(i,e){
					$(e).data('onError',fnError); // store a pointer to the validation function in the data object
					$(e).data('onSuccess',fnSuccess); // store a pointer to the validation function in the data object
					if ($(e).attr('matchvalue') != null) $(e).data('matchValue',$('#'+$(e).attr('matchvalue')));
				});
				// add's type specific event handlers and formatting
				for (var i in jc.form.field.types) {
					$(sel).find('input.'+i+', textarea.'+i).each(function(j,e){ 
						jc.form.field.types[i].init(e);
					}); 
				}
			},
			isEmpty:function(input){
				// is the field empty or not (text value = '', selectbox selected index = 0, radio/checkbox = unchecked)
				var tag = $(input)[0].tagName;
				var type = $(input).attr('type');
				if (tag == 'INPUT' || tag == 'TEXTAREA')
					if (type == 'checkbox' || type == 'radio') return ($(input).attr('checked') == false);
					else return ($(input).val() == '');
				else if (tag == 'SELECT') return ($(input).selectedIndex == null);
			},
			types:{
				email:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.email.validate); // store a pointer to the validation function in the data object
						jc.form.restrictInput.apply(input,'email');
						$(input).blur(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var test = (jc.util.string.isEmail(input.val()));
						var d = $(input).data();
						return jc.form.fireFn(input, test, d.onError, 'Invalid email address.', d.onSuccess);
					}
				},
				text:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.text.validate); // store a pointer to the validation function in the data object
						$(input).blur(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var test = true;
						var d = $(input).data();
						return jc.form.fireFn(input, test, d.onError, 'Invalid text.', d.onSuccess);
					}
				},
				alpha:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.alpha.validate); // store a pointer to the validation function in the data object
						jc.form.restrictInput.apply(input,'alpha');
						$(input).blur(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var d = $(input).data();
						return jc.form.fireFn(input, true, d.onError, '', d.onSuccess);
					}
				},
				password:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.password.validate); // store a pointer to the validation function in the data object
						$(input).blur(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var test = ($(input).val().indexOf(' ') == -1); // no spaces (simple validation)
						var d = $(input).data();
						return jc.form.fireFn(input, test, d.onError, 'Your password cannot contain any spaces.', d.onSuccess);
					}
				},
				date:{
					init:function(input){
						$(input).datepicker();
						$(input).data('validate',jc.form.field.types.date.validate); // store a pointer to the validation function in the data object
						//$(input).blur(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var test = true; // no validation
						var d = $(input).data();
						return jc.form.fireFn(input, test, d.onError, '', d.onSuccess);
					}
				},
				checkbox:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.checkbox.validate); // store a pointer to the validation function in the data object
						$(input).click(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var test = true; // no test
						var d = $(input).data();
						return jc.form.fireFn(input, test, d.onError, '', d.onSuccess);
					}
				},
				dollar:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.dollar.validate); // store a pointer to the validation function in the data object
						$(input).before('<span class="fieldPrefix">$&nbsp;</span>');
						jc.form.restrictInput.apply(input,'dollar');
						$(input).click(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var test = true; // no test
						var d = $(input).data();
						return jc.form.fireFn(input, test, d.onError, '', d.onSuccess);
					}
				},
				float:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.float.validate); // store a pointer to the validation function in the data object
						$(input).attr('maxlength','10');
						jc.form.restrictInput.apply(input,'numeric');
						$(input).click(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var test = true; // no test
						var d = $(input).data();
						return jc.form.fireFn(input, test, d.onError, '', d.onSuccess);
					}
				},
				integer:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.integer.validate); // store a pointer to the validation function in the data object
						$(input).attr('maxlength','10');
						jc.form.restrictInput.apply(input,'integer');
						$(input).blur(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var d = $(input).data();
						return jc.form.fireFn(input, true, d.onError, '', d.onSuccess);
					}
				},
				integer5:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.integer.validate); // store a pointer to the validation function in the data object
						$(input).attr('maxlength','5');
						jc.form.restrictInput.apply(input,'integer');
						$(input).blur(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					}
				},
				integer3:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.integer.validate); // store a pointer to the validation function in the data object
						$(input).attr('maxlength','3');
						jc.form.restrictInput.apply(input,'integer');
						$(input).blur(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					}
				},
				percent:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.percent.validate); // store a pointer to the validation function in the data object
						$(input).after('<span class="fieldSuffix">&nbsp;%</span>');
						$(input).attr('maxlength','6');
						jc.form.restrictInput.apply(input,'numeric');
						$(input).click(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var test = true; // no test
						var d = $(input).data();
						return jc.form.fireFn(input, test, d.onError, '', d.onSuccess);
					}
				},
				creditCard:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.creditCard.validate); // store a pointer to the validation function in the data object
						$(input).attr('maxlength','16');
						jc.form.restrictInput.apply(input,'integer');
						$(input).blur(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var test = $(input).val().length >= 14; // credit cards are aleast 14 digits long
						var d = $(input).data();
						return jc.form.fireFn(input, test, d.onError, 'Invalid card number.', d.onSuccess);
					}
				},
				pczip:{
					init:function(input){
						$(input).data('validate',jc.form.field.types.pczip.validate); // store a pointer to the validation function in the data object
						$(input).attr('maxlength','12');
						jc.form.restrictInput.apply(input,'noSpace');
						$(input).blur(function(){ $(this).data().validate($(this)); }); // BIND validation on blur
					},
					validate:function(input){
						var test = $(input).val().length >= 5; // postal codes and zips need to be atleast 5 chars long
						var d = $(input).data();
						return jc.form.fireFn(input, test, d.onError, 'Invalid entry.', d.onSuccess);
					}
				}
			}
		}		
	}
};
