/**
 * Scrollable Control makes an element scrollable by wrapping it in a viewport.
 *
 * ChangeLog:
 * v1.1, 2008-01-04, JeanV:
 *   - Added a parameter that makes it possible to change the scrollers behaviour. It can now continuousely scroll onMouseDown or scroll onClick
 *   - Added a parameter that makes it possible to specify scrolling speed
 *   - Added the predefined scrolling speeds for smooth and hard scrolling
 * 
 * @author Jean-Bernard Valentaten
 * @copyright Phase 4 Commucations GmbH 2007
 */

if (!Control) var Control = {};
Control.ScrollbarHorizontal = Class.create();
Control.ScrollbarVertical = Class.create();
Control.Scrollable = Class.create();

/**
 * Constants
 */
var SMOOTH_SCROLLING_SPEED = 0.02;
var HARD_SCROLLING_SPEED = 0.1;

/**
 * options:
 *  arrows{left, right}: The sources for the left and right arrows (optional, if not passed, arrows are not displayed)
 *  classes{handle, scrollbar, track}: The names of the css classes used to customize the look&feel of scrollbar
 *  disabled: true or false (default), if set to true, won't do anything (except look cool *g*)
 *  height: The height of scrollbar (optional, as you might want to define this in css)
 *  width: The overall width of the scrollbar including the arrows (optional, as you might want to define this in css)
 *  scrolling: onMouseDown, onClick (default)
 *  speed: The scrolling speed (0.01 - 0.99). Defaults to SMOOTH_SCROLLING_SPEED if scrolling onMouseDown or HARD_SCROLLING_SPEED otherwise
 * 
 * callbacks:
 *  onChange: The callback function that will be called when finished sliding
 *  onSlide: The callback function that will be called when dragging the handle
 */
