/*
** Slideygal - Slideshow Gallery jQuery plugin
** Based on innerfade
*/

(function($) {
	/*
	** Default options
	*/
	var defaultControl = {
		// Width and height default to the size of the main element (container)
		width:	0,
		height:	0,
		
		// (selector) the child elements of the container; each will be cycled
		children:	null,
		
		// (selector) if specified, all selectors (other than children) will search within this
		// Note: This will be evaluated after the selector is forced to 
		// Example: $(next) would become $(root).find(next)
		root:	undefined,
		
		// Time to display: number of milliseconds to display each slide for
		displayTime:	3000,
		
		// Transition type: one of 'fade', 'slide'
		transitionType:	'fade',
		
		// Transition speed: a string representing one of the three predefined speeds ("slow", "normal", or "fast") or the number of milliseconds to run the animation (e.g. 1000)
		transitionSpeed:	'normal',
		
		// CSS class name for an enabled slideshow container
		containerClass:	undefined,
		
		// (selector) if specified, this will be the caption container
		// Note: This could be an empty space for a caption elements to be added, or a filled list of caption elements; see captionChild
		// Note: If caption is a UL or OL, captions will be added as LI, otherwise as DIV
		caption:	undefined,
		
		// (selector) if specified, this will be the child elements for the caption, index-linked to the main container
		// If not specified, a UL will be appended to the caption container
		captionChild:	undefined,
		
		// (selector) if specified, this will be called to detect the caption content, with captionCaptureFn(thisSlideshowElement, index)
		captionFn: function (el, i) {
			return $(el).find('img').attr('title');
		},
		
		// CSS class name for an enabled caption container
		captionClass: undefined,
		
		// (selector) control elements
		next:	'',
		previous:	'',
		play:	'',
		pause:	'',
		
		// Event callbacks; will be passed (thisControl, fromId, toId) at the start of a transition
		transitionBefore: undefined,
		transitionAfter: undefined,
		
		// Event callbacks; will be passed (thisControl, currentId) when the event occurs
		playFn: '',
		pauseFn: '',
		
		overlayTop:	0,
		overlayBottom:	0,
		overlayLeft:	0,
		overlayRight:	0,
		overlayCenter:	0
	};
	
	var Control = function (container, options) {
		options = this.options = $.extend({}, defaultControl, options);
		
		// Re-scope this for closures
		var thisControl = this;
		
		// Look up the container
		$container = this.$container = $(container);
		
		// Look up the search root
		if (options.root) {
			this.$root = $(options.root);
		}
		
		// Look up the container child elements
		var $element_set = this.$container.children(options.children);
		this.length = $element_set.length;
		if (this.length == 0) {
			return;
		}
		
		// Register control keys
		var controls = ['next','previous','play','pause'];
		for (var i=0; i<controls.length; i++) {
			(function () {
				var control = controls[i];
				if (options[control]) {
					thisControl.find(options[control]).click( function (e) {
						e.preventDefault();
						thisControl[control]();
					});
				}
			})();
		}
		
		
		// Transition store
		this.transition = {};
		
		// Look for captions
		if (options.caption) {
			this.$caption = this.find(options.caption);
			var captionChild = options.captionChild;
			if (!captionChild) {
				// Caption child not specified
				var conType = this.$caption.get(0).tagName;
				captionChild = (conType == 'UL' || conType == 'OL') ? 'li' : 'div';
				captionEl = '<'+captionChild+'></'+captionChild+'>';
				
				// Read captions and build them into the caption element
				var captionFn = options.captionFn;
				for (var i=0; i<this.length; i++) {
					this.$caption.append(
						$(captionEl).append(
							captionFn($element_set.get(i), i)
						)
					);
				};
			}
			// Build caption transition
			this.transition.caption = new Transition({
				$container:	this.$caption,
				$element_set:	this.$caption.children(captionChild),
				type:		options.transitionType,
				speed:		options.transitionSpeed
			});
		}
		
		// Create main slideshow transition
		this.transition.main = new Transition({
			$container:	$container,
			$element_set:	$element_set,
			type:		options.transitionType,
			speed:		options.transitionSpeed,
			containerClass:	options.captionClass,
            width:      options.width,
            height:     options.height
		}, function () {
			thisControl.startCountdown();
		});
		
		// Show the first element
		this.currentId = 0;
		this.changing = false;
		this.playing = false;
	};
	
	$.extend(Control.prototype, {
        add: function ($el) {
            this.$container.append($el);
            this.transition.main.add($el);
            this.length++;
            return this;
        },
        end: function () {
            // jQuery.end() to return scope to container
            return this.$container;
        },
		find: function (selector) {
			if (!this.$root) {
				return $(selector);
			}
			return this.$root.find(selector);
		},
        
        isEnabled: function () {
            // Is enabled if there is something to slide between
            if (this.length > 1)  {
                return true;
            }
            return false;
        },
		
		/*
		** Control functions
		*/
		previous: function () {
            // Check to see if it's enabled
            if (!this.isEnabled()) {
                return;
            }
            
			// Cancel if we're already mid-transition
			if (this.changing) {
				return;
			}
			
			// Stop the countdown, in case this was triggered by an external event press
			this.stopCountdown();
			
			// Move to previous id and show
			var nextId = this.currentId - 1;
			if (nextId < 0) {
				nextId = this.length + nextId;
			}
			this.show(nextId);
		},
		next: function () {
            // Check to see if it's enabled
            if (!this.isEnabled()) {
                return;
            }
            
			// Cancel if mid-transition
			if (this.changing) {
				return;
			}
			
			// Stop countdown
			this.stopCountdown();
			
			// Next ID
			var nextId = (this.currentId + 1) % (this.length);
			this.show(nextId);
		},
		pause: function () {
			this.playing = false;
			this.stopCountdown()
			
			// Trigger callback
			if (this.options.pauseFn) {
				this.options.pauseFn(this, this.currentId);
			}
		},
		play: function () {
			this.playing = true;
			this.startCountdown()
			
			// Trigger callback
			if (this.options.playFn) {
				this.options.playFn(this, this.currentId);
			}
		},
		startCountdown: function () {
			// Clear any existing countdowns
			this.stopCountdown();
			
			if (!this.playing) {
				return;
			}
			
			// Create timeout
			var thisControl = this;
			this.timeout = setTimeout(function () {
                if (thisControl.isEnabled()) {
                    thisControl.next();
                } else {
                    thisControl.startCountdown();
                }
			}, this.options.displayTime);
		},
		stopCountdown: function () {
			if (this.timeout) {
				clearTimeout(this.timeout);
			}
		},
		
		show: function (nextId) {
			// About to transition, trigger
			if (this.options.transitionBefore) {
				this.options.transitionBefore(this, this.currentId, nextId);
			}
			
			// Tell all transitions to move to nextId
			var thisControl = this;
			this.changing = true;
			if (this.transition.caption) {
				this.transition.caption.show(nextId);
			}
			this.transition.main.show(nextId, function() {
				// Done transition, trigger
				if (thisControl.options.transitionAfter) {
					thisControl.options.transitionAfter(thisControl, thisControl.currentId, nextId);
				}
				
				// Complete change
				thisControl.currentId = nextId;
				thisControl.changing = false;
				thisControl.startCountdown();
			});
		}
	});
	
	
	/*
	** Transitions
	*/
	var defaultTransition = {
		$container:	undefined,
		$element_set:	undefined,
		type:	defaultControl.transitionType,
		speed:	defaultControl.transitionSpeed,
		width:	0,
		height:	0,
		containerClass:	undefined
	};
	
	var Transition = function (options, callbackFn) {
		options = $.extend({}, defaultTransition, options);
		this.$container = options.$container;
		this.type = options.type;
		this.speed = options.speed;
        
		var $element_set = options.$element_set;
		
		// Set up CSS on container
		$container.css('position', 'relative');
		if (options.height) {
			$container.css('height', options.height);
		}
		if (options.width) {
			$container.css('width', options.width);
		}
		if (options.containerClass) {
			$container.addClass(options.containerClass);
		}
		
		// Set CSS on the child elements
		this.zIndexNext = 1;
		var thisTransition = this;
		this.$elements = [];
		$element_set.each( function () {
			var $el = $(this);
            thisTransition.add($el)
		});
		
		// Start off on the first slide
		this.currentId = 0;
		this.$elements[this.currentId].show();
		if (callbackFn) {
			callbackFn();
		}
	}
	
	
	$.extend(Transition.prototype, {
        add: function ($el) {
			this.$elements.push($el);
			$el
				.css('z-index', this.zIndexNext++)
				.css('position', 'absolute')
				.hide()
			;
            return this;
        },
		jump: function (nextId, callbackFn) {
			this._show(nextId, callbackFn, 0);
		},
		show: function (nextId, callbackFn) {
			this._show(nextId, callbackFn, this.speed);
		},
		_show: function (nextId, callbackFn, speed) {
			// Abort any existing transitions
			this.abort();
			
			// Prepare all variables
			var from =	this.from =	this.$elements[this.currentId];
			var to =	this.to =	this.$elements[nextId];
			var type = this.type;
			
			// Prepare inner callback
			var thisTransition = this;
			var completeFn = function () {
				// Don't callback if transition is no longer active
				if (!thisTransition.active) {
					return;
				}
				thisTransition.active = false;
                
                // Make sure the hidden is hidden and the shown is shown
                from.hide();
                to.show();
				
				// Advance the currentId
				thisTransition.currentId = nextId;
				
				// Call the original callback
				if (callbackFn) {
					callbackFn();
				}
			};
			
			// Mark as active
			this.active = true;
			
			// Perform the transition
			if (type == 'fade') {
				from.fadeOut(speed);
				to.fadeIn(speed, completeFn);
			} else if (type == 'slide') {
				from.slideUp(speed);
				to.slideDown(speed, completeFn);
			} else {
				from.hide();
				to.show();
				completeFn();
			}
		},
		abort: function (toEnd) {
			// Only abort if active
			if (!this.active) {
				return;
			}
			
			// No longer active; this will disable animation callbacks
			this.active = false;
			
			// Stop both
			this.to.stop(true, true);
			this.from.stop(true, true);
			
			// Reset to show the original
			this.to.hide();
			this.from.show();
		}
	});
	
	
	/*
	** Control factories
	*/
	function elementFactory(el, options) {
		// Only initialise once per container
		if (el.$Slideygal) {
			return el.$Slideygal;
		}
		
		// Initialise new object and return
		return el.$Slideygal = new Control(el, options);
	}
	function jQueryFactory($el, options) {
		var built = [];
		$el.each( function () {
			built.push( elementFactory(this, options) );
		});
		return built;
	}
	
	
	/*
	** Hook into jQuery
	*/
	$.fn.slideygal = function (options) {
		// Build and play
		var built = jQueryFactory(this, options);
		$.each(built, function () {
			this.play();
		});
        
        // Return first built
        return built[0];
	};
	$.slideygal = function (el, options) {
		// Build and return
		if (el instanceof $) {
			return jQueryFactory(el, options);
		}
		return elementFactory(el, options);
	};
})(jQuery);

