function pc3Scroller( scrollerId, scrollerData, scrollerBars, scrollerArrows ){

 	Object.extend(Event, {
		wheel:function (event){
			if (!event) event = window.event;
			var delta = 0;
			var direction = 'vertical';
			if ( event.wheelDelta ) {
				delta = -(event.wheelDelta/10);
				if ( delta < 1 && delta >= 0 ){
					delta = 1;
				} else if ( delta < 0 && delta >= -1 ){
					delta = -1;
				}
				if ( event.wheelDeltaY == 0 ) direction = 'horizontal';
			} else if (event.detail) {
				delta = event.detail;
				if ( event.VERTICAL_AXIS != event.axis )  direction = 'horizontal';
			}
			
			return {
				direction:direction,
				delta:delta.round()
			}
		}
	});
	
	var self = this;
	this.id = scrollerId;
	this.frame = $( this.id );
	this.currentState = 'mouseOut';
	
	this.bars = $H({});
	this.arrows = $H({});

	this.handleElements = new Array();
	
	this.init = function(){
		$H(scrollerData).each(function(attribute){ self[attribute.key] = attribute.value; });
		this.content = new pc3ScrollerContent(this, this.frame.childElements(), scrollerData);		
		scrollerBars.each(function(data){
			var element = $(self.id+"bar"+data.direction);
			if ( !element ) return;
			self.bars.set(data.direction, new pc3ScrollerBar(self, element, data));
		});

		scrollerArrows.each(function(data){
			var element = $(self.id+"arrow"+data.direction);
			if ( !element ) return;
			self.arrows.set(data.direction, new pc3ScrollerArrow(self, element, data, scrollerData.maxspeed));
		});

		if ( this.activateMouseWheel ){
			var eventType = 'mousewheel';
			if ( Prototype.Browser.Gecko ) eventType = 'DOMMouseScroll';
			Event.observe(this.frame, eventType, this.scrollWheel.bindAsEventListener(this));
		}
		
		this.update();
	}

	
	this.scrollTo = function(position, elementId, alignment){ this.content.scrollTo(position, elementId, alignment); }

	this.scrollWheel = function(event){
		var wheel = Event.wheel(event);
		this.scrollByPixel((wheel.direction=='horizontal'?wheel.delta:null),(wheel.direction!='horizontal'?wheel.delta:null));
		Event.stop(event);
	}
	
	this.hideScrollBars = function(){
		this.bars.each(function(bar){bar.value.hide();});
	}
	
	
	this.initDisplayHandler = function(element){ this.handleElements.push(element); }


	this.showingArrow = function(direction){ if ( this.bars.get(direction) ) this.bars.get(direction).showingArrow(); }

	this.hidingArrow = function(direction){ if ( this.bars.get(direction) ) this.bars.get(direction).hidingArrow(); }
	

	this.scroll = function(x, y, source){
		switch( source ){
			case 'bar':
				this.content.scroll(x, y, (Prototype.Browser.IE?'pixel':'')); //performance for IE
				break;
			case 'content':
				this.bars.each(function(bar){ bar.value.scroll(x, y, source); });
				break;
		}				
	}

	this.scrollByUnit = function(direction){ this.content.scrollByUnit(direction); }

	this.scrollByPixel = function(x, y){ this.content.scrollByPixel(x, y); }

	this.scrollable = function( direction ){
		if ( direction == 'horizontal' && this.content.width && (this.content.width > this.width) ) return true;
		if ( direction == 'vertical' && this.content.height && (this.content.height > this.height) ) return true;
		return false;
	}

	this.handleMouseMove = function(event){
		this.bars.each(function(bar){bar.value.handleMouseMove(event);});
		if ( !this.handleElements.size() ) return;
		if ( this.currentState == 'mouseOut' && pc3Widget.mouseIsInside(event, this.frame) ){
			this.currentState = 'mouseIn';
			this.handleElements.each(function(element){ element.show(); });
		} else if ( this.currentState == 'mouseIn' && !pc3Widget.mouseIsInside(event, this.frame) ){
			var scrolling = false;
			this.bars.each(function(bar){ if ( bar.value.scrolling ) scrolling = true;});
			if ( scrolling ) return;
			this.currentState = 'mouseOut';
			this.handleElements.each(function(element){ element.hide(); });
		}
	}

	this.handleMouseUp = function(event){
		this.bars.each(function(bar){bar.value.handleMouseUp(event);});
		this.arrows.each(function(arrow){arrow.value.handleMouseUp(event);});
	}
	
	this.update = function(){
		this.width = this.frame.getWidth();
		this.height = this.frame.getHeight();
		this.content.update();
		this.bars.each(function(bar){bar.value.update();});
		this.arrows.each(function(arrow){arrow.value.update();});
	}
	
	this.init();
}