Control.ScrollbarHorizontal.prototype = {
	/*
	 * inherited methods
	 */
	initialize: function(element, options) {
		var scrollbarHorizontal = this;
		
		this.element = $(element);
		this.options = options || {};

		//init & default options
		this.arrows = this.options.arrows || {};
		this.classes = this.options.classes || {};
		this.disabled = this.options.disabled || false;
		this.height = this.options.height || null;
		this.width = this.options.width || null;
		this.scrolling = this.options.scrolling || 'onClick';
		this.speed = this.options.speed || (this.scolling=='onClick' ? HARD_SCROLLING_SPEED : SMOOTH_SCROLLING_SPEED);
		
		//init & default callbacks
		this.onChange = this.options.onChange || null;
		this.onSlide = this.options.onSlide || null;
		
		//init datamembers
		this.slideExecuter = null; //needed for mousedown/-up functionality
		this.slider = null;
		this.elements = {
			arrows: {
				left: null,
				right: null
			},
			handle: null,
			track: null
		};
		this.events = {
			leftArrow: this._leftArrowClicked.bindAsEventListener(this),
			rightArrow: this._rightArrowClicked.bindAsEventListener(this)
		}
		
		//set up the DOM-elements
		this._setDOMElements();
		this._setInteractivity();
		
		//that's all folks
		this.initialized = true;
	},
	dispose: function() {
		Event.stopObserving(this.elements.arrows.left, 'click', this.events.leftArrow);
		Event.stopObserving(this.elements.arrows.right, 'click', this.events.rightArrow);
	},
	
	/*
	 * private methods
	 */
	
	/**
	 * Calculates the width that our track is going to need
	 * @return int The width of track in pixels
	 */
	_getTrackWidth: function() {
		var tWidth = this.width;
		
		if (this.elements.arrows.left) {
			tWidth -= this.elements.arrows.left.width;
		}
		if (this.elements.arrows.right) {
			tWidth -= this.elements.arrows.right.width;
		}
		
		//return at least 0
		return (tWidth < 0 ? 0 : tWidth);
	},
	
	/**
	 * Fired when left arrow is clicked.
	 * Decrements current value by 0.1
	 * @param Event event The event that was fired by click
	 */
	_leftArrowClicked: function(event) {
		var scrollbarHorizontal = this;

		if (this.scrolling == 'onClick' && event.type  == 'click') {
			this.setValueBy(-this.speed);
		} else if (this.scrolling == 'onMouseDown') {
			switch (event.type) {
				case 'mousedown':
					this.slideExecuter = new PeriodicalExecuter(function() { scrollbarHorizontal.setValueBy(-scrollbarHorizontal.speed) }, 0.05);
					break;
				case 'mouseup':
				case 'mouseout':
					if (this.slideExecuter) this.slideExecuter.stop();
					break;

			}
		}

		Event.stop(event);
	},

	/**
	 * Fired when right arrow is clicked.
	 * Increments current value by 0.1
	 * @param Event event The event that was fired by click
	 */
	_rightArrowClicked: function(event) {
		var scrollbarHorizontal = this;

		if (this.scrolling == 'onClick' && event.type  == 'click') {
			this.setValueBy(this.speed);
		} else if (this.scrolling == 'onMouseDown') {
			switch (event.type) {
				case 'mousedown':
					this.slideExecuter = new PeriodicalExecuter(function() { scrollbarHorizontal.setValueBy(scrollbarHorizontal.speed) }, 0.05);
					break;
				case 'mouseup':
				case 'mouseout':
					if (this.slideExecuter) this.slideExecuter.stop();
					break;

			}
		}

		Event.stop(event);
	},
	
	/**
	 * Sets up the DOM elements that we need to display a scrollbar.
	 * As a sideeffect the following datamembers are set:
	 * - elements.arrows.* (if requested)
	 * - elements.handle
	 * - elements.track
	 */
	_setDOMElements: function() {
		//create the nodes we'll need
		this.elements.handle = document.createElement('div');
		this.elements.track = document.createElement('div');
		
		//create an left-arrow if requested
		if (this.arrows.left) {
			this.elements.arrows.left = document.createElement('img');
			this.elements.arrows.left.setAttribute('src', this.arrows.left);
			Element.setStyle(this.elements.arrows.left, {cssFloat: 'left'}); //For Mozilla
			Element.setStyle(this.elements.arrows.left, {cursor: 'pointer'});
		}
		
		//create a right-arrow if requested
		if (this.arrows.right) {
			this.elements.arrows.right = document.createElement('img');
			this.elements.arrows.right.setAttribute('src', this.arrows.right);
			Element.setStyle(this.elements.arrows.right, {cssFloat: 'right'}); //For Mozilla
			Element.setStyle(this.elements.arrows.right, {cursor: 'pointer'});
		}
		
		//assign classes to handle and track
		Element.addClassName(this.elements.handle, this.classes.handle);
		Element.addClassName(this.elements.track, this.classes.track);
		Element.setStyle(this.elements.track, { cssFloat: 'left'}); //For Mozilla
		
		//set scrollbars class or default styles
		if (this.classes.scrollbar) {
			Element.addClassName(this.element, this.classes.scrollbar);
		} else {
			Element.setStyle(this.element, {
				border: '1px solid black',
				margin: '0px',
				padding: '0px'
			});
		}
		
		//now we put the puzzle together
		this.elements.track.appendChild(this.elements.handle);
		if (this.arrows.left) this.element.appendChild(this.elements.arrows.left);
		this.element.appendChild(this.elements.track);
		if (this.arrows.right) this.element.appendChild(this.elements.arrows.right);
		
		//always resize to ensure functionality of slider
		if (this.height==null) this.height = Element.getHeight(this.element);
		if (this.width==null) this.width = Element.getWidth(this.element);
		this.resize(this.height, this.width);
	},
	
	/**
	 * Sets up the controls, effects and events that we need for interactivity
	 * As a sideeffect, the following datamembers are set: 
	 * - slider
	 * - scroll
	 * - events.leftArrow
	 * - events.rightArrow
	 */
	_setInteractivity: function() {
		scrollbarHorizontal = this;
		
		this.slider = new Control.Slider(
			this.elements.handle,
			this.elements.track,
			{
				axis: 'horizontal',
				onSlide: scrollbarHorizontal.onSlide,
				onChange: scrollbarHorizontal.onChange
			}
		);
		
		//add event-handlers for arrows, if requested
		if  (this.arrows.left) {
			if (this.scrolling=='onClick') {
				Event.observe(
					scrollbarHorizontal.elements.arrows.left,
					'click',
					this.events.leftArrow
				);
			} else {
				Event.observe(
					scrollbarHorizontal.elements.arrows.left,
					'mousedown',
					this.events.leftArrow
				);
				Event.observe(
					scrollbarHorizontal.elements.arrows.left,
					'mouseup',
					this.events.leftArrow
				);
				Event.observe(
					scrollbarHorizontal.elements.arrows.left,
					'mouseout',
					this.events.leftArrow
				);
			}

		}
		if (this.arrows.right) {
			if (this.scrolling=='onClick') {
				Event.observe(
					scrollbarHorizontal.elements.arrows.right,
					'click',
					this.events.rightArrow
				);
			} else {
				Event.observe(
					scrollbarHorizontal.elements.arrows.right,
					'mousedown',
					this.events.rightArrow
				);
				Event.observe(
					scrollbarHorizontal.elements.arrows.right,
					'mouseup',
					this.events.rightArrow
				);
				Event.observe(
					scrollbarHorizontal.elements.arrows.right,
					'mouseout',
					this.events.rightArrow
				);
			}

		}
	},
	
	/*
	 * public methods
	 */
	
	/**
	 * Resizes the control
	 * @param int height The desired height of scrollbar in pixels
	 * @param int width The desired width of scrollbar in pixels
	 */
	resize: function(height, width) {
		//set datamembers
		this.height = height;
		this.width = width;
		
		//and resize, track first, then scrollbar
		Element.setStyle(this.elements.track, {
			height: this.height + 'px',
			width: this._getTrackWidth() + 'px'
		});
		Element.setStyle(this.element, {
			height: this.height + 'px',
			width: this.width + 'px'
		});
	},
	
	/**
	 * Disables the scrollbar
	 */
	setDisabled: function() {
		this.disabled = true;
		if (this.slider) this.slider.setDisabled();
	},
	
	/**
	 * Enables the scrollbar
	 */
	setEnabled: function() {
		this.disabled = false;
		if (this.slider) this.slider.setEnabled();
	},
	
	/**
	 * Sets the value of slider by offset, if not disabled
	 * @param float value The value by which slider shall be changed
	 */
	setValueBy: function(value) {
		if (!this.disabled) {
			this.slider.setValueBy(value);
		}
	}
}

