

jQuery.fn.slideshow = function( options )
{
	if( typeof( options ) == 'string' )
	{
		switch( options )
		{
			case 'next':
			case 'previous':
			case 'start':
			case 'stop':
			case 'resume':
			case 'pause':
				this.each( function( i, o )
				{
					var $show = jQuery(o),
						handle = $show.data( 'slideshow' );
					
					if( !handle )
						return true; // continue;
					
					handle[ options ]();
				});
				
				return;
			break;
			
			default:
				throw Error( 'Operation "'+ options +'" on Slideshow unknown' );
			break;
		}
	}
	
	var defaults = {
			delay: 3000, // 3 Seconds
			duration: 300, // 0.3 Seconds
			layer: 100 // 100th level (z-index)
		};
	
	this.each( function( i, o )
	{
		if( !(o.nodeName.toLowerCase() in {ol:1,ul:1}) )
			return true; // continue;
		
		var $show = jQuery(o),
			$slides = jQuery( 'li', $show ).filter( function(i){ return jQuery(this).parent().get(0) == o && !jQuery(this).hasClass( 'slideshowIgnore' ) } );

		if( !$slides || !$slides.length || $show.data( 'slideshow' ) )
			return true; // continue;

		var status = { transitioning: false, running: false, paused: false, manual: false },
			slide = { index: 0, current: null, emerge: null },
			button = { prev: null, next: null },
			timer = null, timed = null,
			transition = {
				initTransition: function( $show, $slides, opts )
				{
					// reset all slides 
					$slides.css( 'position', 'absolute' ).css( 'zIndex', 0 ).fadeOut();
					$slides.eq( opts.index ).css( 'zIndex', opts.layer ).fadeIn();
				},
				runTransition: function( $current, $emerge, $show, $slides, opts, direction, callback )
				{
					// we only need the callback to be called once...
					$current.css( 'zIndex', 0 ).fadeOut( opts.duration );
					$emerge.css( 'zIndex', opts.layer ).fadeIn( opts.duration, callback );
				}
			},
			opts = {},
			handle = {};

		function delRunTimer()
		{
			if( timer )
				window.clearTimeout( timer );

			timer = null;
		}

		function setRunTimer( delayed )
		{
			delRunTimer();
			
			// continue show only if we're not in manual progression mode
			if( opts.manual )
				return;
			
			if( typeof( delayed ) == 'undefined' && opts.paused )
				return;

			timed = new Date();
			timer = window.setTimeout( handle.run, delayed || opts.delay );
		}
	
		function performTransition( direction )
		{
			opts.transitioning = true;
			var currentIndex = opts.index;
			
			opts.index = jQuery.minmax( opts.index + direction, 0, $slides.length -1 );
			
			opts.current = $slides.eq( currentIndex );
			opts.emerge = $slides.eq( opts.index );
			
			opts.runTransition( opts.current, opts.emerge, $show, $slides, opts, direction, completeTransition );
		}
	
		function completeTransition( direction )
		{
			opts.transitioning = false;
			setRunTimer();
		}
	
		handle.next = function()
		{
			opts.manual = true;
			performTransition( 1 );
		};
	
		handle.previous = function()
		{
			opts.manual = true;
			performTransition( -1 );
		};
	
		handle.run = function()
		{
			performTransition( 1 );
		};
	
		handle.start = function()
		{
			opts.running = true;
			opts.manual = false;
			opts.index = 0;
			setRunTimer();
		};
	
		handle.pause = function()
		{
			opts.paused = true;
			delRunTimer();
		};
	
		handle.resume = function()
		{
			if( !opts.paused || opts.manual )
				return;
		
			opts.paused = false;

			if( ((new Date()).getTime() - timed.getTime()) > opts.delay )
			{
				handle.run();
				return;
			}
		
			setRunTimer();
		};
	
		handle.stop = function()
		{
			opts.running = false;
			delRunTimer();
		};

		var cssOptions = {
			manuals: $show.hasClass( 'slideshowManual' ),
			pausable: $show.hasClass( 'slideshowPausable' ),
			autorun: $show.hasClass( 'slideshowAutorun' )
		};

		$show.data( 'slideshow', handle );
		opts = jQuery.extend( {}, defaults, status, slide, button, transition, cssOptions, options );
		opts.index = opts.step = 0;

		if( opts.manuals )
		{
			function mouseOverListener( e )
			{
				var from = e.relatedTarget;
				while( from && from.length && from.get(0).nodeName == 'BODY' )
				{
					if( from.parentNode == this )
						return;

					from = from.parentNode;
				}

				handle.pause();
			}

			function mouseOutListener( e )
			{
				var from = e.relatedTarget;
				while( from && from.length && from.get(0).nodeName == 'BODY' )
				{
					if( from.parentNode == this )
						return;

					from = from.parentNode;
				}

				handle.resume();
			}
			
			opts.prev = jQuery( '<li class="slideshowIgnore slideshowPrevious"></li>' ).appendTo( $show ).bind( 'click', handle.previous );
			opts.next = jQuery( '<li class="slideshowIgnore slideshowNext"></li>' ).appendTo( $show ).bind( 'click', handle.next );
		}
		
		if( opts.pausable )
		{
			$show.bind( 'mouseover', mouseOverListener ).bind( 'mouseout', mouseOutListener );
		}
		
		opts.initTransition( $show, $slides, opts );
		
		if( opts.autorun )
		{
			setRunTimer();
		}
	} );

	return this;
};

jQuery( function()
{
	jQuery( 'ul.slideshow' ).slideshow( {autorun:true} );
} );