/*
 * rpgSimpleCarousel 0.1.1
 * Copyright (c) 2011 Ryan Gibson, conceptx.co.uk
 * http://www.conceptx.co.uk/jquery-carousel/
 * 
 * Redistribution and use in source and minified forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * - Redistributions of source code must retain the above copyright notice, this list 
 *   of conditions and the following disclaimer.
 * - Redistributions of minified code must retain the above copyright notice.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
 * SUCH DAMAGE.
 * 
 * 
 * 
 * ## Documentation ##
 * ### Embedding the carousel ###
 * 
 * To enable the carousel call the following javascript:
 * 
 *     $("#selector").rpgSimpleCarousel(options);
 * 
 * The following options can be passed in an object:
 * 
 * - `autoplay`
 * :    Should the carousel auto play
 * :    Type: Boolean
 * :    Default: true
 * - `pauseOnMouse`
 * :    Should the carousel pause playing on mouse over
 * :    Type: Boolean
 * :    Default: true
 * - `type`
 * :    Vertical orientation. (you don't need to set this if setting a direction)
 * :    Type: String
 * :    Options: horizontal, vertical
 * :    Default: horizontal
 * - `direction`
 * :    The direction the slides move in.
 * :    Type: String
 * :    Options: left, right, up, down
 * :    Default: left
 * - `slideDuration`
 * :    The duration a slide is visible when playing (in seconds)
 * :    Type: Number
 * :    Default: 4.5
 * - `slideContainer`
 * :    A selector for the slides container (under the embed selector above)
 * :    Type: String
 * :    Default: '.container'
 * - `slides`
 * :    A selector for the slides (under the embed selector and slideContainer selector above)
 * :    Type: String
 * :    Default: '.slide'
 * - `animationDuration`
 * :    The duration of the slide animation (in seconds)
 * :    Type: Number
 * :    Default: 0.5
 * - `animationEasing` 
 * :    The jQuery easing option
 * :    Type: String
 * :    Options: linear, swing
 * :    Default: 'swing'
 * - `width`
 * :    Explicitly set the carousel width, if not set will try and get on image load.
 * :    Type: Number
 * :    Default: null
 * - `height`
 * :    Explicitly set the carousel height, if not set will try and get on image load.
 * :    Type: Number
 * :    Default: null
 * - `willShowSlide`
 * :    A callback before changing slide
 * :    Type: Function
 * :    Default: null
 * - `didShowSlide`
 * :    A callback after changing slide
 * :    Type: Function
 * :    Default: null
 * - `click`
 * :    A callback when a user clicks on a slide
 * :    Type: Function
 * :    Default: null
 * - `mouseover`
 * :    A callback when a user mouseovers a slide
 * :    Type: Function
 * :    Default: null
 * - `mouseout`
 * :    A callback when a user mouseouts a slide
 * :    Type: Function
 * :    Default: null
 * - `init`
 * :    A callback after the carousel has initialised
 * :    Type: Function
 * :    Default: null
 * 
 * The callbacks (`willShowSlide`, `didShowSlide`, `click`, `mouseover` and `mouseout`) have the following 
 * function prototype:
 * 
 *     function(slideIndex, slideElement, state);
 * 
 * State is an object and should be treated as read only, the behaviour when changing the 
 * state in callbacks is undefined. Some attributes of interest on state are: current, the 
 * current slide; slides, and array of slides; width; and height.
 * 
 * ### Carousel methods ###
 * 
 * You can call these methods on the carousel with the following code:
 * 
 *     $("#selector").rpgSimpleCarousel(methodName[, ...]);
 * 
 * All the methods are chain-able apart from isPlaying, which returns a boolean.
 * 
 * - show
 * 
 *         $("#selector").rpgSimpleCarousel('show', slideIndex);
 * 
 *     Show the slide at `slideIndex` (the slide index is zero-based).
 * 
 * - next
 * 
 *         $("#selector").rpgSimpleCarousel('next');
 * 
 *     Show the next slide, if there is no next slide the first slide will be shown.
 * 
 * - previous
 * 
 *         $("#selector").rpgSimpleCarousel('previous');
 * 
 *     Show the previous slide, if there is no previous slide the last slide will be shown.
 * 
 * - start
 * 
 *         $("#selector").rpgSimpleCarousel('start');
 * 
 *     Start the carousel playing.
 * 
 * - stop
 * 
 *         $("#selector").rpgSimpleCarousel('stop');
 * 
 *     Stop the carousel playing.
 * 
 * - isPlaying
 * 
 *         $("#selector").rpgSimpleCarousel('isPlaying');
 * 
 *     Return true if the carousel is playing, otherwise return false.
 * 
 * - debug
 * 
 *         $("#selector").rpgSimpleCarousel('debug');
 * 
 *     Output the carousel state to `console.log`, if available.
 * 
 * Thats all folks.
 * 
 * -- Ry
 * 
 */