/**
 * options:
 *  arrows{up, down}: The sources for the left and right arrows (optional, if not passed, arrows are not displayed)
 *  classes{handle, scrollbar, track}: The names of the css classes used to customize the look&feel of scrollbar
 *  disabled: true or false (default), if set to true, won't do anything (except look cool *g*)
 *  height: The height of scrollbar (optional, as you might want to define this in css)
 *  width: The overall width of the scrollbar including the arrows (optional, as you might want to define this in css)
 *  scrolling: onMouseDown, onClick (default)
 *  speed: The scrolling speed (0.01 - 0.99). Defaults to SMOOTH_SCROLLING_SPEED if scrolling onMouseDown or HARD_SCROLLING_SPEED otherwise
 * 
 * callbacks:
 *  onChange: The callback function that will be called when finished sliding
 *  onSlide: The callback function that will be called when dragging the handle
 */
Control.ScrollbarVertical.prototype = {
	/*
	 * inherited methods
	 */
	initialize: function(element, options) {
		var scrollbarVertical = this;
		
		this.element = $(element);
		this.options = options || {};

		//init & default options
		this.arrows = this.options.arrows || {};
		this.classes = this.options.classes || {};
		this.disabled = this.options.disabled || false;
		this.height = this.options.height || null;
		this.width = this.options.width || null;
		this.scrolling = this.options.scrolling || 'onClick';
		this.speed = this.options.speed || (this.scrolling=='onClick' ? HARD_SCROLLING_SPEED : SMOOTH_SCROLLING_SPEED);
		
		//init & default callbacks
		this.onChange = this.options.onChange || null;
		this.onSlide = this.options.onSlide || null;
		
		//init datamembers
		this.slideExecuter = null;	//needed for mousedown/-up functionality
		this.slider = null;
		this.elements = {
			arrows: {
				up: null,
				down: null
			},
			handle: null,
			track: null
		};
		this.events = {
			upArrow: this._upArrowClicked.bindAsEventListener(this),
			downArrow: this._downArrowClicked.bindAsEventListener(this)
		}
		
		//set up the DOM-elements
		this._setDOMElements();
		this._setInteractivity();
		
		//that's all folks
		this.initialized = true;
	},
	dispose: function() {
		Event.stopObserving(this.elements.arrows.down, 'click', this.events.downArrow);
		Event.stopObserving(this.elements.arrows.up, 'click', this.events.upArrow);
	},
	
	/*
	 * private methods
	 */
	
	/**
	 * Fired when down-arrow is clicked.
	 * @param Event event The event that was fired by click
	 */
	_downArrowClicked: function(event) {
		var scrollbarVertical = this;

		if (this.scrolling == 'onClick' && event.type  == 'click') {
			this.setValueBy(this.speed);
		} else if (this.scrolling == 'onMouseDown') {
			switch (event.type) {
				case 'mousedown':
					this.slideExecuter = new PeriodicalExecuter(function() { scrollbarVertical.setValueBy(scrollbarVertical.speed) }, 0.05);
					break;
				case 'mouseup':
				case 'mouseout':
					if (this.slideExecuter) this.slideExecuter.stop();
					break;

			}
		}

		Event.stop(event);
	},
	
	/**
	 * Calculates the correct height of track
	 * @return int The height of the track in pixels
	 */
	_getTrackHeight: function() {
		var tHeight = this.height;
		
		if (this.elements.arrows.up) {
			tHeight -= this.elements.arrows.up.height;
		}
		if (this.elements.arrows.down) {
			tHeight -= this.elements.arrows.down.height;
		}
		
		//return at least 0
		return (tHeight<0 ? 0 : tHeight);
	},
	
	/**
	 * Sets up the DOM elements that we need to display a scrollbar.
	 * As a sideeffect the following datamembers are set:
	 * - elements.arrows.*
	 * - elements.handle
	 * - elements.track
	 */
	_setDOMElements: function() {
		//create the nodes we'll need
		this.elements.handle = document.createElement('div');
		this.elements.track = document.createElement('div');
		
		//create an left-arrow if requested
		if (this.arrows.up) {
			this.elements.arrows.up = document.createElement('img');
			this.elements.arrows.up.setAttribute('src', this.arrows.up);
			Element.setStyle(this.elements.arrows.up, {cursor: 'pointer'});
		}
		
		//create a right-arrow if requested
		if (this.arrows.down) {
			this.elements.arrows.down = document.createElement('img');
			this.elements.arrows.down.setAttribute('src', this.arrows.down);
			Element.setStyle(this.elements.arrows.down, {cursor: 'pointer'});
		}
		
		//assign classes to handle and track
		Element.addClassName(this.elements.handle, this.classes.handle);
		Element.addClassName(this.elements.track, this.classes.track);
		
		//set scrollbars class or default styles
		if (this.classes.scrollbar) {
			Element.addClassName(this.element, this.classes.scrollbar);
		} else {
			Element.setStyle(this.element, {
				border: '1px solid black',
				margin: '0px',
				padding: '0px'
			});
		}
		
		//now we put the puzzle together
		this.elements.track.appendChild(this.elements.handle);
		if (this.arrows.up) this.element.appendChild(this.elements.arrows.up);
		this.element.appendChild(this.elements.track);
		if (this.arrows.down) this.element.appendChild(this.elements.arrows.down);
		
		//always resize to enforce functionality of slider
		if (this.height==null) this.height = Element.getHeight(this.element);
		if (this.width==null) this.width = Element.getWidth(this.element);
		this.resize(this.height, this.width);
	},
	
	/**
	 * Sets up the controls, effects and events that we need fo interactivity
	 * As a sideeffect, the following datamembers are set: 
	 * - slider
	 * - scroll
	 * - events.upArrow
	 * - events.downArrow
	 */
	_setInteractivity: function() {
		scrollbarVertical = this;
		
		this.slider = new Control.Slider(
			scrollbarVertical.elements.handle,
			scrollbarVertical.elements.track,
			{
				axis: 'vertical',
				onSlide: scrollbarVertical.onSlide,
				onChange: scrollbarVertical.onChange
			}
		);
		
		//add event-handlers for arrows, if requested
		if (this.arrows.up) {
			if (this.scrolling=='onClick') {
				Event.observe(
					scrollbarVertical.elements.arrows.up,
					'click',
					this.events.upArrow
				);
			} else {
				Event.observe(
					scrollbarVertical.elements.arrows.up,
					'mousedown',
					this.events.upArrow
				);
				Event.observe(
					scrollbarVertical.elements.arrows.up,
					'mouseup',
					this.events.upArrow
				);
				Event.observe(
					scrollbarVertical.elements.arrows.up,
					'mouseout',
					this.events.upArrow
				);
			}
		}
		if (this.arrows.down) {
			if (this.scrolling=='onClick') {
				Event.observe(
					scrollbarVertical.elements.arrows.down,
					'click',
					this.events.downArrow
				);
			} else {
				Event.observe(
					scrollbarVertical.elements.arrows.down,
					'mousedown',
					this.events.downArrow
				);
				Event.observe(
					scrollbarVertical.elements.arrows.down,
					'mouseup',
					this.events.downArrow
				);
				Event.observe(
					scrollbarVertical.elements.arrows.down,
					'mouseout',
					this.events.downArrow
				);
			}
		}
	},
	
	/**
	 * Fired when up-arrow is clicked
	 * Decrements current value by 0.1
	 * @param Event event The event that was fired by click
	 */
	_upArrowClicked: function(event) {
		var scrollbarVertical = this;

		if (this.scrolling == 'onClick' && event.type  == 'click') {
			this.setValueBy(-this.speed);
		} else if (this.scrolling == 'onMouseDown') {
			switch (event.type) {
				case 'mousedown':
					this.slideExecuter = new PeriodicalExecuter(function() { scrollbarVertical.setValueBy(-scrollbarVertical.speed) }, 0.05);
					break;
				case 'mouseup':
				case 'mouseout':
					if (this.slideExecuter) this.slideExecuter.stop();
					break;

			}
		}

		Event.stop(event);
	},
	
	/*
	 * public methods
	 */
	
	/**
	 * Resizes the control
	 * @param int height The desired height of scrollbar in pixels
	 * @param int width The desired width of scrollbar in pixels
	 */
	resize: function(height, width) {
		//set datamembers
		this.height = height;
		this.width = width;
		
		//and resize, track first, then scrollbar
		Element.setStyle(this.elements.track, {
			height: this._getTrackHeight() + 'px',
			width: this.width + 'px'
		});
		Element.setStyle(this.element, {
			height: this.height + 'px',
			width: this.width + 'px'
		});
	},

	/**
	 * Disables the scrollbar
	 */
	setDisabled: function() {
		this.disabled = true;
		if (this.slider) this.slider.setDisabled();
	},
	
	/**
	 * Enables the scrollbar
	 */
	setEnabled: function() {
		this.disabled = false;
		if (this.slider) this.slider.setEnabled();
	},
	
	/**
	 * Sets the value of slider by offset, if not disabled
	 * @param float value
	 */
	setValueBy: function(value) {
		if (!this.disabled) {
			this.slider.setValueBy(value);
		}
	}
}

