(function( window, $, undefined ) {

	

	// http://www.netcu.de/jquery-touchwipe-iphone-ipad-library

	$.fn.touchwipe 				= function(settings) {

		

		var config = {

			min_move_x: 20,

			min_move_y: 20,

			wipeLeft: function() { },

			wipeRight: function() { },

			wipeUp: function() { },

			wipeDown: function() { },

			preventDefaultEvents: true

		};

     

		if (settings) $.extend(config, settings);

 

		this.each(function() {

			var startX;

			var startY;

			var isMoving = false;



			function cancelTouch() {

				this.removeEventListener('touchmove', onTouchMove);

				startX = null;

				isMoving = false;

			}	

		 

			function onTouchMove(e) {

				if(config.preventDefaultEvents) {

					e.preventDefault();

				}

				if(isMoving) {

					var x = e.touches[0].pageX;

					var y = e.touches[0].pageY;

					var dx = startX - x;

					var dy = startY - y;

					if(Math.abs(dx) >= config.min_move_x) {

						cancelTouch();

						if(dx > 0) {

							config.wipeLeft();

						}

						else {

							config.wipeRight();

						}

					}

					else if(Math.abs(dy) >= config.min_move_y) {

						cancelTouch();

						if(dy > 0) {

							config.wipeDown();

						}

						else {

							config.wipeUp();

						}

					}

				}

			}

		 

			function onTouchStart(e)

			{

				if (e.touches.length == 1) {

					startX = e.touches[0].pageX;

					startY = e.touches[0].pageY;

					isMoving = true;

					this.addEventListener('touchmove', onTouchMove, false);

				}

			}    	 

			if ('ontouchstart' in document.documentElement) {

				this.addEventListener('touchstart', onTouchStart, false);

			}

		});

 

		return this;

	};

	

	$.elastislide 				= function( options, element ) {

		this.$el	= $( element );

		this._init( options );

	};

	

	$.elastislide.defaults 		= {

		speed		: 450,	// animation speed

		easing		: '',	// animation easing effect

		imageW		: 191,	// the images width

		margin		: 8,	// image margin right

		border		: 2,	// image border

		minItems	: 1,	// the minimum number of items to show. 

							// when we resize the window, this will make sure minItems are always shown 

							// (unless of course minItems is higher than the total number of elements)

		current		: 0,	// index of the current item

		onLoaded	: '',
							// when we resize the window, the carousel will make sure this item is visible 

		onClick		: function() { return false; } // click item callback 

    };

	

	$.elastislide.prototype 	= {

		_init 				: function( options ) {

			

			this.options 		= $.extend( true, {}, $.elastislide.defaults, options );

			

			// <ul>

			this.$slider		= this.$el.find('ul');

			

			// <li>

			this.$items			= this.$slider.children('li');

			

			// total number of elements / images

			this.itemsCount		= this.$items.length;

			

			// cache the <ul>'s parent, since we will eventually need to recalculate its width on window resize

			this.$esCarousel	= this.$slider.parent();

			

			// validate options

			this._validateOptions();

			

			// set sizes and initialize some vars...

			this._configure();

			

			// add navigation buttons

			this._addControls();

			

			// initialize the events

			this._initEvents();

			//fit slider
			
			this._loaded();

			// show the <ul>

			this.$slider.show();

			

			// slide to current's position

			this._slideToCurrent( false );

			

		 

			

		},

		_validateOptions	: function() {

		

			if( this.options.speed < 0 )

				this.options.speed = 450;

			if( this.options.margin < 0 )

				this.options.margin = 4;

			if( this.options.border < 0 )

				this.options.border = 1;

			if( this.options.minItems < 1 || this.options.minItems > this.itemsCount )

				this.options.minItems = 1;

			if( this.options.current > this.itemsCount - 1 )





				this.options.current = 0;

				

		},

		_configure			: function() {

			

			// current item's index

			this.current		= this.options.current;

			

			// the ul's parent's (div.es-carousel) width is the "visible" width

			this.visibleWidth	= this.$esCarousel.width();

			

			// test to see if we need to initially resize the items

			if( this.visibleWidth < this.options.minItems * ( this.options.imageW + 2 * this.options.border ) + ( this.options.minItems - 1 ) * this.options.margin ) {

				this._setDim( ( this.visibleWidth - ( this.options.minItems - 1 ) * this.options.margin ) / this.options.minItems );

				this._setCurrentValues();

				// how many items fit with the current width

				this.fitCount	= this.options.minItems;

			}

			else {

				this._setDim();

				this._setCurrentValues();

			}

			

			// set the <ul> width

			this.$slider.css({

				width	: this.sliderW

			});

			

		},

		_setDim				: function( elW, elm ) {

			
			var instance = elm ? elm : this;
			// <li> style

			instance.$items.css({

				marginRight	: instance.options.margin,

				width		: ( elW ) ? elW : instance.options.imageW + 2 * this.options.border

			}).children('a').css({ // <a> style

				borderWidth		: instance.options.border

			});

			

		},

		_setCurrentValues	: function(elm) {
	
			var instance  = elm ? elm : this;		
			
			// the total space occupied by one item

			instance.itemW			= instance.$items.outerWidth(true);

			// total width of the slider / <ul>

			// this will eventually change on window resize

			instance.sliderW		= instance.itemW * instance.itemsCount;

			

			// the ul parent's (div.es-carousel) width is the "visible" width

			instance.visibleWidth	= instance.$esCarousel.width();

			

			// how many items fit with the current width

			instance.fitCount		= Math.floor( instance.visibleWidth / instance.itemW );

			

		},

		_addControls		: function() {

			

			this.$navNext	= $('<span class="es-nav-next">Next</span>');

			this.$navPrev	= $('<span class="es-nav-prev">Previous</span>');

			$('<div class="es-nav"/>')

			.append( this.$navPrev )

			.append( this.$navNext )

			.appendTo( this.$el );

			

			//this._toggleControls();

				

		},

		_toggleControls		: function( dir, status ) {

			

			// show / hide navigation buttons

			if( dir && status ) {

				if( status === 1 )

					( dir === 'right' ) ? this.$navNext.show() : this.$navPrev.show();

				else

					( dir === 'right' ) ? this.$navNext.hide() : this.$navPrev.hide();

			}

			else if( this.current === this.itemsCount - 1 || this.fitCount >= this.itemsCount )

					this.$navNext.hide();

			

		},

		_initEvents			: function(elm) {

			

			var instance	= elm ? elm : this;

			

			// window resize

			$(window).bind('resize.elastislide', function( event ) {

				

				instance._reload();

				

				// slide to the current element

				clearTimeout( instance.resetTimeout );

				instance.resetTimeout	= setTimeout(function() {

					instance._slideToCurrent();

				}, 200);

				

			});
			
			// navigation buttons events

			this.$navNext.bind('click.elastislide', function( event ) {

				instance._slide('right'); 
			});

			

			this.$navPrev.bind('click.elastislide', function( event ) {

				instance._slide('left');
			});

			

			// item click event

			this.$items.bind('click.elastislide', function( event ) {

				instance.options.onClick( $(this) );

				return false;

			});

			

			// touch events

			instance.$slider.touchwipe({

				wipeLeft			: function() {

					instance._slide('right');

				},

				wipeRight			: function() {

					instance._slide('left');

				}

			});

			

		},
		
		
		_loaded				: function(){
			var instance = this;
			var images = instance.$slider.find( 'img' );
			var count = 0;
			images.each( function(index,image){ 
				if( !image.complete ){				  
					image.onload =function(){
						count++;
						if( count >= images.length ){
							instance.__proto__.onComplete(instance);
						}
					}
					image.onerror =function(){ 
						count++;
						if( count >= images.length ){
							instance.__proto__.onComplete(instance);								
						}	
					}
				}else {
					count++;
					if( count >= images.length ){
						instance.__proto__.onComplete(instance);
					}	
				}
			} );
		},

		reload				: function( callback ) {

			this._reload();

			if ( callback ) callback.call();

		

		},
		
		onComplete				: function(elm){
			var instance = elm ? elm : this;
			clearTimeout( instance.resetTimeout );
			instance.resetTimeout	= setTimeout(function() {
				instance._reload(instance);
				instance._slideToCurrent();
				instance.startUp();
			}, 200);
		},
		
		startUp				: function(){
			var instance = this;
			if(instance.options.onLoaded){
				instance.options.onLoaded( $(this) );
			}
		},
		
		_reload				: function(instance) {


			var instance	= instance ? instance : this;
			
			

			// set values again

			instance._setCurrentValues();

			

			// need to resize items

			if( instance.visibleWidth < instance.options.minItems * ( instance.options.imageW + 2 * instance.options.border ) + ( instance.options.minItems - 1 ) * instance.options.margin ) {

				instance._setDim( ( instance.visibleWidth - ( instance.options.minItems - 1 ) * instance.options.margin ) / instance.options.minItems );

				instance._setCurrentValues();

				instance.fitCount	= instance.options.minItems;

			}	

			else{

				instance._setDim();

				instance._setCurrentValues();

			}
			instance.$slider.css({

				width	: instance.sliderW + 10 // TODO: +10px seems to solve a firefox "bug" :S

			});

			

		},

		_slide				: function( dir, val, anim, callback ) {

			

			// if animating return

			//if( this.$slider.is(':animated') )

				//return false;

			

			// current margin left

			var ml		= parseFloat( this.$slider.css('margin-left') );

			

			// val is just passed when we want an exact value for the margin left (used in the _slideToCurrent function)

			if( val === undefined ) {

			

				// how much to slide?

				var amount	= this.fitCount * this.itemW, val;

				

				if( amount < 0 ) return false;

				

				// make sure not to leave a space between the last item / first item and the end / beggining of the slider available width

				if( dir === 'right' && this.sliderW - ( Math.abs( ml ) + amount ) < this.visibleWidth ) {

					amount	= this.sliderW - ( Math.abs( ml ) + this.visibleWidth ) - this.options.margin; // decrease the margin left

					// show / hide navigation buttons

					this._toggleControls( 'right', -1 );

					this._toggleControls( 'left', 1 );

				}

				else if( dir === 'left' && Math.abs( ml ) - amount < 0 ) {				

					amount	= Math.abs( ml );

					// show / hide navigation buttons

					this._toggleControls( 'left', -1 );

					this._toggleControls( 'right', 1 );

				}

				else {

					var fml; // future margin left

					( dir === 'right' ) 

						? fml = Math.abs( ml ) + this.options.margin + Math.abs( amount ) 

						: fml = Math.abs( ml ) - this.options.margin - Math.abs( amount );

					

					// show / hide navigation buttons

					if( fml > 0 )

						this._toggleControls( 'left', 1 );

					else	

						this._toggleControls( 'left', -1 );

					

					if( fml < this.sliderW - this.visibleWidth )

						this._toggleControls( 'right', 1 );

					else	

						this._toggleControls( 'right', -1 );

						

				}

				

				( dir === 'right' ) ? val = '-=' + amount : val = '+=' + amount

				

			}

			else {

				var fml		= Math.abs( val ); // future margin left

				

				if( Math.max( this.sliderW, this.visibleWidth ) - fml < this.visibleWidth ) {

					val	= - ( Math.max( this.sliderW, this.visibleWidth ) - this.visibleWidth );

					if( val !== 0 )

						val += this.options.margin;	// decrease the margin left if not on the first position

						

					// show / hide navigation buttons

					this._toggleControls( 'right', -1 );

					fml	= Math.abs( val );

				}

				

				// show / hide navigation buttons

				if( fml > 0 )

					this._toggleControls( 'left', 1 );

				else

					this._toggleControls( 'left', -1 );

				

				if( Math.max( this.sliderW, this.visibleWidth ) - this.visibleWidth > fml + this.options.margin )	

					this._toggleControls( 'right', 1 );

				else

					this._toggleControls( 'right', -1 );

					

			}

			

			$.fn.applyStyle = ( anim === undefined ) ? $.fn.animate : $.fn.css;

			

			var sliderCSS	= { marginLeft : val };

			

			var instance	= this;

			

			this.$slider.stop().applyStyle( sliderCSS, $.extend( true, [], { duration : this.options.speed, easing : this.options.easing, complete : function() {

				if( callback ) callback.call();

			} } ) );

			

		},

		_slideToCurrent		: function( anim ) {

			

			// how much to slide?

			var amount	= this.current * this.itemW;

			this._slide('', -amount, anim );

			

		},

		add					: function( $newelems, callback ) {

			

			// adds new items to the carousel

			this.$items 		= this.$items.add( $newelems );

			this.itemsCount		= this.$items.length;

			this._setDim();

			this._setCurrentValues();

			this.$slider.css({

				width	: this.sliderW

			});

			this._slideToCurrent();

			

			if ( callback ) callback.call( $newelems );

			

			

		},

		setCurrent			: function( idx, callback ) {

			

			this.current = idx;

			

			var ml		= Math.abs( parseFloat( this.$slider.css('margin-left') ) ),

				posR	= ml + this.visibleWidth,

				fml		= Math.abs( this.current * this.itemW );

			

			if( fml + this.itemW > posR || fml < ml ) {

				this._slideToCurrent();

			}

			

			if ( callback ) callback.call();

			

		},

		destroy				: function( callback ) {

			

			this._destroy( callback );

			

		},

		_destroy 			: function( callback ) {

			this.$el.unbind('.elastislide').removeData('elastislide');

			$(window).unbind('.elastislide');

			if ( callback ) callback.call();

		}

	};

	

	var logError 				= function( message ) {

		if ( this.console ) {

			console.error( message );

		}

	};

	

	$.fn.elastislide 				= function( options ) {

		if ( typeof options === 'string' ) {

			var args = Array.prototype.slice.call( arguments, 1 );



			this.each(function() {

				var instance = $.data( this, 'elastislide' );

				if ( !instance ) {

					logError( "cannot call methods on elastislide prior to initialization; " +

					"attempted to call method '" + options + "'" );

					return;

				}

				if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {

					logError( "no such method '" + options + "' for elastislide instance" );

					return;

				}

				instance[ options ].apply( instance, args );

				

			});

		} 

		else {

			this.each(function() {

				var instance = $.data( this, 'elastislide' );

				if ( !instance ) {

					$.data( this, 'elastislide', new $.elastislide( options, this ) );

				}

			});

		}

		return this;

	};

	

})( window, jQuery );
