(function($) {

	// cache some values
	var cache	= {
			idx_expanded	: -1, // the index of the current expanded slice
			sliceH			: 0,  // the default slice's height	
			current			: 0,  // controls the current slider position
			totalSlices		: 0	  // total number of slices
		},
		aux		= {
			// triggered when we click a slice. If the slice is expanded,
			// we close it, otherwise we open it..
			selectSlice		: function( $el, $slices, $navNext, $navPrev, settings ) {
				
				return $.Deferred(
					function( dfd ) {
					
						var	expanded	= $el.data('expanded'),
							pos			= $el.data('position'),
							
							itemHeight, othersHeight,
							
							$others		= $slices.not( $el );
							
						// if it's opened..	
						if( expanded ) {
							$el.data( 'expanded', false );
							cache.idx_expanded	= -1;
							
							// the default values of each slices's height
							itemHeight	= cache.sliceH;
							othersHeight= cache.sliceH;
							
							// hide the content div
							$el.find('.va-content').hide();
							
							// control the navigation buttons visibility
							if( aux.canSlideUp( $slices, settings ) )
								$navPrev.fadeIn();
							else
								$navPrev.fadeOut();
								
							if( aux.canSlideDown( $slices, settings ) )
								$navNext.fadeIn();
							else
								$navNext.fadeOut();
						}
						// if it's closed..
						else {
							$el.data( 'expanded', true );
							cache.idx_expanded	= $el.index();
							$others.data( 'expanded', false );
							// the current slice's height
							itemHeight	= settings.expandedHeight;
							// the height the other slices will have
							othersHeight= Math.ceil( ( settings.accordionH - settings.expandedHeight ) / ( settings.visibleSlices - 1 ) );
							
							// control the navigation buttons visibility
							if( cache.idx_expanded > 0 )
								$navPrev.fadeIn();
							else	
								$navPrev.fadeOut();
							
							if( cache.idx_expanded < cache.totalSlices - 1 )
								$navNext.fadeIn();	
							else
								$navNext.fadeOut();
						}
						
						// the animation parameters for the clicked slice
						var	animParam	= { 
											height	: itemHeight + 'px', 
											opacity : 1,
											top		: ( pos - 1 ) * othersHeight + 'px'
										  };
						
						// animate the clicked slice and also its title (<h3>)
						$el.stop()
						   .animate( animParam, settings.animSpeed, settings.animEasing, function() {
								if( !expanded )
									$el.find('.va-content').fadeIn( settings.contentAnimSpeed );
						   })
						   .find('.va-title')
						   .stop()
						   .animate({
								lineHeight	: cache.sliceH + 'px'
						   }, settings.animSpeed, settings.animEasing );	
						   
						// animate all the others
						$others.each(function(i){
							var $other	= $(this),
								posother= $other.data('position'),
								t;
							
							if( expanded )
								t	= ( posother - 1 ) * othersHeight ;
							else {
								if( posother < pos )
									t	= ( posother - 1 ) * othersHeight ;
								else
									t	= ( ( posother - 2 ) * othersHeight ) + settings.expandedHeight;
							}
							
							$other.stop()
								  .animate( {
										top		: t + 'px',
										height	: othersHeight + 'px',
										opacity	: ( expanded ) ? 1 : settings.animOpacity
								  }, settings.animSpeed, settings.animEasing, dfd.resolve )
								  .find('.va-title')
								  .stop()
								  .animate({
										lineHeight	: othersHeight + 'px'
								  }, settings.animSpeed, settings.animEasing )
								  .end()
								  .find('.va-content')
								  .hide();
						});
					}
				).promise();
				
			},
			// triggered when clicking the navigation buttons / mouse scrolling
			navigate		: function( dir, $slices, $navNext, $navPrev, settings ) {
				// if animating return
				if( $slices.is(':animated') ) 
					return false;
				
				// all move up / down one position
				// if settings.savePositions is false, then we need to close any expanded slice before sliding
				// otherwise we slide, and the next one will open automatically
				var $el;
				
				if( cache.idx_expanded != -1 && !settings.savePositions ) {
					$el	= $slices.eq( cache.idx_expanded );
					
					$.when( aux.selectSlice( $el, $slices, $navNext, $navPrev, settings ) ).done(function(){
						setTimeout(function() {
						aux.slide( dir, $slices, $navNext, $navPrev, settings );
						}, 10);
					});
				}
				else {
					aux.slide( dir, $slices, $navNext, $navPrev, settings );
				}	
			},
			slide			: function( dir, $slices, $navNext, $navPrev, settings ) {
				// control if we can navigate.
				// control the navigation buttons visibility.
				// the navigation will behave differently for the cases we have all the slices closed, 
				// and when one is opened. It will also depend on settings.savePositions 
				if( cache.idx_expanded === -1 || !settings.savePositions ) {
				if( dir === 1 && cache.current + settings.visibleSlices >= cache.totalSlices )
					return false;
				else if( dir === -1 && cache.current === 0 )
					return false;
				
				if( dir === -1 && cache.current === 1 )
					$navPrev.fadeOut();
				else
					$navPrev.fadeIn();
					
					if( dir === 1 && cache.current + settings.visibleSlices === cache.totalSlices - 1 )
					$navNext.fadeOut();
				else
					$navNext.fadeIn();
				}
				else {
					if( dir === 1 && cache.idx_expanded === cache.totalSlices - 1 )
						return false;
					else if( dir === -1 && cache.idx_expanded === 0 )
						return false;
						
					if( dir === -1 && cache.idx_expanded === 1 )
						$navPrev.fadeOut();
					else
						$navPrev.fadeIn();
						
					if( dir === 1 && cache.idx_expanded === cache.totalSlices - 2 )
						$navNext.fadeOut();
					else
						$navNext.fadeIn();
				}
				
				var $currentSlice	= $slices.eq( cache.idx_expanded ),
					$nextSlice,
					t;
				
				( dir === 1 ) ? $nextSlice = $currentSlice.next() : $nextSlice = $currentSlice.prev();
				
				// if we cannot slide up / down, then we just call the selectSlice for the previous / next slice
				if( ( dir === 1 && !aux.canSlideDown( $slices, settings ) ) || 
					( dir === -1 && !aux.canSlideUp( $slices, settings ) ) ) {
					aux.selectSlice( $nextSlice, $slices, $navNext, $navPrev, settings );
					return false;
				}
					
				// if we slide down, the top and position of each slice will decrease
				if( dir === 1 ) {
					cache.current++;
					t = '-=' + cache.sliceH;
					pos_increment	= -1;
				}
				else {
					cache.current--;
					t = '+=' + cache.sliceH;
					pos_increment	= 1;
				}
				
				$slices.each(function(i) {
					var $slice		= $(this),
						pos			= $slice.data('position');
					
					// all closed or savePositions is false
					if( !settings.savePositions || cache.idx_expanded === -1 )
						$slice.stop().animate({top : t}, settings.animSpeed, settings.animEasing);
					else {
						var itemHeight, othersHeight;
						
						// if the slice is the one we should open..
						if( i === $nextSlice.index() ) {
							$slice.data( 'expanded', true );
							cache.idx_expanded	= $slice.index();
							itemHeight			= settings.expandedHeight;
							othersHeight		= ( settings.accordionH - settings.expandedHeight ) / ( settings.visibleSlices - 1 );
							
							$slice.stop()
						          .animate({
										height		: itemHeight + 'px', 
										opacity 	: 1,
										top			: ( dir === 1 ) ? ( pos - 2 ) * othersHeight + 'px' : pos * othersHeight + 'px'
								  }, settings.animSpeed, settings.animEasing, function() {
										$slice.find('.va-content').fadeIn( settings.contentAnimSpeed );
								  })
								  .find('.va-title')
								  .stop()
								  .animate({
										lineHeight	: cache.sliceH + 'px'
								  }, settings.animSpeed, settings.animEasing );
						}
						// if the slice is the one opened, lets close it
						else if( $slice.data('expanded') ){
							// collapse
							
							$slice.data( 'expanded', false );
							othersHeight		= ( settings.accordionH - settings.expandedHeight ) / ( settings.visibleSlices - 1 );
							
							$slice.stop()
						          .animate({ 
										height	: othersHeight + 'px', 
										opacity : settings.animOpacity,
										top		: ( dir === 1 ) ? '-=' + othersHeight : '+=' + settings.expandedHeight
								  }, settings.animSpeed, settings.animEasing )
								  .find('.va-title')
								  .stop()
								  .animate({
										lineHeight	: othersHeight + 'px'
								  }, settings.animSpeed, settings.animEasing )
								  .end()
								  .find('.va-content')
								  .hide();		  
						}
						// all the others..
						else {
							$slice.data( 'expanded', false );
							othersHeight		= ( settings.accordionH - settings.expandedHeight ) / ( settings.visibleSlices - 1 );
							
							$slice.stop()
						          .animate({ 
										top		: ( dir === 1 ) ? '-=' + othersHeight : '+=' + othersHeight
								  }, settings.animSpeed, settings.animEasing );
						}
					}
					// change the slice's position
					$slice.data().position += pos_increment;
				});
			},
			canSlideUp		: function( $slices, settings ) {
				var $first			= $slices.eq( cache.current );
					
				if( $first.index() !== 0 )
					return true;
			},
			canSlideDown	: function( $slices, settings ) {
				var $last			= $slices.eq( cache.current + settings.visibleSlices - 1 );
					
				if( $last.index() !== cache.totalSlices - 1 )
					return true;
			}
		},
		methods = {
			init 		: function( options ) {
				
				if( this.length ) {
					
					var settings = {
						// the accordion's width
						accordionW		: 550,
						// the accordion's height
						accordionH		: 260,
						// number of visible slices
						visibleSlices	: 3,
						// the height of a opened slice
						// should not be more than accordionH
						expandedHeight	: 350,
						// speed when opening / closing a slice
						animSpeed		: 250,
						// easing when opening / closing a slice
						animEasing		: 'jswing',
						// opacity value for the collapsed slices
						animOpacity		: 0.2,
						// time to fade in the slice's content
						contentAnimSpeed: 900,
						// if this is set to false, then before
						// sliding we collapse any opened slice
						savePositions	: true
					};
					
					return this.each(function() {
						
						// if options exist, lets merge them with our default settings
						if ( options ) {
							$.extend( settings, options );
						}
						
						var $el 			= $(this),
							// the accordion's slices
							$slices			= $el.find('div.va-slice'),
							// the navigation buttons
							$navNext		= $el.find('span.va-nav-next'),
							$navPrev		= $el.find('span.va-nav-prev');
							
						// each slice's height
						cache.sliceH		= Math.ceil( settings.accordionH / settings.visibleSlices );
						
						// total slices
						cache.totalSlices	= $slices.length;
						
						// control some user config parameters
						if( settings.expandedHeight > settings.accordionH )
							settings.expandedHeight = settings.accordionH;
						else if( settings.expandedHeight <= cache.sliceH )
							settings.expandedHeight = cache.sliceH + 50; // give it a minimum
							
						// set the accordion's width & height
						$el.css({
							width	: settings.accordionW + 'px',
							height	: settings.accordionH + 'px'
						});
						
						// show / hide $navNext 
						if( settings.visibleSlices < cache.totalSlices  )
							$navNext.show();
						
						// set the top & height for each slice.
						// also save the position of each one.
						// as we navigate, the first one in the accordion
						// will have position 1 and the last settings.visibleSlices.
						// finally set line-height of the title (<h3>)
						$slices.each(function(i){
							var $slice	= $(this);
							$slice.css({
								top		: i * cache.sliceH + 'px',
								height	: cache.sliceH + 'px'
							}).data( 'position', (i + 1) );
						})
						.children('.va-title')
						.css( 'line-height', cache.sliceH + 'px' );
						
						// click event
						$slices.bind('click.vaccordion', function(e) {
							// only if we have more than 1 visible slice. 
							// otherwise we will just be able to slide.
							if( settings.visibleSlices > 1 ) {
								var $el			= $(this);
								aux.selectSlice( $el, $slices, $navNext, $navPrev, settings );
							}
						});
						
						// navigation events
						$navNext.bind('click.vaccordion', function(e){
							aux.navigate( 1, $slices, $navNext, $navPrev, settings );
						});
						
						$navPrev.bind('click.vaccordion', function(e){
							aux.navigate( -1, $slices, $navNext, $navPrev, settings );
						});
						
						// adds events to the mouse
						$el.bind('mousewheel.vaccordion', function(e, delta) {
							if(delta > 0) {
								aux.navigate( -1, $slices, $navNext, $navPrev, settings );
							}	
							else {
								aux.navigate( 1, $slices, $navNext, $navPrev, settings );
							}	
							return false;
						});
						
					});
				}
			}
		};
	
	$.fn.vaccordion = 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.vaccordion' );
		}
	};
	
})(jQuery);