(function($) {
	$.fn.carosl = function(o) {
		
		o = $.extend({
			speed : 200,
			easing : null,
			scroll: 1,
			beforeScroll : null,		// Callback handlers.
			afterScroll : null
		}, o || {});
		
		// Carry out the following for each matching element.
		return this.each(function() {
			var ul = $(this);
			
			// New DOM elements that we'll be creatings
			var carosl = null, ul_frame = null, btn_prev = null, btn_next = null;
			var li = null, dummy_ul = null;
			
			// Convenience variables
			var li_count = 0, ul_width = 0, li_widths = [], current_pos = 0, running = false;
			var active_top = null, active_sub = null;
			var submenu_timer = null, submenu_delay = 100;

			init_dom();		// Lay the groundwork...
			init_css();		// We apply some basic styles essential to the plugin using JS.
			init_handlers();		// Initialise the event handlers.
			
			
			/**
			 * Handles the showing and hiding of the submenus.
			 */
			function handle_submenu() {
				// Clear the timeout
				if (submenu_timer)
					clearTimeout(submenu_timer);
				
				// Over a top-level menu item, with a submenu to display.
				if ((active_top) && (active_sub)) {
					li_pos = active_top.position();
					ul_pos = ul.position();
					ul_frame_pos = ul_frame.position();
					
					li_real_left = li_pos.left + ul_pos.left;
					li_real_right = li_pos.left + active_top.outerWidth() + ul_pos.left;
					
					// If the list item is not fully within the "viewport", don't display the submenu.
					if ((li_real_left < 0) || (li_real_right > ul_frame.innerWidth())) {
						active_top = false;
						active_sub = false;
						$('> li', ul).removeClass('open');
						handle_submenu();
						return;
					}
					
					// Remove the "open" class from all top-level menu items except the active one.
					$('> li', ul).not(active_top).removeClass('open');
					
					// Add a class to the top-level menu item.
					active_top.addClass('open');					

					// Calculate the position of the submenu
					sub_left_offset = ul_frame_pos.left + parseInt(ul_frame.css('margin-left')) + parseInt(ul_frame.css('padding-left')) + parseInt(ul_frame.css('border-left-width'));
					sub_top_offset = ul_frame_pos.top + parseInt(ul_frame.css('margin-top')) + parseInt(ul_frame.css('padding-top')) + parseInt(ul_frame.css('border-top-width')) + parseInt(ul_frame.css('padding-bottom')) + parseInt(ul_frame.css('border-bottom-width'));
					sub_left = sub_left_offset + li_pos.left + ul_pos.left;
					sub_top = sub_top_offset + ul_pos.top + active_top.outerHeight();		// Note: measure the LI, not the UL,
																																							// in case the UL isn't clearfixed
					
					// Hide all other submenus
					$('.carosl-submenu', carosl).not(active_sub).hide();

					// Display the submenu
					active_sub.css({'top': sub_top, 'left': sub_left});
					active_sub.fadeIn('fast');
				}
				
				// Not over a top-level menu item, with no submenu to display.
				if ((!active_top) && (!active_sub)) {
					$('> li', ul).removeClass('open');
					$('.carosl-submenu', carosl).fadeOut('fast');
				}
			}
			
			
			/**
			 * Scrolls the ul to the target position, taking into account visible items
			 * and 'circular' scrolling.
			 * @param int target_pos The target list item (could be considered the target index in the li array).
			 */
			function scroll_to(target_pos, next) {
				var min_px = ul_frame.innerWidth() - ul_width;
				var max_px = 0;
				
				// TRICKY:
				// We've duplicated all the original list items to create a 
				// double-length list. In order for things to wrap ad-infinitum
				// we need to shift everything left by exactly half the length of
				// our new "double-list" every time we get over halfway.
				if (Math.abs(ul.position().left) >= (ul_width * 0.5)) {
					new_left = ul.position().left + (ul_width * 0.5);
					ul.css({'left': new_left + 'px'});
					target_pos -= (li_count / 2);
				}
				
				if ((ul.position().left == 0) && (!next)) {
					ul.css({'left': -(ul_width * 0.5) + 'px'});
					target_pos += (li_count / 2);
				}
						
				if (running)
					return;
					
				if (o.beforeScroll)		// Call beforeScroll function
					o.beforeScroll.call(this);								
				
				running = true;
				target_px = max_px;
				
				for (i = 0; i < target_pos; i++) {
					target_px -= li_widths[i];
				}
				
				if (target_px <= min_px)
					target_px = min_px;
				
				ul.animate({left: target_px + 'px'}, o.speed, o.easing, function() {
					if (o.afterScroll)		// Call afterScroll function
						o.afterScroll.call(this);						
					running = false;
				});
				
				current_pos = target_pos;
			};
			
			
			/**
			 * Initialises the event handlers for the scroll buttons, and the drop-down menus.
			 */
			function init_handlers() {
				btn_prev.click(function() {
					scroll_to(current_pos - o.scroll, false);
					return false;
				});
				
				btn_next.click(function() {
					scroll_to(current_pos + o.scroll, true);
					return false;
				});
				
				li.hover(function() {
					active_sub = $('#' + $('a', $(this)).attr('rel'));
					active_top = $(this);
					handle_submenu();
				}, function() {
					active_top = false;
					active_sub = false;
					submenu_timer = setTimeout(handle_submenu, submenu_delay);
				});
				
				$('.carosl-submenu', carosl).hover(function() {
					active_sub = $(this);
					handle_submenu();
				}, function() {
					active_sub = false;
					submenu_timer = setTimeout(handle_submenu, submenu_delay);
				});
			};
			
			
			/**
			 * Applies the bare-minimum of essential CSS styles. Anything more fancy is left
			 * where it should be -- in the CSS.
			 */
			function init_css() {
				ul_width = 0;
				
				// IMPORTANT: MUST APPLY THESE STYLES BEFORE CALCULATING THE WIDTH.
				$('li', ul_frame).css({'float': 'left'});
								
				// Calculate the total width of all the list items.
				li.each(function() {
					ul_width += $(this).outerWidth({margin: true});
					li_widths.push($(this).outerWidth({margin: true}));
				});
				
				carosl.css({'position': 'relative'});
				ul_frame.css({'overflow': 'hidden', 'position': 'relative'});
				ul.css({'position': 'relative', 'left': '0px', 'width': (ul_width + 5) + 'px', 'z-index': '10'});	// Add 5px just to be sure.
			}; // init_css
			
			
			/**
			 * Creates additional DOM elements required by the plugin, and move some things
			 * around in preparation for the drop-down.
			 */
			function init_dom() {
				var base_id, ul_id;
				
				// Create the wrapper divs
				ul.wrap('<div class="carosl-frame"></div>');
				ul.parent('div.carosl-frame').wrap('<div class="carosl"></div>');
				
				// Shortcut variables
				carosl = ul.parents('div.carosl');
				ul_frame = ul.parent('div.carosl-frame');
				
				// Create the buttons
				btn_prev = $('<p class="carosl-button prev"><a href="#" title="Previous">Previous</a></p>').prependTo(carosl);
				btn_next = $('<p class="carosl-button next"><a href="#" title="Next">Next</a></p>').appendTo(carosl);
				
				// Extract the sub-menus, and place them in the root of the carousel.
				base_id = 'carosl-submenu-';

				$('ul ul', ul_frame).each(function() {					
					// Generate a unique ID.
					ul_id = base_id + parseInt(Math.random() * 99999);
					
					$(this).attr('id', ul_id);													// Unique ID for each sub-menu.
					$(this).addClass('carosl-submenu');									// Convenience class hook for CSS.
					$(this).parent('li').find('a').attr('rel', ul_id);	// Assign ID to rel of parent anchor.
					$(this).remove().prependTo(carosl);		// Extract sub-menu, and insert at start of carosl div.
				});
				
				// Implementing the wrapping.												
				$('li', ul).clone().appendTo(ul);		// Duplicate all the list items to make the wrapping effect more convincing.
				
				// Shortcut variables for the list items.
				li = $('li', ul);
				li_count = li.length;
			}; // init_dom
		});
		
	} // carosl
})(jQuery);