function PhotoSlideShow(userOptions) {
	var options = jQuery.extend({}, userOptions);
	var iterator = 0, 
		queue = 0,	
		stopped = false,			
		now = null, 
		next = null, 
		elem = null, 
		newElem = null,
		current = null, 
		blockClicked = null,
		currentAnimation = null, 
		progressBlocks = null,	
		showProgress = options.showProgress || false,
		autoPlay = options.autoPlay || true,
		fadeTimeout = options.fadeTimeout || 5000,					
		fadeSpeed = options.fadeSpeed || 'slow', 						
		home = options.wrapper || $('#image_wrapper'), 
		imgs = options.imageClass ? home.find('.' + options.imageClass) : home.find('.image'), 		
		control = options.controller || $('#controller'),
		startControl = options.startControl || $('.start'),
		stopControl = options.stopControl || $('.stop'),
		currentClass = options.currentClass || 'current',
		progressElement = options.progressElement || 'span',
		progressElementClass = options.progressElementClass || null,
		clearFloat = options.clearFloat || true,
		clearFloatHTML = options.clearFloatHTML || '<div class="clear"></div>',
		showSlider = options.showSlider || false,
		sliderElement = options.sliderElement || $('#slider'),
		sliderMin = options.sliderMin || 1, 
		sliderMax = options.sliderMax || 10, 
		sliderStep = options.sliderStep || 1,
		sliderVal = options.sliderVal || 5,
		onloadDefaultCss = {visibility : 'visible'},
		onloadCss = options.onloadCss ? jQuery.extend(onloadDefaultCss, options.onloadCss) : onloadDefaultCss,
		maxZIndex = options.maxZIndex || 1000,
		zIndexOffset = options.zIndexOffset || 10,	
		pageLoadCallback = options.pageLoadCallback || null,
		onLoadCallback = options.onLoadCallback	|| null;
				
	function __constructor() {
		startControl.click(start);
		stopControl.click(stop);		
		
		imgs.each(function(i) {
			elem = $(this);

			if (i === 0) {
				elem.show();
			}

			elem.css('z-index', maxZIndex - (i * zIndexOffset));
			
			if (showProgress) {
				newElem = document.createElement(progressElement);
				if (progressElementClass) {
					newElem.className = progressElementClass;
				}
			
				control.append(newElem);				
			}
		});	
		
		if (showSlider) {
			$(sliderElement).slider({
				value : sliderVal,
				min   : sliderMin,
				max   : sliderMax,	
				step  : sliderStep,
				slide: function(event, ui) {
					setFadeTimeout(ui.value);
				}
			});			
		}		
		
		if (clearFloat) {
			control.append(clearFloatHTML);
		}
		
		if (showProgress) {
			progressBlocks = control.find(progressElement);
			progressBlocks.eq(0).addClass(currentClass);				
			progressBlocks.click(selectImage);
		}
		
		if (pageLoadCallback) {
			pageLoadCallback();
		}
	};
	
    function selectImage() {
		stop();
		
		imgs.hide();

		queue = progressBlocks.index(this) === 0 ? progressBlocks.length - 1 : progressBlocks.index(this) - 1;
		progressBlocks.eq(queue).addClass(currentClass).siblings().removeClass(currentClass);
		
		start();	
	};		
	
	function getFadeTimeout() {
		return fadeTimeout;
	};	
	
	function setFadeTimeout(slideTime) {
		fadeTimeout = slideTime * 1000;
	};	
		
	function morph() {	
		if (stopped) {
			return false;
		}

		current = iterator;
		queue = (iterator === imgs.length - 1) ? 0 : iterator + 1;						

		now = imgs.eq(current);
		next = imgs.eq(queue);					

		now.stop().fadeOut(fadeSpeed, function() {
			if (showProgress) {
				progressBlocks.removeClass(currentClass);
			}
			
			next.stop().fadeIn(fadeSpeed, function() {
				next.siblings().removeAttr('style');	
				if (showProgress) {
					progressBlocks.eq(queue).addClass(currentClass);
				}
				window.clearTimeout(currentAnimation);		
				
				if (onLoadCallback) {
					onLoadCallback({
						currentItemIndex   : queue,
						imageCollection    : imgs,
						progressCollection : progressBlocks,
						getSlideShowSpeed  : getFadeTimeout,
						setSlideShowSpeed  : setFadeTimeout
					});						
				}
						
				currentAnimation = window.setTimeout(morph, getFadeTimeout());
			});				
		});	
		iterator++;
		if (iterator === imgs.length) {
			iterator = 0;
		}			
	};
	
	function stop() {
		window.clearTimeout(currentAnimation);			
		stopped = true;			
		
		imgs.eq(now).show().siblings().hide();
	};

	function start() {
		iterator = queue;
		stopped = false;
		morph();
	};
			
	// make sure the first image has been loaded by the browser before creating slide show
	var loadImage = new Image();
	loadImage.src = imgs.eq(0).find('img').attr('src');
	loadImage.onload = function() {
		control.css(onloadCss);
		if (imgs.length > 1) {
			__constructor();
			
			if (autoPlay) {
				currentAnimation = window.setTimeout(morph, getFadeTimeout());				
			}
		}						
	};	
	
	// return controls for each instance
	var self = {};
	self.stop = stop;
	self.start = start;
	self.getSlideShowSpeed = getFadeTimeout;
	self.setSlideShowSpeed = setFadeTimeout;
	
	return self;									
}