/**
 * options:
 *  axis: vertical, horizontal, auto (default).
 *  force: true or false (default)
 *  height: height in pixels or height of element (default)
 *  width: width in pixels or width of element (default)
 *  arrows{up, down, left, right}: the sources for the arrows (optional, if not passed, no arrows are displayed)
 *  classes{hHandle, hScrollbar, hTrack, vHandle, vScrollbar, vTrack, viewport}: the name of the classes used to customize the scrollbars and the viewport
 *  scrolling: onMouseDown, onClick (default)
 *  speed: The scrolling speed (0.01 - 0.99). Defaults to SMOOTH_SCROLLING_SPEED if scrolling onMouseDown or HARD_SCROLLING_SPEED otherwise. Does not affect the scrolling via mousewheel!
 */
Control.Scrollable.prototype = {
	/*
	 * inherited methods
	 */
	initialize: function(element, options) {
		var scrollable = this;
		
		this.element = $(element);
		this.options = options || {};
		
		this.axis = this.options.axis || 'auto';
		this.arrows = this.options.arrows || {};
		this.classes = this.options.classes || {};
		this.force = this.options.force || false;
		this.height = this.options.height || this.element.clientHeight;
		this.width = this.options.width || this.element.clientWidth;
		this.scrolling = this.options.scrolling;
		this.speed = this.options.speed,
		this.hScrollbar = null;
		this.vScrollbar = null;
		
		//set controls-datamember
		this.controls = {
			hScrollbar: null,
			vScrollbar: null
		};
		
		//set effects-datamember
		this.effects = {
			hScroll: null,
			vScroll: null
		};
		
		//set elements-datamember
		this.elements = {
			arrows: {
				down: null,
				left: null,
				right: null,
				up: null
			},
			content: null,
			hHandle: null,
			hTrack: null,
			vHandle: null,
			vTrack: null,
			viewport: null
		};
		
		//set events-datamember
		this.events = {
			//mousewheel-event
			mousewheel: this._mouseWheel.bindAsEventListener(this)
		};
		
		//set up the scrollbars, if needed (or forced)
		if ((this.needsHorizontal() || this.force) && (this.isHorizontal() || this.isAuto())) {
			this._setHorizontalScrollbar();
		}
		if ((this.needsVertical() || this.force) && (this.isVertical() || this.isAuto())) {
			this._setVerticalScrollbar();
		}
		
		//wrap element (put the pieces together in correct order) and set up interactivity
		this._wrapElement();
		this._setInteractive();
		
		//that's all folks
		this.initialized = true;
	},
	dispose: function() {
		var scrollable = this;
		eventName = (window.addEventListener) ? 'DOMMouseScroll' : 'mousewheel';
		Event.stopObserving(this.element, eventName, this.events.mousewheel);
	},
	
	/*
	 * private methods
	 */

	/**
	 * Calculates height of viewport
	 * @return int Height of viewport in pixels
	 */
	_getViewportHeight: function() {
		var vHeight = this.height;
		
		if (this.hScrollbar) {
			vHeight -= Element.getHeight(this.hScrollbar);
		}
		
		return vHeight;
	},
	
	/**
	 * Calculates width of viewport
	 * @return int Width of viewport in pixels
	 */
	_getViewportWidth: function() {
		var vWidth = this.width - 3;
		
		if (this.vScrollbar) {
			vWidth -= Element.getWidth(this.vScrollbar);
		}
		
		return vWidth; 
	},
	
	/**
	 * Fired when mousewheel is used.
	 * Increments vertical (or horizontal, if no vertical) scrollbar by a tenth of delta
	 * @param Event event The event that was fired by mousewheel
	 */
	_mouseWheel: function(event) {
		var delta = 0;

		if (!event) //IE specific.
        	event = window.event;
		if (event.wheelDelta) { //IE & Opera case
        	delta = event.wheelDelta/120;
        	//In Opera 9, delta differs in sign as compared to IE.
        	if (window.opera)
				delta = -delta;
		} else if (event.detail) { //Mozilla case
        	//In Mozilla, sign of delta is different than in IE.
         	//Also, delta is multiple of 3.
        	delta = -event.detail/3;
		}

		//If delta is nonzero, handle it.
 		//Basically, delta is now positive if wheel was scrolled up,
 		//and negative, if wheel was scrolled down.
		if (delta) {
			var scrollbar = (this.vScrollbar) ? this.controls.vScrollbar : this.controls.hScrollbar;
			scrollbar.setValueBy(-delta/10);
			Event.stop(event);
		}
	},
	
	/**
	 * Installs an event-listener for mousewheel
	 */
	_observeMouseWheel: function() {
		var eventName = (window.addEventListener) ? 'DOMMouseScroll' : 'mousewheel';
		
		Event.observe(
			this.element,
			eventName,
			this.events.mousewheel
		);
	},
	
	/**
	 * Sets datamember hScrollbar.
	 */
	_setHorizontalScrollbar: function() {
		this.hScrollbar = document.createElement('div');
		Element.addClassName(this.hScrollbar, this.classes.hScrollbar);
		Element.setStyle(this.hScrollbar, {cssFloat: 'left', left: '0px'}); //For FF
	},
	
	/**
	 * Sets up the effects and events needed for
	 * interactivity of scrollable control
	 */
	_setInteractive: function() {
		var scrollable = this;
		
		//take care that horizontal scrolleffect works, if we have one
		if (this.hScrollbar) {
			this.effects.hScroll = new Effect.ScrollHorizontal(
				scrollable.elements.viewport, { from: 0, to: 0 }
			);
			this.controls.hScrollbar = new Control.ScrollbarHorizontal(
				this.hScrollbar,
				{
					arrows: {
						left: this.arrows.left,
						right: this.arrows.right
					},
					classes: {
						handle: this.classes.hHandle,
						scrollbar: this.classes.hScrollbar,
						track: this.classes.hTrack
					},
					width: this._getViewportWidth(),
					scrolling: this.scrolling,
					speed: this.speed,
					onSlide: function(v) { scrollable._updateScrollEffect(scrollable, 'horizontal', v); },
					onChange: function(v) { scrollable._updateScrollEffect(scrollable, 'horizontal', v); }
				}
			);
			
			//if we're forced to display a scrollbar, but don't need one,
			//disable it
			if (!this.needsHorizontal()) {
				this.controls.hScrollbar.setDisabled();
			}
		}
		
		//take care that vertical scrolleffect works, if we have one
		if (this.vScrollbar) {
			this.effects.vScroll = new Effect.ScrollVertical(
				scrollable.elements.viewport, { from: 0, to: 0 }
			);
			this.controls.vScrollbar = new Control.ScrollbarVertical(
				this.vScrollbar,
				{
					arrows: {
						up: this.arrows.up,
						down: this.arrows.down
					},
					classes: {
						handle: this.classes.vHandle,
						scrollbar: this.classes.vScrollbar,
						track: this.classes.vTrack
					},
					height: this._getViewportHeight(),
					scrolling: this.scrolling,
					speed: this.speed,
					onSlide: function(v){ scrollable._updateScrollEffect(scrollable, 'vertical', v); },
					onChange: function(v){ scrollable._updateScrollEffect(scrollable, 'vertical', v); }
				}
			);
			
			//if we were forced to display a scrollbar,
			//but don't need one, we disable the scrollbar
			if (!this.needsVertical()) {
				this.controls.vScrollbar.setDisabled();
			}
		}

		//finally, we take care that mousewheel is useable
		if (this.hScrollbar || this.vScrollbar) {
			this._observeMouseWheel();
		}
	},

	/**
	 * Sets datamember vScrollbar
	 */
	_setVerticalScrollbar: function() {
		this.vScrollbar = document.createElement('div');
		Element.addClassName(this.vScrollbar, this.classes.vScrollbar);
		Element.setStyle(this.vScrollbar, {cssFloat: 'right', top: '0px', right: '0px'});
	},
	
	/**
	 * Updates a scroll effect on a given axis in a given scrollable control
	 * @param Control.Scrollable scrollable The scrollable control
	 * @param string axis The axis that shall be updated (horizontal or vertical)
	 * @param float value The value that shall be used to update
	 */
	_updateScrollEffect: function(scrollable, axis, value) {
		var scrollEffect;
		var translatedValue;
		
		if (axis=='horizontal') {
			scrollEffect = scrollable.effects.hScroll;
			translatedValue = (
				Element.getDimensions(scrollable.elements.content).width -
				Element.getDimensions(scrollable.elements.viewport).width
				) * value;
		} else if (axis=='vertical') {
			scrollEffect = scrollable.effects.vScroll;
			translatedValue = (
				Element.getDimensions(scrollable.elements.content).height -
				Element.getDimensions(scrollable.elements.viewport).height
				) * value;
		} else {
			return;
		}
		
		scrollEffect.update(translatedValue);
	},
	
	/**
	 * Wraps the element, making it scrollable.
	 * Sets the following datamembers as a side effect:
	 * - elements.viewport -> our viewport
	 * - elements.content -> the content that we'll scroll
	 */
	_wrapElement: function() {
		//store content and dimensions of element, as we will rewrite and -size it
		var content = this.element.innerHTML;
		var contentDims = Element.getDimensions(this.element); 
		
		//clear the element
		this.element.innerHTML = '';
		
		//create viewport- and content-node, and embed content in viewport
		this.elements.content = document.createElement('div');
		this.elements.viewport = document.createElement('div');
		this.elements.viewport.appendChild(this.elements.content);
		
		//first we append vScrollbar (if any), the viewport and finally hScrollbar (if any)
		if (this.vScrollbar)
			this.element.appendChild(this.vScrollbar);
		this.element.appendChild(this.elements.viewport);
		if (this.hScrollbar)
			this.element.appendChild(this.hScrollbar);
		
		//resize content depending on which scrollbars we have installed
		//if we are vertical only, we just set width
		//if we are horizotal only, we just set height
		//otherwise, we set old dimensions
		if (this.vScrollbar && !this.hScrollbar) {
			Element.setStyle(this.elements.content, {width: this._getViewportWidth() + 'px'});
		} else if (this.hScrollbar && !this.vScrollbar) {
			Element.setStyle(this.elements.content, {height: this._getViewportHeight() + 'px'});
		} else {
			Element.setStyle(this.elements.content, {
				height: contentDims.height + 'px',
				width: contentDims.width + 'px'
			});
		}
		
		//resize viewport
		Element.setStyle(this.elements.viewport, {
			height: this._getViewportHeight() + 'px',
			width: this._getViewportWidth() + 'px',
			overflow: 'hidden',
			cssFloat: 'left'
		});
		
		//resize element
		Element.setStyle(this.element, {
			height: this.height + 'px',
			width: this.width + 'px'
		});
		
		//set class (if any)
		if (this.classes.viewport)
			Element.addClassName(this.elements.viewport, this.classes.viewport);
		
		//set content
		this.elements.content.innerHTML = content;
	},
	
	/*
	 * public methods
	 */
	
	/**
	 * Returns whether control installs scrollbars automatically
	 * @return bool True if controls axis was initialized to be automatic, false otherwise
	 */
	isAuto: function() {
		return (this.axis=='auto');
	},
	
	/**
	 * Returns whether axis of control is horizontal
	 * @return bool True if controls axis was initialized to be horizontal, false otherwise
	 */
	isHorizontal: function() {
		return (this.axis=='horizontal');
	},
	
	/**
	 * Returns whether axis of control is vertical
	 * @return bool True if controls axis was initialized to be vertical, false otherwise
	 */
	isVertical: function() {
		return (this.axis=='vertical');
	},
	
	/**
	 * Returns whether control needs a horizontal scrollbar
	 * @return bool True if control needs a horizontal scrollbar to display content, false otherwise
	 */
	needsHorizontal: function() {
		var dimensions = Element.getDimensions(this.elements.content || this.element);
		return (this.width < dimensions.width);
	},
	
	/**
	 * Returns whether  control needs a vertical scrollbar
	 * @return bool True if control needs a vertical scrollbar to display content, false otherwise
	 */
	needsVertical: function() {
		var dimensions = Element.getDimensions(this.elements.content || this.element);
		return (this.height < dimensions.height);
	}
}

