(function($){

	//Defaults
	var defaults = {
		paneSelector : '.pane',
		itemSelector : '.item',
		controlsSelector : false,
		showItemCount : true,
		intentRatio : .3,
		positionOffset : '0px',
		clickFunction : null,
		initialItem : 0,
		slideCallback : null,
		minimumSpeed : 2,
		easing : 'swing'
	}

	var mouseDown = false, //flag for mouse events
		mouseTarget = null; //target from mousedown

	var methods = {
		init : function ( options ) {

			return this.each(function(){

				var $this = $(this),
					opts = $.extend(defaults, options),
					data = $this.data('ysp');

				$this.addClass('ysp_frame');

				if ( !data ){
					//setup defaults and vars
					$this.data('ysp',{
						frame : $this,
						pane : $(opts.paneSelector,$this),
						itemSelector : opts.itemSelector,
						items : $(opts.itemSelector, $(opts.paneSelector,$this)),
						controlsSelector : opts.controlsSelector,
						controls: $this.siblings(opts.controlsSelector),
						showItemCount : opts.showItemCount,
						intentRatio : opts.intentRatio,
						positionOffset : opts.positionOffset,
						clickFunction : opts.clickFunction,
						startX : false,
						endX : false,
						startT : false,
						startIndex : false, //index of the item the "start" event was called on
						defaultSpeed : 1, //pixels/ms
						minimumSpeed : opts.minimumSpeed,
						easing : opts.easing,
						paneOffset : 0,
						currentItem : 0,
						itemCount : 0,
						offsets : [], //holds offsets for items
						initialItem : opts.initialItem, //starting item index
						slideCallback : opts.slideCallback, //function to call after slide animation
						firstSlide : true
					})

					data = $this.data('ysp');
				}

				//set pane width
				var fullWidth = 200;
				data.items.each(function(){
					fullWidth += $(this).outerWidth(true);
				});

				data.pane.width(fullWidth);

				bindPane(data);
			})
		},
		state : function ( property ) {

				var $this = this,
					data = $this.data('ysp'),
					state = "";
				switch (property){
					case 'currentItem':
						state = data.currentItem;
						break;
					case 'count':
						state = data.itemCount;
						break;
					case 'paneOffset':
						state = data.paneOffset;
						break;
					default:
						state = "Frame: " + data.frame.attr('id') + " " + data.frame.attr('class') + "\n" +
							"Pane: " + data.pane.attr('id') + " " + data.pane.attr('class') + "\n" +
							"Item Selector: " + data.itemSelector + "\n" +
							"Items: " + data.itemCount + "\n" +
							"Current Item: " + data.currentItem;
				}

				return state;
		},
		destroy : function ( ) {

			return this.each(function(){

				var $this = $(this),
					data = $this.data('ysp');

					//remove current bindings
					data.items.unbind();
					data.controls.html("");
					data.items.css('left','0px');

					$this.removeData('ysp');
					$this.removeClass('ysp_frame');
			})
		},
		next : function ( ) {

			return this.each(function(){
				var $this = $(this),
					data = $this.data('ysp');

					$this.ysp('slideTo', data.currentItem + 1, data.defaultSpeed);
			});
		},
		previous : function ( ) {
			return this.each(function(){
				var $this = $(this),
					data = $this.data('ysp');

					$this.ysp('slideTo', data.currentItem - 1, data.defaultSpeed);
			});
		},
		//animate slider to a specific item
		//index: index of item to move to
		//speed: speed of animation in pixels/ms
		slideTo : function ( index ,speed,fromClick ) {

			return this.each(function(){
				var $this = $(this),
					data = $this.data('ysp');

				fromClick = fromClick === undefined ? true : false;

				//check if speed was specified (in pixels/ms)
				speed = speed === false || speed === undefined ? data.defaultSpeed : speed;

				//check if speed is above min
				speed = speed < data.minimumSpeed ? data.minimumSpeed : speed;

				//get current slider position
				var p = data.pane.position();

				//check if requested index is in range
				if( index >= 0 && index < data.itemCount ) {

					//set new item as currentItem
					data.currentItem = index;

					//get position of item and calculate animation distance
					var position = 0 - (data.offsets[data.currentItem]),
					animateDistance = Math.abs(p.left - position);

					data.paneOffset = position;

					speed = animateDistance > 1800 ? data.defaultSpeed * 5 : speed;

					//stop any ongoing animation and do new animation using duration calculated from distance/speed
					data.pane.stop(true,false).addClass('ysp_sliding').animate({'left':position,leaveTransforms:true},parseInt(animateDistance / speed),data.easing,function (){
						$(this).removeClass('ysp_sliding');
						if(typeof data.slideCallback == 'function'){
							data.slideCallback.call(this, data, fromClick);
						}
						data.firstSlide = false;
					});

					if ( data.controlsSelector !== false ){
						//update navigation marker
						$(".item_current", data.controls).html(data.currentItem + 1);

						if ( data.currentItem <= 0 ) {
							$('.item_previous',data.controls).addClass('disabled');
						}else{
							$('.item_previous',data.controls).removeClass('disabled');
						}

						if ( data.currentItem >= (data.itemCount - 1) ){
							$('.item_next',data.controls).addClass('disabled');
						}else{
							$('.item_next',data.controls).removeClass('disabled');
						}
					}

				} else {

					//out of range go to first or last
					if (index >= data.itemCount) {
						//go to last
						data.frame.ysp('slideTo', data.itemCount - 1,false);
					} else {
						//go to first
						data.frame.ysp('slideTo', 0,false);
					}
				}

				return false;
			});
		}
	}

	//setup pane and bind mouse/touch interactions
	function bindPane(data) {

		//setup image slider elements
		var lastItemPos = data.items.last().position(),
			paneWidth = lastItemPos + data.items.last().outerWidth(),
			itemControls = "";

		data.itemCount = data.items.length;

		if ( data.itemCount > 0 ) {

			//blank offset array
			data.offsets = [];

			//store offsets of new items
			data.items.each( function() {
				var p = $(this).position();
				data.offsets.push(p.left);
			});

			//add position offset
			data.items.css('left',data.positionOffset);

			//set initial position
			data.pane.css('left','-' + data.offsets[data.initialItem] + 'px');
			data.currentItem = data.initialItem;
			data.paneOffset = 0 - data.offsets[data.initialItem];

			if ( data.controlsSelector !== false ){
				//build controls
				itemControls += "<span class='item_position'>Item&nbsp;<span class='item_current'>" + (data.currentItem + 1) + "</span>&nbsp;/&nbsp;<span class='item_count'>" + data.itemCount + "</span> <span class='controls_line'>&nbsp;------------&nbsp;</span> </span><a class='item_previous' href='#'>Previous</a><span class='controls_slash'>&nbsp;/&nbsp;</span><a class='item_next' href='#'>Next</a>";

				//add enlarge link
				//itemControls += "<a onclick='enlargeHandler()'>enlarge</a>";

				//populate controls
				data.controls.html(itemControls);

				if ( data.showItemCount === false ){
					$('.item_position',data.controls).css('display','none');
				}

				//bind controls
				$('.item_previous',data.controls).click(function(event) {
					event.preventDefault();
					data.frame.ysp('previous');
				});

				$('.item_next',data.controls).click(function(event) {
					event.preventDefault();
					data.frame.ysp('next');
				});

				if ( data.currentItem === 0){
					$('.item_previous',data.controls).addClass('disabled');
				}

				if ( data.currentItem + 1 === data.items.length){
					$('.item_next',data.controls).addClass('disabled');
				}

			}

			//bind mouse
			data.items.mousedown(function(event) {
				mouseDown = true;
				//event.preventDefault();
				startHandler(event);
				return false;
			});

			$(document).mousemove(function(event) {
				if (mouseTarget != null){
					//event.preventDefault();
					moveHandler(event);
				}
				return false;
			});

			$(document).mouseup(function(event) {

					mouseDown = false;
					//event.preventDefault();
					endHandler(event);
					return false;

			});

			/*
			data.items.mouseleave(function(event) {

				if( mouseDown === true ) {
					//mouseDown = false;
					//event.preventDefault();
					endHandler(event);
					return false;
				}

			});
			*/

			data.items.click(function(event) {
				event.preventDefault();
				return false;
			});

			//bind touch
			if ( 'ontouchstart' in window ) {

				data.items.bind('touchstart',function(event) {
					var e = event.originalEvent;
					//e.preventDefault();
					startHandler(e);
					return false;
				});

				data.items.bind('touchmove',function(event) {
					var e = event.originalEvent;
					//e.preventDefault();
					moveHandler(e);
					return false;
				});

				data.items.bind('touchend',function(event) {
					var e = event.originalEvent;
					//e.preventDefault();
					endHandler(event);
					return false;
				});
			}

			//data.frame.ysp('slideTo', data.currentItem,false);
			if(typeof data.slideCallback == 'function'){
				data.slideCallback.call(this, data);
			}
			data.firstSlide = false;

		}
	}

	//interaction handlers for item slider

	//start handler
	//eventX: the 'x' coord from the event (pane only moves horizontally)
	function startHandler(event) {

		var eventX = 'ontouchstart' in window  ? event.touches[0].pageX : event.pageX,
			data = $(event.target).closest('.ysp_frame').data('ysp');

		//store target
		mouseTarget = $(event.target).closest(data.itemSelector);

		//store item index
		data.startIndex = $(event.target).closest(data.itemSelector).index();

		//stop any ongoing animation
		data.pane.stop(true,false);

		//store start time
		data.startT = new Date();

		//store current postion
		data.paneOffset = parseInt(data.pane.css('left'));

		//store start coords
		data.startX = eventX;
		data.endX = eventX;
	}

	//move handler
	//eventX: the 'x' coord from the event
	function moveHandler(event) {

		var eventX = 'ontouchstart' in window  ? event.touches[0].pageX : event.pageX,
			data = $(mouseTarget).closest('.ysp_frame').data('ysp');

		//make sure start event has been fired
		if (data.startX !== false){

			//add the distance traveled to the original position
			var newLeft = data.paneOffset + (eventX - data.startX);

			//update end coords (the end event does not return coords, we will use the last movement coord)
			data.endX = eventX;

			//move slider to new position (no animation)
			data.pane.css('left',newLeft + 'px');
		}
	}

	//end handler
	function endHandler(event) {

		if (mouseTarget != null){
			var data = $(mouseTarget).closest('.ysp_frame').data('ysp');



			if ( data.startIndex ==$(mouseTarget).closest(data.itemSelector).index() ){

			//store end time
			var endT = new Date();

			var distance = data.endX - data.startX, //total distance traveled
				time = endT - data.startT, //time taken to travel
				direction = distance < 0 ? 'left' : 'right', //determine direction based on +/- distance
				isClick = false;

			//strip direction from distance
			distance = Math.abs(distance);

			//detect 'flick' and amplify by increasing distance
			if ( time < 750 ) {

				if (distance > 15){
					//add the minimum trigger distance
					distance += data.frame.width() * data.intentRatio;
				}else if (distance < 5){
					//this is a click
					isClick = true;
					if(typeof data.clickFunction == 'function'){
						data.clickFunction.call(this, event);
					}
				}
			}

			//don't slide if click
			if (!isClick){
				//determine speed for animation
				speed = distance / time;


				//determine if movement distance was longer than our intent threshold
				if ( distance > (data.frame.width() * data.intentRatio) ) {

					//animate to next/previous item based on movement direction
					if ( direction == 'left' ) {
						data.frame.ysp('slideTo', data.currentItem + 1,speed,false);
					} else {
						data.frame.ysp('slideTo', data.currentItem - 1,speed,false);
					}

				} else {

					//too short, return to current item
					data.frame.ysp('slideTo', data.currentItem,speed);
				}
			}
			//reset time/distance vars
			data.startX = false;
			data.endX = false;
			data.startT = false;
			data.endT = false;
			mouseTarget = null;
			}
		}
	}


	$.fn.ysp = 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 $.ysp' );
		}

	};

})(jQuery)

