(function($){	

	// Parse options - used by several functions
	function processOptions (caller,options) {
		var defaults = {
			slideClip: 1,
			slideCenter: 1,
			slideAspect: 1,
			slideResize: 1,
			slideStack: 1,
			marginVertical: 0,
			marginHorizontal: 0,
			resizeTo: $(document),
			showTime: 5000,
			showClass: '',
			showFirst: '.first',
			showStart: 1,
			onChange: function(){},
			onPlay: function(){},
			onPause: function(){},
			onLoad:	function(){},
			startDelay: 1000,
			maxRedraws: 3,
			recurseLevel: 1,
			transTime: 3000
		};
		var options = $.extend({}, defaults, options);
		// Determine what we're working on...
		if (typeof(options.resizeTo) == 'string') options.resizeTo = $(options.resizeTo);
		if (typeof(options.container) == 'string') options.container = $(options.container);
		if (typeof(options.element) == 'string') options.element = $(options.element);
		if (!options.element) {
			options.element = caller;
		}
		if (!options.element || !options.element.length || !options.element[0].style) {
			console.log("Element passed: ",options.element, "Resulting object: ",options.element);
			throw ("elementPCRS error: not sure what element you want me to work on? Most likely cause is that named element does not exist on page. Check console for element debug, check documentation for usage.");
		} 
		options.showClass = options.showClass ? '.'+options.showClass : '';
		return(options);
	}

	// Unbind from window resize
	$.fn.unbindResize = function() {
		$(window).unbind("resize.elementResizer."+$(this).data('slideNo'));
//console.log("unbind",$(this).data('slideNo'));
	}

	// Bind to window resize
	$.fn.bindResize = function(options) {
		var objectOptions = this.data('options');
		if (objectOptions) {
			var options = objectOptions;
		} else {
			var options = processOptions(this,options);
			options.element.data('options',options);
		}
		// Double call - clear scrollbars
		options.element.slideCropPositionResize();
		var objectToBind = options.element;
		if (options.slideClip||options.slideCenter||options.slideResize||options.marginVertical||options.marginHorizontal) {
			$(window).bind("resize.elementResizer."+options.element.data('slideNo'), function() { objectToBind.slideCropPositionResize(); });
//console.log("bind",$(this).data('slideNo'));
		}
		return(this);
	}

	// Set up a slide show
	$.fn.elementSlideshow = function(options) {
		var options = processOptions(this,options);
		options.element.data('options',options);
		// Need the whole document to make this work
		$(window).ready(function() { initShow(options.element,options); });
		options.onLoad.call(options.element);
		return(options.element);
	}

	// Initialise slide show - called as a function bound to window.ready so
	// that everything can be measured properly
	function initShow(caller,options) {
		options.zindex = options.element.css('z-index');
		if (options.zindex == 'auto') options.zindex = 0;

		// Create an array of the slides rather than searching the DOM each time
		slides = options.element.children(options.showClass);
		if (slides.length == 0) {
			slides = options.element.siblings(options.showClass);
			options.element = options.element.parent();
		}
		if (slides.length < 2) {
			// No slides or only 1 slide found = turn off the show!
			throw ("elementPCRS error: elementSlideShow: no slides found or only one slide found - are you sure you want to do this?");
		}
		// Find the first slide; select first in array if no slide specified
		first = options.element.children(options.showFirst+':first');
		if (!first.length) first = $(slides[0]);
		first.addClass('current');
		// Hide all slides except the current (first) one and get each slide's dimensions, set CSS position so that slides are stacked
		var slideCount = 0;
		$(slides).each(function() { 
			$(this).data('slideNo',slideCount++);
			$(this).data('startHeight',options.height>0?options.height:$(this).height());
			$(this).data('startWidth',options.width>0?options.width:$(this).width());
			var slideOffset = $(this).position();
			if ($(this).offsetParent() != $(this).parent()) {slideOffset.left = 0; slideOffset.top = 0}
			if (options.slideStack) {
				$(this).css({'position': 'absolute', 'margin-top': slideOffset.top, 'margin-left': slideOffset.left});
			}
			if (!$(this).hasClass('current')) $(this).hide() 
			$(this).data('options',$.extend({},options,{element: $(this), slideOffset: slideOffset}));
		});
		// Slideshow position
		// Autostart show
		if (options.showStart) options.timeout = setTimeout(function() {caller.playShow()},options.transTime + options.startDelay);
		// Bind resizer to first slide
		first.bindResize();
		return caller;
	}

	// Return 1-index number of slides in the show
	$.fn.slideCount = function() {
		var options = this.data('options');
		return(options.element.children().length);
	}

	// Return 1-index slide number
	$.fn.slideNumber = function() {
		var options = this.data('options');
		var current = options.element.children('.current');
		if (current.length == 0) return(0);
		var index = options.element.children().index(current);
		return(index+1);
	}

	// Transition from one slide to another - not necessarily adjacent
	$.fn.transition = function(current,next,transTime) {
		var options = this.data('options');
		next.hide();
		next.addClass('current');
		current.removeClass('current');
		next.bindResize();
		current.css({'z-index':options.zindex-1});
		next.css({'z-index':options.zindex});
		//next.show();
		// Fade in new slide, hide old after fade. unBind resize after transition complete
		next.fadeIn(transTime||options.transTime,function() {
		//next.animate({opacity: 1},transTime||options.transTime,function() {
			if (!current.hasClass('current')) current.hide(0,function() {
				current.unbindResize()
			});
		});
		options.onChange.call(this);
	}

	// next slide
	$.fn.nextSlide = function(transTime) {
		var options = this.data('options');
		var current = options.element.children('.current');
		if ( current.length == 0 ) current = options.elemenet.children(options.showClass+':last');
		var next =	current.next().length ? current.next() : options.element.children(options.showClass+':first');
		$(this).transition(current,next,transTime);
	}
		
	// previous slide
	$.fn.prevSlide = function(transTime) {
		var options = this.data('options');
		var current = options.element.children('.current');
		if ( current.length == 0 ) current = options.elemenet.children(options.showClass+':first');
		var prev =	current.prev().length ? current.prev() : options.element.children(options.showClass+':last');
		$(this).transition(current,prev,transTime);
	}

	// Skip to 1-index slide in selection
	$.fn.skipTo = function(slide,transTime) {
		var options = this.data('options');
		if (slide-1 > options.element.children().length) {
			console.log('Warning: elementPCRS: skipTo: invalid slide number '+slide+' specified when only '+this.slideCount()+' slides exist');
			return;
		}
		var current = options.element.children('.current');
		var skip = $(options.element.children().get(slide-1));
		$(this).transition(current,skip,transTime);
	}

	// start autoplay
	$.fn.playShow = function() {
		var options = this.data('options');
		options.timeout = 0;
		if (!options.interval) {
			options.element.nextSlide();
			options.interval = setInterval( function() {options.element.nextSlide()}, options.transTime + options.showTime );
			options.onPlay.call(this);
		}
	}

	// stop autoplay
	$.fn.pauseShow = function() {
		var options = this.data('options');
		if (options.interval) {
			clearInterval(options.interval);
			options.interval = 0;
			options.onPause.call(this);
		}
		if(options.timeout) {
			clearTimeout(options.timeout);
			options.timeout = 0;
			options.onPause.call(this);
		}
	}

	// slideResize current slide 
	$.fn.resizeCurrentSlide = function() {
		var options = this.data('options');
		var current = options.element.children('.current');
		current.slideCropPositionResize();
	}

	// Apply crop / position / slideResize to any element - ref: x1
	$.fn.slideCropPositionResize = function(options) {
//console.log("resize",$(this).data('slideNo'));
		var objectOptions = this.data('options');
		if (objectOptions) {
			var options = objectOptions;
		} else {
			var options = processOptions(this,options);
		}
		var startHeight = 0;
		var startWidth = 0;
		var slideCenterHeight = 0;
		var slideCenterWidth = 0;
		if (!options.slideOffset) {
			options.slideOffset = options.element.position();
		}
		if (options.slideAspect) {
			startHeight = this.data('startHeight') || options.height || this.height();
			startWidth = this.data('startWidth') || options.width || this.width();
		};
		// Set clipping window
		// Need to slideResize the clipping window before taking measurements of the element to slideResize to,
		// as it's possible the clipping window can affect the size of the element to be measured and the 
		// slideResize may affect scrollbars, especially when used for full window. Can't cache resizeTo anywhere
		// as this will change as other things are slideResized, hence needs to be measured each time, here and later.
		if (options.slideClip) {
			if (!options.container) options.container = this.parent();
			options.container.height(options.resizeTo.height() - options.marginVertical);
			options.container.width(options.resizeTo.width() - options.marginHorizontal);
		}

		// Measure container to resize to
		var measuredHeight = options.resizeTo.height();
		var measuredWidth = options.resizeTo.width();

		// If we're centering the slide, start center dimensions with measured width/height
		var newHeight = measuredHeight;
		var newWidth = measuredWidth;

		// Apply margins if required
		newHeight = newHeight - options.marginVertical;//; - options.showOffset.top;// + options.slideOffset.top;
		newWidth = newWidth - options.marginHorizontal;// - options.showOffset.left;// + options.slideOffset.left;

		// Apply slideAspect ratio if required
		if (options.slideAspect && options.slideResize) {
			var ratio = startHeight / startWidth;
			if ((newHeight/newWidth) > ratio){
				newWidth = newHeight / ratio;
			} else {
				newHeight = newWidth * ratio;
			}
		}

		// Resize the slide
		if (options.slideResize) {
			this.height(newHeight);
			this.width(newWidth);
		}

		// If the target to be measured has resized since the last measure, recurse - this clears
		// scrollbars that appear or disappear....
		if (measuredHeight != options.resizeTo.height() || measuredWidth != options.resizeTo.width()) {
			options.recurseLevel = options.recurseLevel + 1;
			if (options.recurseLevel > options.maxRedraws) {
				console.log('elementPCRS.slideCropPositionResize warning: hit maximum recursion of '+options.maxRedraws+'. Stopping');
			} else {
				$(this).slideCropPositionResize();
			}
			options.recurseLevel = options.recurseLevel - 1;
		}

		// Center the slide 
		if (options.slideCenter) {
			this.css('margin-top',((measuredHeight - newHeight - options.marginVertical) / 2));// + options.slideOffset.top);
			this.css('margin-left',((measuredWidth - newWidth - options.marginHorizontal) / 2));// + options.slideOffset.left);
		}
	};
})(jQuery);

