/* scroller v0.92 */

jQuery.fn.scroller = function(options) {
	var settings = jQuery.extend({
		width : 0,
		height : 0,
		items : '.ScrollItem',
		speed : 3,
		direction : 'H',
		onSelect : false, //function
		drag : true,
		cmds : [],
		mousewheel : '1item',
		over : false,
		mousedown : true,
		inverted : false,  //per le scrollbar
		keyboard : true,
		autoselect : false,
		itemscroll : 'center', //'center', 'visible', false
		type : 'scroller' //'scroller,'scrollbar'
	}, options);

	return this.each(function(){
		var $s = $(this),
			self = this;

		//force dimensions for internal absolute positioning
		$s.width(settings.width || $s.width())
		  .height(settings.height || $s.height());
		
		$commandobjs = [];
		if ('scrollbar'==settings.type) {
			settings.inverted = true;
			settings.mousewheel = false;
			settings.items = settings.handler;
			var mrg = $s.width() - $(settings.handler,$s).width();
			$(settings.handler,$s).css({'margin-left':mrg,'margin-right':mrg});
		}
		
		//make scroller;
		var	$items = $(settings.items,this);

		$s.css({overflow:'hidden'});
		if ($s.css('position')!='absolute') $s.css({position:'relative'});

		$s.wrapInner('<span/>');
		var $content = $('>span',$s).css({position:'absolute',width:'10000px',height:'10000px',top:0,left:0});
		
		var $offset = 'left', $func = 'outerWidth';
		if (settings.direction == 'V') {
			$offset = 'top'; $func = 'outerHeight';
		}
				
		var $curPx = 0,
			$curItem = null;
		
		this.curItem = function(){	return $curItem; }
		this.items = function(){	return $items; }
		
		
		this.stopScroll = function( nolink ) {
			$content.stop();
			if (!nolink) {
				$(linked).each(function(){
					this.stopScroll( true );
				});
			}
		}


		function itemProp(idx, func) {
			if (idx<0) idx += $items.length;
			var item = $items.eq(idx);
			if (item.is(':hidden') || settings.virtualitems) {
				return $s.width() * ( func == 'offset' ? idx : 1 ) //virtual item
			} else {
				return ( func == 'offset' ? 
					item.offset()[$offset] 
				: 	item[$func]() + parseInt(item.css('margin-'+$offset)) + parseInt(item.css('margin-'+('left'==$offset?'right':'bottom')))
				) 
			}
		}
		// scroll function
		this.mv = function( cmd, speed, callback, nolink, inverted ) {

			var command = /([=]?)([+\-\.\d]*)(.*)/.exec( cmd );

			var n = parseFloat(command[2]), type = command[3], positioning = command[1] ? 'absolute':'relative';
			
			self.stopScroll(nolink);
			
			if (cmd=='stop') return;

			$curPx = -parseInt($content.css('margin-'+$offset)) || 0;
			
			var limit = itemProp(-1,'offset') + itemProp(-1,'outersize') - itemProp(0,'offset') - ('left'==$offset?$s.width():$s.height());

			var dest = $curPx;
		
			/* type checking */
			if ('item'!=type) {
				dest = n;
				switch (type) {
					case 'page' : dest *= $s.width(); break;
					case '%' 	: dest /= 100;
					case 'perc' : dest *= limit; break;
				}
				if ('relative' == positioning) dest += $curPx;
			}
			var $newItem;
			if ('item'==type) {				
				$newItem = n;
				if ('relative' == positioning) $newItem += $curItem; 
				if ($newItem >= $items.length) $newItem = $items.length - 1;
				if ($newItem < 0) $newItem = 0;
				
				if ($newItem !== $curItem) {
					$curItem = $newItem;
					$items.removeClass('selected').eq($curItem).addClass('selected');
					if (settings.onSelect) settings.onSelect();
				}
				var lim1 = itemProp($curItem,'offset') - itemProp(0,'offset'),
					lim2 = lim1 + itemProp($curItem,'outersize') - $s.width() ;

				if ($curPx < lim2 ) dest = lim2;
				if ($curPx > lim1) dest = lim1;

			}

			if ( dest >= limit ) dest = limit;
			if ( dest < 0 ) dest = 0;
			var anVars = {};
		
			if ( nolink && inverted != settings.inverted ) {
				dest = limit - dest;
			}
			
			if (!speed && speed!==0) speed = settings.speed || 0;
			var prop = 'margin';
			if (settings.virtualitems) { prop = 'fakeProp' }
			
			anVars[prop+$offset.charAt(0).toUpperCase() + $offset.substr(1)] = -dest+"px";

			var anSpeed = parseInt(Math.abs(dest-$curPx)*speed);

			if (nolink) anSpeed = speed;

			var anOpt = { duration: anSpeed, easing: 'linear', complete: callback }
			
			if (settings.autoSelectItem && 'item'!=type) anOpt.step = function(now){
				var step = (limit+1)/$items.length;
				$newItem = parseInt(-now/step);
				if ($newItem !== $curItem) {
					$curItem = $newItem;
					$items.removeClass('selected').eq($curItem).addClass('selected');
					if (settings.onSelect) settings.onSelect();
				}
			}
			$content.animate(anVars, anOpt );

			
			if (!nolink) $(linked).each(function(){
				this.mv( '=' + dest/limit + 'perc', anSpeed, null, true, settings.inverted );
			});
		}
		

		var linked = [];
		this.link = function( scroller, nolink ) { // link scrollers percentually
			linked.push( scroller );
			if (!nolink) {
				scroller.link( this, true );
			}
		}

		//handling classes
		//$commands + $items + $s
		var $objs = $items.add($s);
		$(settings.cmds).each(function(){ $objs = $objs.add( this.obj ); });
		
		$objs	.mouseenter(function(event){ $(this).addClass('hover'); })
				.mouseleave(function(){ $(this).removeClass('hover'); })
				.mousedown(function(){ $(this).addClass('click'); });
		$(document).mouseup(function(){ $objs.removeClass('click'); });
		
		//binding events
		if ('scrollbar' == settings.type && settings.mousedown) {
			$s.mousedown(function(event){ 
				var center = itemProp(0, 'offset') + $items.width()/2;
				var delta = center - (($offset == 'top') ? event.pageY : event.pageX);
				self.mv( delta , settings.speed )
			})
		}
		$(settings.cmds).each(function(){
			var cmd = this;
			$(cmd.obj).bind( cmd.event, function(event){
				var act = cmd.action;
				if (Object.prototype.toString.call(act) === "[object Function]") act = act(event);
				( settings.type=='scrollbar' && /item/.test(cmd.action) ? $(linked).get(0) : self)
					.mv( act, cmd.speed );
			});
		})
		if (settings.cmds.length) {
			$(document).mouseup(function(){ self.stopScroll(); });
		}
		
		//onhover handler
		if (settings.over) {
			var center = ($offset == 'top') ? $s.height()/2 + $s.offset().top : $s.width()/2 + $s.offset().left;
			$s.mousemove(function(event){
				var delta = center - (($offset == 'top') ? event.pageY : event.pageX);
				if (settings.inverted) delta = -delta;
				if (delta) self.mv( '=' + (delta < 0) + 'perc', Math.abs(center/delta*settings.speed) );
			}).mouseleave(function(){
				self.stopScroll();
			});
		}

		//drag & drop handler
		if (settings.drag && jQuery.event.special.drag) {
			var oldev,passClick;
			$s.drag(function(event){
				var ev = event['offset'+('top'==$offset?'Y':'X')];
				if (oldev !== null) this.mv( oldev-ev, 0 );
				oldev = ev;
				passClick = false;
			}).mousedown(function(event){
				oldev = null;
				passClick = true;
			}).click(function(){	//disable click when dragging (expecially for anchors)
				return passClick;
			})
		}
		
		//mousewheel handler
		if (settings.mousewheel && jQuery.event.special.mousewheel) {
			$s.mousewheel(function(event, delta) {
				if (settings.inverted) delta = -delta;
				self.mv( (delta>0 ? '-':'') + settings.mousewheel );
				return false;
			});
		}
		
		//start: todo - general linked sync
		if ('scrollbar' == settings.type) {
			this.mv('=1perc',0);
		}
	});
};