/**
*  Content
**/
function pc3ScrollerContent( scroller, childs, data ){
	var self = this;
	this.scroller = scroller;
	this.type = 'content';
	this.dragging = false;
	this.startDragPosition = {x:0,y:0};
	this.scrollElements = new Array();
	
	this.scrollBy = (data.scrollBy == undefined?'pixel':data.scrollBy);
	this.animateScroll = data.animateScroll;
	this.animateDuration = (data.animateDuration?parseFloat(data.animateDuration*1000):300);
	this.tween = '';
	this.cumulativePixels = {x:0,y:0};
	this.lastPosition = {x:0,y:0};
	
	this.init = function(){
		this.content = new Element('div').setStyle({position:'absolute'});
		this.content.addClassName('clearfix');
		childs.each(function(item){  self.content.appendChild(item); });
		this.scroller.frame.appendChild(this.content);

		// Get all Elements with Classname
		if( data.scrollByElementClassName && this.scrollBy == 'elements' ){
			this.content.select('.'+data.scrollByElementClassName).each(function(item){  self.scrollElements.push(item); });
		}
		
		if ( this.paning ) this.initPanning();
	}


	this.scrollByElements = function(){ 
		if ( this.scrollBy == 'elements' && this.scrollElements.size() > 0 ) return true;
		return false;
	}

	this.scrollByPixel = function(x, y){
		var before = {
			left:this.position.left,
			top:this.position.top
		};
		
		var newX = null;
		var newY = null;
	
		if ( x != null ){
			this.cumulativePixels.x = this.cumulativePixels.x + x;
			newX = -this.position.left + this.cumulativePixels.x;
		}
		
		if ( y != null ){
			this.cumulativePixels.y = this.cumulativePixels.y + y;
			newY = -this.position.top + this.cumulativePixels.y;
		}
		
		this.scroll(newX, newY, (Prototype.Browser.IE?'pixel':'')); //performance for IE
		this.scroller.scroll(-this.position.left, -this.position.top, 'content');
		

		if ( x != null && this.position.left != before.left || (x < 0 && this.position.left == 0) || (x > 0 && -this.position.left == this.maxScroll.x) ) this.cumulativePixels.x = 0;
		if ( y != null && this.position.top != before.top || (y < 0 && this.position.top == 0) || (y > 0 && -this.position.top == this.maxScroll.y) ) this.cumulativePixels.y = 0;
	}
	
	
	this.scroll = function(x, y, scrollBy){
		if ( !scrollBy ) scrollBy = this.scrollBy;
		if ( x != null ){
			if ( !this.scrollable.x ) x = 0;
			if ( x < 0 ) x = 0;

			if ( x > 0 && scrollBy != 'pixel' ){
				var newX = 0;
				if ( scrollBy == 'page' ){
					newX = ((x / this.scroller.width).round()) * this.scroller.width;
				} else if ( this.scrollByElements() ){
					for (var i=0,length=this.scrollElements.size(); i<length; ++i){
						newX = this.scrollElements[i].positionedOffset().left;
						if ( parseInt(newX + (this.scrollElements[i].getWidth()/2)) >= x ) break;
					}
				}
				if ( newX == 0 && x > newX && x > parseInt((this.width - this.scroller.width)/2) ) newX = this.maxScroll.x;
				x = newX;
			}
			if ( this.scrollable.x && x > this.maxScroll.x ) x = this.maxScroll.x;
			this.position.left = -x;
		}

		if ( y != null ){
			if ( !this.scrollable.y ) y = 0;
			if ( y < 0 ) y = 0;
			
			if ( y > 0 && scrollBy != 'pixel' ){
				var newY = 0;
				if ( scrollBy == 'page' ){
					newY = ((y / this.scroller.height).round()) * this.scroller.height;
				} else if ( this.scrollByElements() ){
					for (var i=0,length=this.scrollElements.size(); i<length; ++i){
						newY = this.scrollElements[i].positionedOffset().top;
						if ( parseInt(newY + (this.scrollElements[i].getHeight()/2)) >= y ) break;
					}
				}
				if ( newY == 0 && y > newY && y > parseInt((this.height - this.scroller.height)/2) ) newY = this.maxScroll.y;
				y = newY;
			}
			if ( this.scrollable.y && y > this.maxScroll.y ) y = this.maxScroll.y;
			this.position.top = -y;
		}
		
		this.content.setStyle({left:this.position.left+'px', top:this.position.top+'px'});
	}
	
	this.scrollTo = function( position, elementId, alignment ){
		var elementPosition = {
			left:0,
			left:0
		}
		var offsetX = 0;
		var offsetY = 0;
		
		if ( elementId ){
			var element = this.content.select('#'+elementId).first();
			if ( !element ) return;
			elementPosition = element.positionedOffset();
			var width = element.getWidth();
			var height = element.getHeight();
			alignment = (alignment==undefined?'topleft':alignment);
			
			switch( alignment ){
				case "topcenter":
					offsetX = ((this.scroller.width - width)/2).round();
					break;
				case "topright":
					offsetX = this.scroller.width - width;
					Break
				case "centerleft":
					offsetY = ((this.scroller.height - height)/2).round();
					break;
				case "centercenter":
					offsetX = ((this.scroller.width - width)/2).round();
					offsetY = ((this.scroller.height - height)/2).round();
					break;
				case "centerright":
					offsetX = this.scroller.width - width;
					offsetY = ((this.scroller.height - height)/2).round();
					break;
				case "bottomleft":
					offsetY = this.scroller.height - height;
					break;
				case "bottomcenter":	
					offsetX = ((this.scroller.width - width)/2).round();
					offsetY = this.scroller.height - height;
					break;
				case "bottomright":
					offsetX = this.scroller.width - width;
					offsetY = this.scroller.height - height;
					break;
			}
		}

		this.endPosition = {
			x:(elementPosition.left-offsetX+position.x),
			y:(elementPosition.top-offsetY+position.y)
		}
		
		if ( this.animateScroll ){
			if ( this.tween ) delete this.tween;
			this.startPosition = {
				x:-this.position.left,
				y:-this.position.top
			};
			this.tween = new pc3Tween('scroll', 1, 100, this.animateDuration, this.animateScroll, this.onTween.bind(self), this.onTweenComplete.bind(self));
		} else {
			this.scroll(this.endPosition.x, this.endPosition.y, '');
			this.scroller.scroll(-this.position.left, -this.position.top, 'content');
		}
	}
	
	this.scrollByUnit = function(direction){
		console.log(direction);
	}

	this.onTween = function( effect ){
		var left = this.startPosition.x - (((this.startPosition.x - this.endPosition.x) * effect.value) / 100).round();
		var top = this.startPosition.y - (((this.startPosition.y - this.endPosition.y) * effect.value) / 100).round();
		this.scroll(left, top, 'pixel');
		this.scroller.scroll(-this.position.left, -this.position.top, 'content');
	}
	
	this.onTweenComplete = function( effect ){ this.tween = ''; }
		
	this.update = function(){
		this.width = this.content.getWidth();
		this.height = this.content.getHeight();
		this.position = this.content.positionedOffset();
		this.scrollable = {x:(this.width<=this.scroller.width?false:true),y:(this.height<=this.scroller.height?false:true)};
		this.maxScroll = {x:(this.scrollable.x?this.width-this.scroller.width:0),y:(this.scrollable.y?this.height-this.scroller.height:0)};
	}
	
	this.init();
}



