/*!
 * Lectric v0.4.2
 * http://github.com/mckinney/lectric
 *
 * Copyright 2011, McKinney
 * Licensed under the MIT license.
 * http://github.com/mckinney/lectric/blob/master/LICENSE
 *
 * Author: Brett C. Buddin (http://github.com/brettbuddin)
 */

(function(window) {
	var ua = navigator.userAgent.toLowerCase();
	isWebkit = !!ua.match(/applewebkit/i);
	var supportsTouch = false;
	try {
		document.createEvent("TouchEvent");
		supportsTouch = true;
	} catch (e) {}

	var cssWithoutUnit = function(element, attribute) {
		var measure = element.css(attribute);
		return (measure !== undefined) ? parseInt(measure.replace('px', ''), 10) : 0;
	};

	var Position = function(x, y) {
		if (x && x.hasOwnProperty('x') && x.hasOwnProperty('y')) {
			x = x.x;
			y = x.y;
		}
		this.x = x;
		this.y = y;
	};
	Position.prototype = {
		difference: function(p) {
			return new Position(p.x - this.x, p.y - this.y);
		}
	};



	var Lectric = function() {
		if (supportsTouch && isWebkit) {
			return new TouchSlider();
		} else {
			return new BaseSlider();
		}
	};

	var BaseSlider = function() {};

	// Initialize the BaseSlider.
	//
	// text - The String CSS selector of the slider container.
	// opts - The Map of extra parameters.
	//
	// Returns nothing.
	BaseSlider.prototype.init = function(target, opts) {
		this.opts = jQuery.extend({
			reverse: false,
			next: undefined,
			previous: undefined,
			itemWrapperClassName: 'items',
			itemClassName: 'item',
			limitLeft: false,
			limitRight: false,
			animateEasing: 'swing',
			animateDuration: $.fx.speeds._default,
			hooks: {},
			touchMoveX: 0
		}, opts);

		this.position = new Position(0, 0);
		this.startPosition = new Position(this.position);
		this.lastPosition = new Position(this.position);

		// Set up the styling of the slider
		var element = $('<div/>', {
			className: this.opts.itemWrapperClassName
		});
		element.css('width', '1000000px');

		var itemSelector = '.' + this.opts.itemClassName;
		var itemWrapperSelector = '.' + this.opts.itemWrapperClassName;

		$(target).css('overflow', 'hidden');
		$(target).find(itemSelector).css('float', 'left').wrapAll(element);
		$(target).addClass('lectric-slider');
		this.element = $(target).find(itemWrapperSelector);
		this.element.itemSelector = itemSelector;
		this.element.itemWrapperSelector = itemWrapperSelector;

		var self = this;

		var type = supportsTouch ? 'touchstart' : 'click';
		$(this.opts.next).bind(type, function(e) {
			e.preventDefault();
			var page = self.page();
			self.to(page + 1);
			self.element.trigger('nextButton.lectric');
		});

		$(this.opts.previous).bind(type, function(e) {
			e.preventDefault();
			var page = self.page();
			self.to(page - 1);
			self.element.trigger('previousButton.lectric');
		});

		// Keep clicks from doing what they do if
		// we support touch on this device
		if (supportsTouch) {
			$(this.opts.next).click(function(e) {
				e.preventDefault();
			});

			$(this.opts.previous).click(function(e) {
				e.preventDefault();
			});
		}

		// Bind callbacks passed in at initialization
		$.each(this.opts.hooks, function(name, fn) {
			if ($.isArray(fn)) {
				$.each(fn, function(fn2) {
					self.subscribe(name, fn2);
				});
			} else {
				self.subscribe(name, fn);
			}
		});

		this.element.trigger('init.lectric');
	};

	// Update the current position of the slider.
	//
	// opts - The Map of extra parameters:
	//        animate - Boolean of whether or not to animate between two states.
	//        triggerSlide - Boolean of whether or not to trigger the move hook.
	//
	// Returns nothing.
	BaseSlider.prototype.update = function(opts) {
		var options = jQuery.extend({animate: true, triggerSlide: true}, opts);

		var self = this;
		var after = function() {
			self.element.trigger('animationEnd.lectric');
			$(this).dequeue();
		};

		if (options.animate) {
			this.element.animate({left: this.position.x + 'px'},
													 this.opts.animateDuration,
													 this.opts.animateEasing
			).queue(after);
		} else {
			this.element.css({left: this.position.x + 'px'}).queue(after);
		}

		if (options.triggerSlide) { this.element.trigger('slide.lectric'); }
	};


	// Subscribe a callback function to a hook.
	//
	// name - The String name of the hook.
	// fn - The Function callback to execute when the hook is triggered.
	//
	// Returns the Function callback that was bound to the hook.
	BaseSlider.prototype.subscribe = function(name, fn) {
		var self = this;
		var callback = function(e) {
			if (e.target == self.element[0]) {
				fn(self, e);
			}
		};

		this.element.bind(name + '.lectric', callback);
		return callback;
	};
	BaseSlider.prototype.bind = function(name, fn) {
		this.subscribe(name, fn);
	};

	// Unsubscribe a callback function from a hook or unsubscribe all callbacks from a hook.
	//
	// name - The String name of the hook.
	// fn - The Function handler to unbind from the element.
	//
	// Returns nothing.
	BaseSlider.prototype.unsubscribe = function(name, fn) {
		if (typeof fn !== undefined && $.isFunction(fn)) {
			this.element.unbind(name + '.lectric', fn);
		} else {
			this.element.unbind(name + '.lectric');
		}
	};
	BaseSlider.prototype.unbind = function(name, fn) {
		this.unsubscribe(name, fn);
	};

	// Retrieve the current page of the slider.
	//
	// Returns the Integer page number of the slider.
	BaseSlider.prototype.page = function() {
		return Math.abs(Math.round(this.position.x / this.itemWidth()));
	};

	// Move to a specific page number.
	//
	// page - The Integer page number to move to.
	//
	// Returns nothing.
	BaseSlider.prototype.to = function(page) {
		var previous = this.position.x;
		this.position.x = this.limitXBounds(this.xForPage(page));
		if (this.position.x !== previous) {
			this.update();
		}
	};

	// Move to a specific item in the slider, regardless of its position.
	//
	// item - The DOM Reference of the item you'd like to move to.
	//
	// Returns nothing.
	BaseSlider.prototype.toItem = function(item) {
		var all = this.element.find(this.element.itemSelector);

		var i;
		var length = all.length;
		for (i = 0; i < length; i++) {
			if ($(all[i])[0] == item[0]) { this.to(i); }
		}
	};

	// Retrieve the current X position.
	//
	// page - The Integer page number.
	//
	// Returns the Integer X position of the slider.
	BaseSlider.prototype.xForPage = function(page) {
		var flip = (this.opts.reverse) ? 1 : -1;
		return flip * page * this.itemWidth();
	};


	// Retrieve the width of a single item (including margin-right and padding).
	//
	// Returns the Integer width of a single item.
	BaseSlider.prototype.itemWidth = function() {
		var first = this.element.find(this.element.itemSelector).eq(0);
		var padding = cssWithoutUnit(first, 'paddingRight') + cssWithoutUnit(first, 'paddingLeft');
		return cssWithoutUnit(first, 'marginRight') + padding + first.width();
	};

	// Retrieve number of items in the slider.
	//
	// Returns the Integer number of items.
	BaseSlider.prototype.itemCount = function() {
		return this.element.find(this.element.itemSelector).size();
	};


	// Constrain the X position to within the slider beginning and end.
	//
	// x - The Integer X position
	//
	// Returns the Integer X position after being constrained.
	BaseSlider.prototype.limitXBounds = function(x) {
		var itemWidth = this.itemWidth();
		var itemCount = this.itemCount();
		var totalWidth = itemWidth * itemCount;

		if (this.opts.reverse) {
			x = (x > totalWidth - itemWidth) ?  totalWidth - itemWidth : x;
			//x = (x < 0) ? 0 : x;
		} else {
			x = (x < -totalWidth + itemWidth) ?  -totalWidth + itemWidth : x;
			//x = (x > 0) ? 0 : x;
		}

		if ((this.position.x - x > 0 && this.opts.limitRight) ||
				(this.position.x - x < 0 && this.opts.limitRight)) {
			x = this.position.x;
		}

		return x;
	};



	var TouchSlider = function() {};
	TouchSlider.prototype = new BaseSlider();
	TouchSlider.superobject = BaseSlider.prototype;

	// Initialize the TouchSlider.
	//
	// text - The String CSS selector of the slider container.
	// opts - The Map of extra parameters.
	//
	// Returns nothing.
	TouchSlider.prototype.init = function(target, opts) {
		TouchSlider.superobject.init.call(this, target, opts);
		this.opts = $.extend({
			tossFunction: function(x, dx, dt) {
				return x + dx * 100 / dt;
			},
			tossing: false
		}, this.opts);
		$(target).addClass('lectric-slider-touch');

		this.gesturing = false;
		$(target)[0].addEventListener('touchstart', this, false);
		$(target)[0].addEventListener('webkitTransitionEnd', this, false);
	};

	// Proxy the events triggered on the element to another function.
	//
	// event - The Event triggered on the element
	//
	// Returns nothing.
	TouchSlider.prototype.handleEvent = function(event) {
		TouchEvents[event.type].call(this, event);
	};



	// Update the current position of the slider.
	//
	// opts - The Map of extra parameters:
	//        animate - Boolean of whether or not to animate between two states.
	//        triggerSlide - Boolean of whether or not to trigger the move hook.
	//
	// Returns nothing.
	TouchSlider.prototype.update = function(opts) {
		var options = jQuery.extend({animate: true, triggerSlide: true}, opts);
		if (options.animate) { this.decayOn(); }
		this.element.css({'-webkit-transform': 'translate3d(' + this.position.x + 'px, 0, 0)'});

		if (options.triggerSlide) { this.element.trigger('slide.lectric'); }
	};


	// Turn off CSS3 animation decay.
	//
	// Returns nothing.
	TouchSlider.prototype.decayOff = function() {
		this.element.css({'-webkit-transition-duration': '0s'});
		this.element.css({'-webkit-transition-property': 'none'});
	};

	// Turn on CSS3 animation decay.
	//
	// Returns nothing.
	TouchSlider.prototype.decayOn = function() {
		var duration = this.opts.animateDuration;
		if (typeof duration === "number") {
			duration = duration / 1000;
		} else {
			if (duration in $.fx.speeds) {
				duration = $.fx.speeds[duration];
			} else {
				duration = $.fx.speeds._default;
			}
		}
		this.element.css({'-webkit-transition-duration': duration + 's'});
		this.element.css({'-webkit-transition-property': '-webkit-transform'});
	};

	var TouchEvents = {
		click: function(e) {
			if (this.moved) { e.preventDefault(); }
			this.element[0].removeEventListener('click', this, false);
			return false;
		},

		touchstart: function(e) {
			this.currentTarget = e.currentTarget;
			this.startPosition.x = e.touches[0].pageX - this.position.x;
			this.startPosition.y = e.touches[0].pageY - this.position.y;
			this.moved = false;

			window.addEventListener('gesturestart', this, false);
			window.addEventListener('gestureend', this, false);
			window.addEventListener('touchmove', this, false);
			window.addEventListener('touchend', this, false);
			this.element[0].addEventListener('click', this, false);

			this.decayOff();

			this.element.trigger('start.lectric');
		},

		touchmove: function(e) {
			if (this.gesturing) { return false; }

			if (!this.moved) {
				var deltaY = e.touches[0].pageY - this.startPosition.y;
				var deltaX = e.touches[0].pageX - this.startPosition.x;
				if (Math.abs(deltaY) < 15) {
					e.preventDefault();
				}

				this.element.trigger('firstSlide.lectric');
			}

			this.moved = true;
			this.lastPosition.x = this.position.x;
			this.lastPosition.y = this.position.y;
			this.lastMoveTime = new Date();

			this.position.x = this.limitXBounds(e.touches[0].pageX - this.startPosition.x);

			this.update({animate: false});
		},

		touchend: function(e) {
			window.removeEventListener('gesturestart', this, false);
			window.removeEventListener('gestureend', this, false);
			window.removeEventListener('touchmove', this, false);
			window.removeEventListener('touchend', this, false);

			if (this.moved) {
				var dx = this.position.x - this.lastPosition.x;
				var dt = (new Date()) - this.lastMoveTime + 1;

				var width = this.itemWidth();

				var winW = 0, winH = 0;
				if (document.body && document.body.offsetWidth) {
				 winW = document.body.offsetWidth;
				}
				if (document.compatMode=='CSS1Compat' &&
						document.documentElement &&
						document.documentElement.offsetWidth ) {
				 winW = document.documentElement.offsetWidth;
				 winH = document.documentElement.offsetHeight;
				}
				if (window.innerWidth && window.innerHeight) {
				 winW = window.innerWidth;
				 winH = window.innerHeight;
				}

				var nrOfItems = $("#slideshow .items .item").length;
				var itemWidth = $("#slideshow .items .item:first").width();
				var totalWidth = nrOfItems * itemWidth;
				var newWidth = (totalWidth - winW);

				if (this.opts.tossing) {
					var tossedX = this.limitXBounds(this.opts.tossFunction(this.position.x, dx, dt));
					this.position.x = Math.round(tossedX / width) * width + this.opts.touchMoveX;
				} else {
					this.position.x = Math.round(this.position.x / width) * width + this.opts.touchMoveX;
				}

				if ( -this.position.x > newWidth)
				{
					this.position.x = -newWidth;
				}

				if ( this.position.x > 0)
				{
					this.position.x = 0;
				}

				this.update();
				this.element.trigger('end.lectric');
			} else {
				this.element.trigger('endNoSlide.lectric');
			}

			this.currentTarget = undefined;
		},

		gesturestart: function(e) {
			this.gesturing = true;
		},

		gestureend: function(e) {
			this.gesturing = false;
		},

		webkitTransitionEnd: function(e) {
			this.element.trigger('animationEnd.lectric');
		}
	};

	Lectric.BaseSlider = BaseSlider;
	Lectric.TouchSlider = TouchSlider;
	window.Lectric = Lectric;
})(window);

