(function( $ ){

	var methods = {
		init : function( options ) {
			var settings = { link : null, required : true };
			$("body")
				.unbind("click.timeselect")
				.bind("click.timeselect", methods.body_click );
			
			return this.each(function(){
				if ( options ) { 
					$.extend( settings, options );
				}
				
				var $this = $(this),
					data = $this.data('timeselect'),
					timeselect = $('<div />')
						.addClass("timeselect")
						.data("timeselect",$this)
						.hide().appendTo("body")
						.click(methods.cancelblur);

				if ( !data ) {
					var diff = null;
					if( settings.link ) {
						if( settings.link.val() != "" && $this.val() != "" )
						{
							diff = methods.timeDiff(settings.link, $this);
						}
						settings.link.data("linkback",$this);
					}

					$this.data('timeselect', {
						target : $this,
						timeselect : timeselect,
						link : settings.link,
						diff : diff
					});
				}

				$this
					.bind("focus.timeselect", methods.focus )
					.bind("keydown.timeselect", methods.keydown )
					.bind("keypress.timeselect", methods.keypress)
					.bind("blur.timeselect", methods.blur)
					.addClass("timeselect_input");
				if( settings.required ) $this.val( $this.val().parseTime() )
			});
		},


		destroy : function( ) {
			return this.each(function(){
				var $this = $(this), data = $this.data('timeselect');

				$(window).unbind('.timeselect');
				data.timeselect.remove();
				$this.removeData('timeselect');
			})
		},



		//########################################################################
		// Event methods

		focus : function(event) {
			var t = $(this), data = t.data("timeselect"), pos = t.position(), ts = data.timeselect, a;
			// event.stopPropagation();
			methods.show(t);
		},

		blur : function(event) {
			var t = $(this), data = t.data("timeselect");
			if( (data.required || t.val() != "") && (t.val() != t.val().parseTime()) ) t.val( t.val().parseTime() );
			methods.update_link(t);
		},

		keydown : function(event) {
			var t = $(this), data = t.data("timeselect"), diff, ts = data.timeselect;

			// Enter
			if( event.keyCode == 13 ) {
				return methods.handle_enter(t);
			}

			// Tab
			if( event.keyCode == 9 ) {
				methods.hide(t);
				return true;
			}

			// Up / Down
			if( event.keyCode == 38 || event.keyCode == 40 ) {
				if( ts.is(":hidden") ) {
					methods.show(t);
					return false;
				}

				var sel = ts.find(".hover");

				if( sel.length == 0 ) {
					sel = ts.find("a:first");
					sel.addClass("hover");
					return false;
				}

				ts.find("a").removeClass("hover");
				sel2 = event.keyCode == 38 ? sel.prev() : sel.next();
				if( sel2.length == 0 ) sel2 = sel;
				sel2.addClass("hover");

				var i = ts.find("a").index(sel2);
				ts.scrollTop((i-3)*20)

				return false;
			}

			methods.hide(t);
			return true;
		},

		keypress : function(event) {
			if( event.keyCode == 13 )
			{
				return methods.handle_enter($(this));
			}
			return true;
		},

		click : function(event) {
			var a = $(this), val = a.data("timeselect"), ts = a.parent(), t = ts.data("timeselect");
			
			if( t.val() != val ) t.val( val );
			methods.update_link(t);
			methods.hide(t);
			t.change();
			
			return false;
		},

		body_click : function(event) {
			var c = $(event.target).closest(".timeselect_input,.timeselect");
			if( c.length == 0 || c.hasClass("timeselect_input") ) $(".timeselect").hide();
			if( c.hasClass("timeselect_input") ) {
				methods.show(c);
			}
			// return true;
		},



		//########################################################################
		// Functional methods

		hover_on : function(a) {
			var p = a.parent();
			p.find(".hover").removeClass("hover");
			a.addClass("hover");
		},
		
		hover_off : function(a) {
			a.removeClass("hover");
		},

		handle_enter : function(t) {
			var data = t.data("timeselect"), ts = data.timeselect;

			if( ts.is(":hidden") ) return true;

			var sel = ts.find(".hover");
			if( sel.length == 0 ) return false;

			methods.select_hover(t);
			methods.update_link(t);
			methods.hide(t);

			return false;
		},


		select_hover : function(t) {
			var data = t.data("timeselect"), ts = data.timeselect, sel = ts.find(".hover");
			if( sel.length == 0 ) return;
			t.val( sel.data("timeselect") );
		},


		show : function(t) {
			var data = t.data("timeselect"), pos = t.offset(), ts = data.timeselect, a, settime, start = "12:00am", scroll_pos = 0, uselink = false;

			if( data.link && $(data.link).val() != "" ) {
				uselink = true;
				start = methods.timeAdd( $(data.link).val(), 30 );
			}

			ts.empty();
			for( var i=0; i < 48; i++ )
			{
				settime = methods.timeAdd( start, i*30 );
				a = $("<a>")
					.attr("href","#")
					.text( settime )
					.data("timeselect", settime )
					.click( methods.click )
					.hover( methods.hover_on, methods.hover_off );
				if( settime == t.val() ) {
					a.addClass("hover");
					scroll_pos = i;
				}
				if( uselink && i%2 == 1 && i > 0 ) {
					a.text( a.text() + " (" + Math.ceil(i/2) + " hours)");
				}
				ts.append( a );
			}
			
			ts
				.css({top: (pos.top+t.outerHeight())+"px", left: (pos.left+1)+"px","min-width": t.outerWidth()+"px"})
				.show()
				.scrollTop((scroll_pos-3)*20)
				// .doTimeout("settop",0,function(){ $(this).scrollTop((scroll_pos-3)*20); });

			return true;
		},


		set_top : function(x) {
			$(".timeselect").scrollTop(x);
		},


		hide : function(t) {
			t.data("timeselect").timeselect.hide();
		},


		update_link : function(t) {
			var data = t.data("timeselect");

			if( data.link ) {
				diff = methods.timeDiff( data.link, t );
				if( diff || diff === 0 ) {
					data.diff = diff;
					t.data( "timeselect", data );
				}
			}
			else if( t.data("linkback") ) {
				var tlink = t.data("linkback"), datalink = tlink.data("timeselect"), diff = 60;
				if( t.val() != "" )
				{
					var newtime = methods.timeAdd( t.val(), datalink.diff || diff );
					if(  tlink.val() != newtime ) tlink.val( newtime );
				}
			}
		},

		update : function( ) {
			var t = $(this), data = t.data("timeselect");
			if( t.val() != t.val().parseTime() ) t.val( t.val().parseTime() );
			return true;
		},

		timeDiff : function(s,en) {
			var start = $(s).val().parseTime(true),
				end = $(en).val().parseTime(true),
				diff = 0;

			if( !start || !end ) return false;

			diff = (end.hours - start.hours) * 60;
			if( diff < 0 ) diff += (24 * 60);
			diff += end.minutes - start.minutes;

			return diff;
		},

		timeAdd : function(t,a) {
			var o = t.parseTime(true);
			if( !o ) return "12:00pm";

			var n = o.hours*60 + o.minutes + a;
			if( n >= (24*60) ) {
				n -= (24*60);
			}

			var hours = Math.floor(n / 60);
			var minutes = n % 60;
			var p = "am";
			if( hours == 12 ) p = "pm";
			else if( hours >= 12 ) { p = "pm"; hours -= 12; }
			else if( hours == 0 ) { p = "am"; hours = 12; }

			return hours + ":" + ((minutes < 10) ? ("0"+minutes) : minutes) + p;
		}
	};


	$.fn.timeselect = function( method ) {
		if ( methods[method] ) {
			return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
		}
		else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		}
		else {
			$.error( 'Method ' +	method + ' does not exist on jQuery.timeselect' );
		}
	
	};


})( jQuery );