/**
*  SCROLLBARS
**/
function pc3ScrollerBar( scroller, barElement, data ){
	var self = this;
	this.scroller = scroller;
	this.bar = barElement;
	this.direction = data.direction;
	this.display = (data.display?data.display:'always');
	this.displayOnlyWhenNeeded = (data.displayOnlyWhenNeeded?true:false);
	this.scrolling = false;
	this.minHandleSize = 10;
	this.opacity = pc3Widget.getOpacity(this.bar) * 100;
	this.tweens = $H({});
	
	this.init = function(){
		this.handle = new Element('div', { 'class': 'handle'+this.direction }).setStyle({position:'absolute', left:'0px', top:'0px', pointer:'pointer'});
		this.bar.appendChild( this.handle );
		this.bar.setStyle({pointer:'pointer'});
		if( this.bar.up(0) == this.scroller.content.content ) this.scroller.frame.appendChild( this.bar );
		this.handleOpacity = pc3Widget.getOpacity(this.handle) * 100;
		
		Event.observe(this.handle, 'mousedown', this.startScrolling.bindAsEventListener(this));		
		Event.observe(this.bar, 'click', this.scrollPage.bindAsEventListener(this));		

		if ( this.display != "always" ){
			this.bar.setStyle({opacity: 0});
			this.handle.setStyle({opacity: 0});
		}

		switch( this.display ){
			case "onlyWhenOnBar":
				Event.observe( this.bar, 'mouseover', this.show.bindAsEventListener(this));
				Event.observe( this.bar, 'mouseout', this.hide.bindAsEventListener(this));
				break;
			case "onlyWhenInArea":
				this.scroller.initDisplayHandler(this);
				break;
		}

	}

	
	this.startScrolling = function(event){
		event.stop();
		this.scrolling = true;
		this.startMousePosition = {
			x:pc3Widget.mousePosition.x,
			y:pc3Widget.mousePosition.y
		}
		
		this.startHandlePosition = {
			left:this.position.left,
			top:this.position.top
		}
		
	}

	this.handleMouseMove = function(event){
		if ( !this.scrolling ) return;
		event.stop();
		if ( this.direction == 'horizontal' ){
			this.scroll((this.startHandlePosition.left+(pc3Widget.mousePosition.x - this.startMousePosition.x)), null, false);
			this.scroller.scroll((this.position.left*this.factor).round(), null, 'bar');		
		} else {
			this.scroll(null, (this.startHandlePosition.top + (pc3Widget.mousePosition.y - this.startMousePosition.y)), false);
			this.scroller.scroll(null, (this.position.top*this.factor).round(), 'bar');		
		}
	}

	this.handleMouseUp = function(event){
		var element = Event.element(event);
		if ( this.scrolling && this.display != 'always' && (element != this.bar && element != this.handle) ){
			this.scrolling = false;
			this.hide('');
		}
		this.scrolling = false;
	}

	this.scrollPage = function(event){
		if ( this.direction == 'horizontal' ){
			if ( event.pointerX() < this.handle.cumulativeOffset().left ) this.scroll((this.position.left - this.handle.getWidth()), null, false);
			if ( event.pointerX() > (this.handle.cumulativeOffset().left + this.handle.getWidth()) ) this.scroll((this.position.left + this.handle.getWidth()), null, false);
			this.scroller.scroll((this.position.left*this.factor).round(), null, 'bar');		
		} else {
			if ( event.pointerY() < this.handle.cumulativeOffset().top ) this.scroll(null, (this.position.top - this.handle.getHeight()), false);
			if ( event.pointerY() > (this.handle.cumulativeOffset().top + this.handle.getHeight()) ) this.scroll(null, (this.position.top + this.handle.getHeight()), false);
			this.scroller.scroll(null, (this.position.top*this.factor).round(), 'bar');		
		}
	}

	this.scroll = function(x, y, content){
		if ( content ){
			if ( x != null ) x = x/this.factor;
			if ( y != null ) y = y/this.factor;
		}
		if ( this.direction == 'horizontal' ){
			if ( x < 0 ) x = 0;
			if ( x > this.width - this.handle.getWidth() ) x = this.width - this.handle.getWidth();
			this.position.left = x;
			this.handle.setStyle({left:this.position.left+'px'});
		} else {
			if ( y < 0 ) y = 0;
			if ( y > this.height - this.handle.getHeight() ) y = this.height - this.handle.getHeight();
			this.position.top = y;

			this.handle.setStyle({top:this.position.top+'px'});
		}
	}


	this.show = function(event){
		if ( event ) event.stop();
		if ( this.tweens.size() ) this.tweens.each(function(tween){ self.tweens.unset(tween.key) });
		this.tweens.set('bar', new pc3Tween('opacity', (pc3Widget.getOpacity(self.bar) * 100), self.opacity, 200, 'EaseInQuad', this.fadeInOutBar.bind(self), function(){  }));
		this.tweens.set('handle', new pc3Tween('opacity', (pc3Widget.getOpacity(self.handle) * 100), self.handleOpacity, 200, 'EaseInQuad', this.fadeInOutHandle.bind(self), function(){  }));
	}
	
	this.hide = function(event){
		if ( event ){
			event.stop();
			if ( pc3Widget.mouseIsInside(event, this.bar) ) return;
			if ( this.scrolling ) return;
		}
		if ( this.tweens.size() ) this.tweens.each(function(tween){ self.tweens.unset(tween.key) });
		this.tweens.set('bar', new pc3Tween('opacity', (pc3Widget.getOpacity(self.bar) * 100), 0, 200, 'EaseInQuad', this.fadeInOutBar.bind(self), function(){  }));
		this.tweens.set('handle', new pc3Tween('opacity', (pc3Widget.getOpacity(self.handle) * 100), 0, 200, 'EaseInQuad', this.fadeInOutHandle.bind(self), function(){  }));
	}
	
	this.fadeInOutBar = function(tween){
		this.bar.setStyle({ opacity : (tween.value/100) });
	}
	this.fadeInOutHandle = function(tween){ this.handle.setStyle({ opacity : (tween.value/100) }); }

	this.showingArrow = function(){ if ( this.display == 'onlyWhenOnArrows' ) this.show(''); }

	this.hidingArrow = function(){ if ( this.display == 'onlyWhenOnArrows' ) this.hide(''); }

	
	this.update = function(){
		this.width = this.bar.getWidth();
		this.height = this.bar.getHeight();
		this.position = this.handle.positionedOffset();
		var lenght = 0;
		
		if ( this.direction == 'horizontal' && this.scroller.scrollable(this.direction) ) lenght = parseInt((this.bar.getWidth() * this.scroller.width) / this.scroller.content.width);
		if ( this.direction == 'vertical' && this.scroller.scrollable(this.direction) ) lenght = parseInt((this.bar.getHeight() * this.scroller.height) / this.scroller.content.height);
		if ( lenght > 0 ){
			if ( lenght < this.minHandleSize ) lenght = this.minHandleSize;
			if ( this.direction == 'horizontal' ) this.handle.setStyle({width:lenght+'px',height:this.bar.getHeight()+'px'});
			else this.handle.setStyle({width:this.bar.getWidth()+'px', height:lenght+'px'});
			this.handle.show();
			this.bar.show();
			if ( this.direction == 'horizontal' ) this.factor = (this.scroller.content.width - this.scroller.width) / (this.bar.getWidth()-lenght);
			else this.factor = (this.scroller.content.height - this.scroller.height) / (this.bar.getHeight()-lenght);
		} else {
			this.factor = 1;
			this.handle.hide();
			if ( this.displayOnlyWhenNeeded ) this.bar.hide();
		}
	}
	
	this.init();
}




