/*! ckSlider - v0.2.0 - 2013-06-21
* https://github.com/ckimrie/ckslider
* Copyright (c) 2013 Christopher Imrie; Licensed MIT */
(function () {
	var defaults = {
			'fadeInDuration' : 800,
			'slideDuration' : 800,
			'delay' : 5000,
			'start' : 1,
			'transition' : 'fade',
			'autoplay' : true,
			'interactionDisablesAutoplay': true,
			'preloadImages' : true,
			'slideInactiveOpacity': 0.5,
			'inactiveZIndex' : 1,
			'zIndexLayer1' : 5,
			'zIndexLayer2' : 10,
			'zIndexLayer3' : 15,
			'pauseOnClick' : true,
			'hideInactiveSlides' : true,
			'legacyIEMode' : jQuery('html').is('.ie6, .ie7, .ie8'),

			'height' : null,
			'width' : null,

			'loadingClass' : 'loading',
			'slideActiveClass' : 'active',
			'slideClass' : 'slide',
			'indicatorActiveClass' : 'active',

			'slideIndicatorWrapper' : '.counter',
			'slideIndicatorElement' : 'a',

			'nextBtn' : '.next',
			'prevBtn' : '.previous',

			'direction' : 'forward',
			'onStart' : function () {
			},
			'onBeforeTransition' : function () {
			},
			'onAfterTransition' : function () {
			},
			'onPause' : function () {
			}
		},
		constants = {
			DIRECTION_FORWARD : 'forward',
			DIRECTION_REVERSE : 'reverse'
		};


	/**
	 * Constructor
	 *
	 * @param {Object} options
	 */
	var CKSlider = function (node, options) {

		this.options = jQuery.extend({}, defaults, options || {});

		this.$container = jQuery(node).eq(0);
		this.$slides = this.$container.find("." + this.options.slideClass);
		this.$indicatorContainer = this.$container.find(this.options.slideIndicatorWrapper);
		this.$indicators = this.$indicatorContainer.find(this.options.slideIndicatorElement);
		this.$nextBtn = this.$container.find(this.options.nextBtn);
		this.$prevBtn = this.$container.find(this.options.prevBtn);

		this.slideCount = this.$slides.length;
		this.current = this.options.start > 0 && this.options.start < this.$slides.length ? this.options.start - 1 : 0;
		this.nextKey = null; //Set automatically whenever an interaction or autplay occurs
		this.autoplayTimeout = null;
		this.direction = this.options.direction;
		this.animationLock = false;

		this.init();
	};

	/**
	 * Methods
	 * @type {Object}
	 */
	CKSlider.prototype = {

		/**
		 * Start the slider lifecycle
		 */
		init : function () {
			this.measureContainerDimensions();
			this.setInitialSlidePresentation();
			this.bindEvents();

			//Trigger public event
			this.trigger('ms.start');
		},


		/**
		 * Set initial slide presentation
		 */
		setInitialSlidePresentation : function () {
			var self = this;

			this.$container.addClass(this.options.loadingClass)
				.css({
					height : this.height,
					position : 'relative'
				});


			this.$slides.each(function (i) {
				if (i === self.current || !self.options.hideInactiveSlides) {
					jQuery(this).show();
				} else {
					jQuery(this).hide();
				}

				jQuery(this).css({
					position : 'absolute',
					height : self.height,
					width : '100%'
				});
			});

			this.$indicatorContainer.css({
				'zIndex' : this.options.zIndexLayer3
			});
			
			if(this.options.transition === "slide") {
				var $prevSlide = this.getPrevSlide(),
					$nextSlide = this.getNextSlide();
	
				//Prep layering and position
				$prevSlide.css({
					'zIndex' : this.options.zIndexLayer1,
					'left' : -1 * this.width
				}).fadeTo(this.options.slideDuration, this.options.slideInactiveOpacity);
	
				$nextSlide.hide().css({
					'zIndex' : this.options.zIndexLayer2,
					'left' : this.width
				}).fadeTo(this.options.slideDuration, this.options.slideInactiveOpacity);
			}

			this.highlightIndicator(this.getCurrentKey());
		},


		/**
		 * Measure container dimensions
		 */
		measureContainerDimensions : function () {
			if (this.options.width !== null) {
				this.width = this.options.width;
			} else {
				this.width = this.$container.width();
			}
			if (this.options.height !== null) {
				this.height = this.options.height;
			} else {
				this.height = this.$container.height();
			}
		},


		/**
		 * Get current key
		 * @returns {int}
		 */
		getCurrentKey : function () {
			return this.current;
		},

		/**
		 * Set current key
		 * @param key
		 */
		setCurrentKey : function (key) {
			this.current = key;
		},


		/**
		 * Get current slide
		 * @returns {jQuery}
		 */
		getCurrentSlide : function () {
			return this.getSlideAtKey(this.getCurrentKey());
		},


		/**
		 * Get next key (given current direction)
		 * @returns {int}
		 */
		getNextKey : function () {
			var key;

			if (this.direction === constants.DIRECTION_REVERSE) {
				key = this.current - 1;
				if (key < 0) {
					key = this.slideCount - 1;
				}
			} else {
				key = this.current + 1;
				if (key >= this.slideCount) {
					key = 0;
				}
			}
			return key;
		},


		getNthRelativeKey : function (n) {
			var key, diff;

			if (this.direction === constants.DIRECTION_REVERSE) {
				n *= -1;
//				key -= 1;
			}

			key = this.current + n;
			if (key < 0) {
				diff = key + this.slideCount;
				key = diff;
			}
			if (key >= this.slideCount) {
				diff = key - this.slideCount;
				key = diff;
			}
			return key;
		},

		/**
		 * Get prev key (given current direction)
		 * @returns {int}
		 */
		getPrevKey : function () {
			var key;

			if (this.direction === constants.DIRECTION_REVERSE) {
				key = this.current + 1;
				if (key >= this.slideCount) {
					key = 0;
				}
			} else {
				key = this.current - 1;
				if (key < 0) {
					key = this.slideCount - 1;
				}
			}
			return key;
		},

		/**
		 * Get next slide relative to current
		 * @returns {jQuery}
		 */
		getNextSlide : function () {
			return this.getSlideAtKey(this.getNextKey());
		},

		/**
		 * Get prev slide relative to current
		 * @returns {jQuery}
		 */
		getPrevSlide : function () {
			return this.getSlideAtKey(this.getPrevKey());
		},


		/**
		 * Get slide at key
		 *
		 * @param {int} key
		 * @returns {jQuery}
		 */
		getSlideAtKey : function (key) {
			return this.$slides.eq(key);
		},


		/**
		 * Setup Autoplay
		 */
		autoplay : function () {
			var self = this;

			clearTimeout(this.autoplayTimeout);

			this.autoplayTimeout = setTimeout(function () {
				self.trigger('ms.next');
			}, this.options.delay);
		},

		/**
		 * Ends autoslide
		 */
		stopAutoplay : function () {
			clearTimeout(this.autoplayTimeout);
			this.options.autoplay = false;
		},

		/**
		 * On Slider lifecycle start
		 */
		onStart : function () {

			if (this.options.preloadImages) {
				this.preloadImages(this.$slides.find('img'));
			}

			this.$container.removeClass("loading");

			//Trigger public method
			this.options.onStart.apply(this);

			if (this.options.autoplay) {
				this.autoplay();
			}
		},


		/**
		 * On the next event
		 */
		onNext : function () {
			this.transitionToSlide(this.getNextKey());
		},

		/**
		 * On previous event
		 */
		onPrev : function () {
			this.transitionToSlide(this.getPrevKey());
		},


		/**
		 * On Change to slide
		 */
		transitionToSlide : function (key) {
			var self = this,
				currentKey, nextKey, currentSlide, nextSlide, def;

			//Same as current key? Bail...
			if (key === this.getCurrentKey() || this.animationLock) {
				return;
			}

			this.animationLock = true;

			//Figure out direction
			this.direction = this.getDirectionBetweenKeys(this.getCurrentKey(), key);

			currentKey = this.getCurrentKey();
			nextKey = key;
			currentSlide = this.getCurrentSlide();
			nextSlide = this.getSlideAtKey(key);

			//Trigger public method
			jQuery.when(this.options.onBeforeTransition.apply(this, [currentSlide, nextSlide, nextKey])).then(function () {
				var here = self,
					cKey = currentKey,
					nKey = nextKey,
					cSlide = currentSlide,
					nSlide = nextSlide;

				self.highlightIndicator(nextKey);

				//Away we go!
				def = self[self.options.transition + 'ToSlide'](nextKey);

				//After transition
				def.then(function () {
					here.setCurrentKey(nKey);
					here.animationLock = false;

					//Trigger public method
					here.options.onAfterTransition.apply(here, [cSlide, nSlide, cKey]);
				});
			});
		},


		/**
		 * Highlight indicator
		 * @param key
		 */
		highlightIndicator : function (key) {
			this.$indicators.removeClass(this.options.indicatorActiveClass);
			this.$indicators.eq(key).addClass(this.options.indicatorActiveClass);
		},


		/**
		 * Calculate transition direction between two keys
		 * @param {int} key1
		 * @param {int} key2
		 * @returns {*}
		 */
		getDirectionBetweenKeys : function (key1, key2) {
			var direction;

			if (key1 > key2) {
				direction = constants.DIRECTION_REVERSE;
			} else {
				direction = constants.DIRECTION_FORWARD;
			}

			//Edge cases
			if (key2 === 0 && key1 >= (this.slideCount - 1)) {
				direction = constants.DIRECTION_FORWARD;
			}

			if (key1 === 0 && key2 >= (this.slideCount - 1)) {
				direction = constants.DIRECTION_REVERSE;
			}

			return direction;
		},


		/*
		 * ------------------------------------------------------
		 *  Fade transition
		 * ------------------------------------------------------
		 */

		fadeToSlide : function (key) {
			var self = this,
				def = new jQuery.Deferred(),
				$currentSlide = this.getCurrentSlide(),
				$nextSlide = this.getSlideAtKey(key);


			//Prep layering and position
			$currentSlide.css({
				'zIndex' : this.options.zIndexLayer1
			});

			$nextSlide.hide().css({
				'zIndex' : this.options.zIndexLayer2
			});

			//Fade In
			$nextSlide.fadeIn(this.options.fadeInDuration, function () {
				self.postFade($currentSlide, $nextSlide);
				def.resolve();
			});

			return def;
		},


		postFade : function ($currentSlide, $nextSlide) {
			$currentSlide.hide().css({
				'zIndex' : this.options.inactiveZIndex
			});
			$nextSlide.css({
				'zIndex' : this.options.inactiveZIndex
			});

			//Autoplay
			if (this.options.autoplay) {
				this.autoplay();
			}
		},
		
		/*
		 * ------------------------------------------------------
		 *  Slide Transition
		 * ------------------------------------------------------
		 */
		slideToSlide : function (key) {
			var self = this,
				direction = this.getDirectionBetweenKeys(this.getCurrentKey(), key),
				def = new jQuery.Deferred(),
				$prevSlide = this.getPrevSlide(),
				$currentSlide = this.getCurrentSlide(),
				$nextSlide = this.getSlideAtKey(key),
				$nextNextSlide = this.getSlideAtKey(this.getNthRelativeKey(2)),
				directionPrefix = this.direction === constants.DIRECTION_FORWARD ? "-=" : "+=";


			//Prep layering and position
			if(direction === constants.DIRECTION_FORWARD){
				$prevSlide.css({
					'zIndex' : this.options.zIndexLayer1,
					'left' : -1 * this.width
				});
				$currentSlide.css({
					'zIndex' : this.options.zIndexLayer1,
					'left' : 0
				});
	
				$nextSlide.css({
					'zIndex' : this.options.zIndexLayer2,
					'left' : this.width
				});
				$nextNextSlide.css({
					'zIndex' : this.options.zIndexLayer1,
					'left' : this.width * 2
				});
			} else {
				$prevSlide.css({
					'zIndex' : this.options.zIndexLayer1,
					'left' : this.width
				});
				$currentSlide.css({
					'zIndex' : this.options.zIndexLayer1,
					'left' : 0
				});
	
				$nextSlide.css({
					'zIndex' : this.options.zIndexLayer2,
					'left' : this.width * -1
				});
				$nextNextSlide.css({
					'zIndex' : this.options.zIndexLayer1,
					'left' : this.width * -2
				});
			}
			 
			//Slide
			jQuery().add($currentSlide).add($nextSlide).add($nextNextSlide).add($prevSlide).animate({
				left: directionPrefix + this.width
			},{
				queue: false,
				duration: this.options.slideDuration,
				complete: function () {
					self.postSlide($currentSlide, $nextSlide, $nextNextSlide);
					def.resolve();
				}
			});
			$prevSlide.fadeOut({
				queue: false,
				duration: this.options.slideDuration
			});
			$currentSlide.fadeTo(this.options.slideDuration, this.options.slideInactiveOpacity);
			$nextSlide.fadeTo(this.options.slideDuration, 1);
			$nextNextSlide.fadeTo(this.options.slideDuration, this.options.slideInactiveOpacity);

			return def;
		},


		/**
		 * Post slide event
		 * 
		 * @param $currentSlide
		 * @param $nextSlide
		 */
		postSlide : function () {
			//Autoplay
			if (this.options.autoplay) {
				this.autoplay();
			}
		},


		/*
		 * ------------------------------------------------------
		 *  Pub/Sub
		 * ------------------------------------------------------
		 */

		/**
		 * Bind to event
		 * @param {Function} cb
		 */
		on : function (eventName, cb) {
			this.$container.on(eventName, cb);
		},

		/**
		 * Trigger local event
		 * @param {string} eventName
		 */
		trigger : function (eventName, data) {
			this.$container.trigger(eventName, data);
		},


		/**
		 * Bind UI interaction event listeners
		 */
		bindInteractionEvents : function () {
			var self = this;

			//Prev/Next
			this.$nextBtn.click(function (e) {
				e.preventDefault();
				if(self.direction !== "forward") {
					self.direction = "forward";
				}
				if(self.options.interactionDisablesAutoplay) {
					self.stopAutoplay();
				}
				self.trigger('ms.next');
				return false;
			});
			this.$prevBtn.click(function (e) {
				e.preventDefault();
				if(self.direction !== "reverse") {
					self.direction = "reverse";
				}
				if(self.options.interactionDisablesAutoplay) {
					self.stopAutoplay();
				}
				self.trigger('ms.next');
				return false;
			});

			//Indicators
			this.$indicators.each(function (i) {
				var there = self;
				jQuery(this).click(function (e) {
					e.preventDefault();
					there.trigger('ms.goTo', [i]);
					return false;
				});
			});

			//Pause on click
			if (this.options.pauseOnClick) {
				this.$container.click(function (e) {
					e.preventDefault();
					self.trigger('ms.stop');
					return false;
				});
			}
		},


		/**
		 * Bind custom event actions
		 */
		bindEvents : function () {
			var self = this;

			//User interaction events
			this.bindInteractionEvents();


			//Next & prev events
			this.on('ms.next', jQuery.proxy(this.onNext, this));
			this.on('ms.prev', jQuery.proxy(this.onPrev, this));

			//Go direct to slide events
			this.on('ms.goTo', function (e, key) {
				self.stopAutoplay();
				self.transitionToSlide(key);
			});

			this.on('ms.start', jQuery.proxy(this.onStart, this));
			this.on('ms.stop', jQuery.proxy(this.stopAutoplay, this));
		},

		preloadImages : function (nodes) {
			jQuery(nodes).each(function () {
				var img = document.createElement("img");
				img.src = jQuery(this).attr('src');
			});
		}
	};


	/**
	 * jQuery API
	 *
	 * @param options
	 * @returns {Array|Object|boolean}
	 */
	jQuery.fn.ckslider = function (options) {
		var a = [];
		this.each(function () {
			a.push(new CKSlider(this, options));
		});

		return a.length > 1 ? a : a.length === 1 ? a[0] : false;
	};
})();