String.prototype.reversestring = function() {
	var f = "";
	for( var i=this.length; i >= 0; i-- ) {
		f += this.charAt(i);
	}
	return f;
}

String.prototype.trimstring = function() {
	return this.replace(/^\s*|\s*$/g, "");
}

String.prototype.parseTime = function(return_object) {
	var value = (this+"").trimstring().reversestring(),
		timeParts = value.match(/(a|p)?\s*((\d{2})?:?)(\d{1,2})/i);
	if (!timeParts) { return "12:00pm"; }

	timeParts = timeParts.reverse();
	for( var i = 0; i < timeParts.length; i++ ) {
		timeParts[i] = timeParts[i] === undefined ? '' : timeParts[i].reversestring();
	}
	var minutes = (parseInt(timeParts[1], 10) || 0),
		hours = parseInt(timeParts[0], 10),
		afternoon = (timeParts[3].toLowerCase() == 'p') ? true : false;

	afternoon = (!timeParts[3] && hours == 12) ? true : afternoon;
	afternoon = (hours > 12) ? true : afternoon;
	hours -= (hours > 12) ? 12 : 0;
	hours += afternoon && (hours != 12) ? 12 : 0;
	hours -= !afternoon && (hours == 12) ? 12 : 0;

	var valid = true;
	if( minutes >= 60 || hours >= 24 ) {
		valid = false;
	}

	if( return_object && valid ) return {"hours":hours,"minutes":minutes};
	else if( return_object ) return false;
	else if( !valid ) return "12:00pm";

	var d = "";
	d += (hours > 12) ? (hours - 12) : ( (hours == 0) ? 12 : hours );
	d += ":" + ((minutes < 10) ? ("0"+minutes) : minutes);
	d += (afternoon) ? "pm" : "am";
	return d;
}