/**
*  ARROWS
**/

function pc3ScrollerArrow( scroller, arrowElement, data, maxSpeed ){
	var self = this;
	this.scroller = scroller;
	this.arrow = arrowElement;
	this.direction = data.direction;
	this.scrollDirection = (this.direction == 'left' || this.direction == 'right' ? 'horizontal' : 'vertical' );
	this.display = (data.display?data.display:'always');
	this.displayOnlyWhenNeeded = (data.displayOnlyWhenNeeded?true:false);
	this.scroll = (data.scroll?data.scroll:'continuously');
	this.trigger = (data.trigger?data.trigger:'mousedown');
	this.maxSpeed = parseInt((maxSpeed?maxSpeed:40));
	this.scrolling = false;
	this.nextScroll = '';
	this.pastTime = 0;
	this.opacity = pc3Widget.getOpacity(this.arrow) * 100;
	this.tween = '';
		
	this.init = function(){
		if ( this.scroll == 'continuously' ){
			Event.observe(this.arrow, this.trigger, this.startScrolling.bindAsEventListener(this));
			if ( this.trigger == 'mousedown' ){
				Event.observe(this.arrow, 'mouseout', this.haltScrolling.bindAsEventListener(this));
				Event.observe(this.arrow, 'mouseover', this.continueScrolling.bindAsEventListener(this));
			}
			Event.observe(this.arrow, (this.trigger=='mousedown'?'mouseup':'mouseout'), this.stopScrolling.bindAsEventListener(this));
		} else {
			Event.observe(this.arrow, 'click', this.scrollByUnit.bindAsEventListener(this));
		}
		
		if( this.display != "always" ) this.arrow.setStyle({opacity: 0});
		
		switch( this.display ){
			case "onlyWhenOnArrow":
				Event.observe( this.arrow, 'mouseover', this.show.bindAsEventListener(this));
				Event.observe( this.arrow, 'mouseout', this.hide.bindAsEventListener(this));
				break;
			case "onlyWhenInArea":
				this.scroller.initDisplayHandler(this);
				break;
		}
	}
	
	this.startScrolling = function(event){
		if ( event ) event.stop();
		this.pastTime = 0;
		this.scrolling = true;
		this.nextScroll = this.scrollByPixel.bind(this).delay(0);
	}
	
	this.haltScrolling = function(event){
		if ( event ) event.stop();
		this.scrolling = false;
	}
	
	this.continueScrolling = function(event){
		if ( event ) event.stop();
		this.scrolling = true;
	}
	
	this.stopScrolling = function(event){
		if ( event ) event.stop();
		if ( this.nextScroll ) window.clearTimeout(this.nextScroll);
		this.scrolling = false;
	}

	this.handleMouseUp = function(event){ this.stopScrolling(''); }

	this.scrollByPixel = function(){
		if ( this.scrolling ){
			var pixels = 5 + this.pastTime.round();
			if ( pixels > this.maxSpeed ) pixels = this.maxSpeed;
			var x = (this.direction=='left'?-pixels:(this.direction=='right'?pixels:null))
			var y = (this.direction=='up'?-pixels:(this.direction=='down'?pixels:null))
			this.scroller.scrollByPixel(x,y);
			this.pastTime = this.pastTime + 0.16;
		}
		this.nextScroll = this.scrollByPixel.bind(this).delay(0.04);
	}
	
	this.scrollByUnit = function(){ this.scroller.scrollByUnit(this.direction); }

	this.show = function(event){
		if ( event ) event.stop();
		if ( this.tween ) this.tween = '';
		this.tween = new pc3Tween('opacity', (pc3Widget.getOpacity(self.arrow) * 100), self.opacity, 200, 'EaseInQuad', this.fadeInOut.bind(self), function(){  });
		if ( event ) this.scroller.showingArrow(this.scrollDirection);
	}
	
	this.hide = function(event){
		if ( event ) event.stop();
		if ( this.tween ) this.tween = '';
		this.tween = new pc3Tween('opacity', (pc3Widget.getOpacity(self.arrow) * 100), 0, 200, 'EaseInQuad', this.fadeInOut.bind(self), function(){  });
		if ( event ) this.scroller.hidingArrow(this.scrollDirection);
	}
	
	this.fadeInOut = function(tween){ this.arrow.setStyle({ opacity : (tween.value/100) }); }
	
	this.update = function(){
		var display = false;
		if ( this.scroller.scrollable(this.scrollDirection) ) display = true;
		if ( !display && this.displayOnlyWhenNeeded ) this.arrow.hide();
		else this.arrow.show();
	}

	this.init();
}