(function($) {
	var settings, methods, plugginName = 'rpgSimpleCarousel';
	settings = {
		'autoplay': true,
		'pauseOnMouse': true,
		'cycle': true,
		'direction': 'left',
		'type': 'horizontal',
		'slideDuration': 4.5,
		'slideContainer': '.container',
		'slides': '.slide',
		'animationDuration': 0.5,
		'animationEasing': 'swing',
		'width': null,
		'height': null,
		'willShowSlide': null,
		'didShowSlide': null,
		'click': null,
		'mouseover': null,
		'mouseout': null,
		'init': null
	};
	
	// Try and output to the console
	function dbg(msg) {
		try {
			console.log(msg);
		} catch (e) {}
	}
	
	// Store/retrieve key value data
	function d(obj, key, val) {
		try {
			var data = obj.data(plugginName);
			if (typeof key === "undefined") {
				return data;
			} else {
				if (typeof val === "undefined") {
					// get some data
					if (data && data.hasOwnProperty(key) === true) {
						return data[key];
					}
				} else {
					// Set some data
					if (!data) {
						data = {};
					}
					data[key] = val;
					obj.data(plugginName, data);
				}
			}
		} catch (e) {}
		return null;
	}
	
	// Get the index of a slide that is not a or b
	function otherSlide(obj, a, b) {
		var i, slides = d(obj, 'slides');
		for (i = 0; i < slides.length; i++) {
			if (i !== a && i !== b) {
				return i;
			}
		}
		return 0;
	}
	
	// Get the index of the next slide (force will force cycling)
	function nextSlide(obj, value, force) {
		var slide, data = d(obj);
		if (typeof force === "undefined") {
			force = false;
		}
		if (typeof value === "undefined") {
			value = data.current;
		}
		slide = value + 1;
		if (slide >= data.slides.length) {
			if (data.settings.cycle === true || force === true) {
				slide = 0;
			} else {
				slide = value;
			}
		} 
		return slide;
	}
	
	// Get the index of the previous
	function previousSlide(obj, value, force) {
		var slide, data = d(obj);
		if (typeof force === "undefined") {
			force = false;
		}
		if (typeof value === "undefined") {
			value = data.current;
		}
		slide = value - 1;
		if (slide < 0) {
			if (data.settings.cycle === true || force === true) {
				slide = data.slides.length - 1;
			} else {
				slide = value;
			}
		}
		return slide;
	}
	
	// Show slide at index, moving in direction (0 = left else right)
	function showSlide(obj, index, dir) {
		var options = {}, sc, pos, data = d(obj);
		if (index === data.current || data.working === true) {
			// Nothing to do
			return;
		}
		
		if (typeof dir === "undefined") {
			dir = data.settings.direction;
		}
		
		d(obj, 'working', true);
		callHandler(obj, 'willShowSlide', index, data.slides[index]);
		
		pos = 0;
		if (dir === 1) {
			if (data.settings.type === "horizontal") {
				pos = data.width * 2;
			} else {
				pos = data.height * 2;
			}
		}
		
		if (dir === 0) {
			if (index !== previousSlide(obj)) {
				// Update slides
				sc = $(data.settings['slideContainer'], obj).empty();
				sc.append(data.slides[index]).append(data.slides[data.current]).append(data.slides[otherSlide(obj, index, data.current)]);
			}
		} else {
			if (index !== nextSlide(obj)) {
				// Update slides
				sc = $(data.settings['slideContainer'], obj).empty();
				sc.append(data.slides[otherSlide(obj, index, data.current)]).append(data.slides[data.current]).append(data.slides[index]);
			}
		}
		
		sc = $(data.settings['slideContainer'], obj);
		if (data.settings.type === "horizontal") {
			options.right = pos;
		} else {
			options.bottom = pos;
		}
		sc.animate(options, 
			{
				'duration': data.settings['animationDuration'] * 1000,
				'easing': data.settings['animationEasing'],
				'queue': false,
				'complete': function () {
					d(obj, 'current', index);
					setupForCurrentSlide(obj);
					d(obj, 'working', false);
					var data = d(obj);
					callHandler(obj, 'didShowSlide', data.current, data.slides[data.current]);
					if (data.playing === true) {
						clearTimeout(data.playingTimout);
						d(obj, 'playingTimout', setTimeout(function () {
							showSlide(obj, nextSlide(obj));
						}, data.settings.slideDuration * 1000));
					}
				}
			}
		);
	}
	
	// Empty the slide container and fill it with thre slides, current - 1, current and current + 1. Centre on current.
	function setupForCurrentSlide(obj) {
		setupForSlideAtIndex(obj, d(obj, 'current'));
	}
	
	// Empty the slide container and fill it with thre slides, index - 1, index and index + 1. Centre on index.
	function setupForSlideAtIndex(obj, index) {
		var next, prev, sc, data = d(obj);
		
		next = nextSlide(obj, index, true);
		prev = previousSlide(obj, index, true);
		
		sc = $(data.settings['slideContainer'], obj).empty();
		sc.append(data.slides[prev]).append(data.slides[index]).append(data.slides[next]);
		if (data.settings.type === "horizontal") {
			sc.css('right', data.width + 'px');
		} else {
			sc.css('bottom', data.height + 'px');
		}
	}
	
	// Call the given callback handler
	function callHandler(obj, handler) {
		var args, data = d(obj);
		if (data.settings.hasOwnProperty(handler) && typeof data.settings[handler] === "function") {
			args = Array.prototype.slice.call(arguments, 2);
			args.push(data);
			return data.settings[handler].apply(this, args);
		}
		return null;
	}
	
	// Star or stop playing the carousel
	function setPlaying(obj, value) {
		var data = d(obj);
		if (data.playing === value) {
			// Nothing to do
			return;
		}
		if (value === true) {
			d(obj, 'playingTimout', setTimeout(function () {
				showSlide(obj, nextSlide(obj));
			}, data.settings.slideDuration * 1000));
		} else {
			clearTimeout(data.playingTimout);
			d(obj, 'playingTimout', null);
			
		}
		d(obj, 'playing', value);
	}
	
	function setupSize(obj) {
		var data = d(obj), width, height, slidesSel = d(obj, 'settings')['slideContainer'] + ' ' + d(obj, 'settings')['slides'];
		width = d(obj, 'width');
		height = d(obj, 'height');
		obj.css('width', width + 'px');
		obj.css('height', height + 'px');
		obj.css('overflow', 'hidden');
		if (data.settings.type === "horizontal") {
			$(data.settings['slideContainer'], obj).css('width', (width * 3) + 'px');
			$(data.settings['slideContainer'], obj).css('height', height + 'px');
			$(slidesSel, obj).css('float', 'left');
		} else {
			$(data.settings['slideContainer'], obj).css('width', width + 'px');
			$(data.settings['slideContainer'], obj).css('height', (height * 3) + 'px');
		}
		$(slidesSel, obj).css('width',  width + 'px');
		$(slidesSel, obj).css('height',  height + 'px');
		$(data.settings['slideContainer'], obj).css('position', 'relative');
		setupForCurrentSlide(obj);
	}
	
	// Pluggin methods, call as $(sel).rpgSimpleCarousel(methodname[, ...]);
	methods = {
		init: function(options) {
			
			return this.each(function(){
				var data, slidesSel, slidesImgSel, slideElements, imageElements, s, $this = $(this);
				d($this, 'current', 0);
				d($this, 'working', false);
				d($this, 'playing', false);
				d($this, 'wasPlaying', false);
				d($this, 'playingTimout', null);
				
				s = {};
				$.extend(s, settings);
				if (options) {
					$.extend(s, options);
				}
				switch (s.direction) {
					case 'up':
						s.direction = 1;
						s.type = 'vertical';
						break;
					case 'down':
						s.direction = 0;
						s.type = 'vertical';
						break;
					case 'right':
						s.direction = 0;
						s.type = 'horizontal';
						break;
					case 'left':
						s.direction = 1;
						s.type = 'horizontal';
						break;
				}
				d($this, 'settings', s);
				
				slidesSel = d($this, 'settings')['slideContainer'] + ' ' + d($this, 'settings')['slides'];
				slidesImgSel = slidesSel + ' img';
				slideElements = $(slidesSel, $this);
				imageElements = $(slidesImgSel, $this);
				d($this, 'slides', slideElements);
				
				d($this, 'width', s.width);
				d($this, 'height', s.height);
				if (s.width === null || s.height === null) {
					$(imageElements[0]).load(function() {
						var data = d($this), $image = $(this);
						if (d($this, 'width') === null) {
							d($this, 'width', $image.width());
						}
						if (d($this, 'height') === null) {
							d($this, 'height', $image.height());
						}
						setupSize($this);
					});
				} else {
					setupSize($this);
				}
				
				data = d($this);
				if (data.settings.click !== null) {
					$(this).bind('click.' + plugginName, function (e) {
						var data = d($this);
						return callHandler($this, 'click', data.current, data.slides[data.current]);
					});
				}
				
				// Pause playing on mouse over
				$(this).bind('mouseover.' + plugginName, function (e) {
					var data = d($this);
					if (d($this, 'settings').pauseOnMouse === true && data.playing === true) {
						d($this, 'wasPlaying', true);
						setPlaying($this, false);
					}
					callHandler($this, 'mouseover', data.current, data.slides[data.current]);
				});

				// Resume playing on mouse over
				$(this).bind('mouseout.' + plugginName, function (e) {
					var data = d($this);
					if (d($this, 'settings').pauseOnMouse === true && data.wasPlaying === true) {
						d($this, 'wasPlaying', false);
						setPlaying($this, true);
					}
					callHandler($this, 'mouseout', data.current, data.slides[data.current]);
				});
				
				if (d($this, 'settings').autoplay === true) {
					setPlaying($this, true);
				}
				
				
				callHandler($this, 'init', data.current, data.slides[data.current]);
			});
		},
		
		debug: function() {
			return this.each(function() {
				dbg(d($this));
			});
		},
		
		show: function(index) {
			return this.each(function() {
				var data, $this = $(this);
				data = d($this);
				if (index >= 0 && index < data.slides.length) {
					showSlide($(this), index);
				}
			});
		},
		
		next: function() {
			return this.each(function() {
				var $this = $(this);
				showSlide($this, nextSlide($(this)), d($this, 'settings').direction === 1 ? 1 : 0);
			});
		},
		
		previous: function(index) {
			return this.each(function() {
				var $this = $(this);
				showSlide($this, previousSlide($(this)), d($this, 'settings').direction === 1 ? 0 : 1);
			});
		},
		
		start: function() {
			return this.each(function() {
				setPlaying($(this), true);
			});
		},
		
		stop: function() {
			return this.each(function() {
				 setPlaying($(this), false);
			});
		},
		
		isPlaying: function() {
			return d($(this), 'playing');
		}
	};
	
	// Register the plugin
	$.fn[plugginName] = function(method) {
		if (methods[method]) {
			return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
		} else if ( typeof method === 'object' || !method ) {
			return methods.init.apply(this, arguments);
		} else {
			$.error('Method ' +  method + ' does not exist on jQuery.' + plugginName);
		}
	};
})(jQuery);

