/*
Script: Scroll.js
	Contains <Scroll>

Author:
	Alan Roemen
	February 4, 2008

Class: Scroll
	A javascript content scroller

Options:
	minControlSize: Minimum size of scroll bar selector. Default: 30
	fixedControlSize: Fixed size of scroll bar selector. Default: false (off)
	mode: Scrolling mode. Default: 'vertical'
	width: Scrolling width (required only for mode: horizontal). Default: 0
	scrollSteps: Pixels to scroll. Default: 10
	wheel: Allows mouse wheel to scroll. Default: true
	scrollLinks: Scrolling arrow id from HTML. Default: {forward: 'scrollForward', back: 'scrollBack'}
	pageOffset: Integer for increment/decrement of page value
	onScroll: Function executed when function onScroll is called. Default: Class.empty
	onPage: Function executed when function onPage is called. Default: Class.empty
*/
var Scroll = new Class({
	options: {
		minControlSize: 30,
		fixedControlSize: false,
		mode: 'vertical',
		width: 0,
		scrollSteps: 10,
		wheel: true,
		scrollLinks: {
			forward: 'scrollForward',
			back: 'scrollBack'
		},
		pageOffset: 0,
		onScroll: Class.empty,
		onPage: Class.empty
	},

	initialize: function(content, control, options){
		if(!$(content)) return;
		this.setOptions(options);
		this.horz = (this.options.mode == "horizontal");

		this.content = $(content);
		this.control = $(control);
		this.track = this.control.getParent();
		this.setPositions();

		// For Horizontal Scrolling
		if(this.horz && this.options.width) {
			this.wrapper = new Element('div');
			this.content.getChildren().each(function(child){
				this.wrapper.adopt(child);
			});
			this.wrapper.injectInside(this.content).setStyle('width', this.options.width);
		}

		this.bound = {
			'start': this.start.bind(this),
			'end': this.end.bind(this),
			'drag': this.drag.bind(this),
			'wheel': this.wheel.bind(this),
			'page': this.page.bind(this)
		};

		this.position = {};
		this.mouse = {};
		this.update();
		this.attach();

		var clearScroll = function (){
			$clear(this.scrolling);
		}.bind(this);

		// Setup Controlling Arrows
		['forward','back'].each(function(direction) {
			var lnk = $(this.options.scrollLinks[direction]);
			if(lnk) {
				lnk.addEvents({
					mousedown: function() {
						this.scrolling = this[direction].periodical(50, this);
					}.bind(this),
					mouseup: clearScroll.bind(this),
					click: clearScroll.bind(this)
				});
				lnk.setStyle('cursor','pointer');
			}
		}, this);

		this.control.addEvent('click', clearScroll.bind(this));
		window.addEvent('domready', function(){
			try {
				$(document.body).addEvent('mouseup', clearScroll.bind(this));
			}catch(e){}
		}.bind(this));
	},

	setPositions: function(){
		[this.track, this.control].each(function(el){
			if (el.getStyle('position') == 'static') el.setStyle('position','relative');
		});
	},

	attach: function(){
		this.control.addEvent('mousedown', this.bound.start);
		if (this.options.scrollSteps) this.content.addEvent('mousewheel', this.bound.wheel);
		this.track.addEvent('mouseup', this.bound.page);
	},

	/* Property: update
	Updates the size of the scroll control; execute this method when the content changes or the container's size is altered.
	*/

	update: function(offset){
		if(!this.content) return;
		var offset = offset || 0;
		var plain = this.horz?'Width':'Height';
		this.contentSize = this.content['offset'+plain].toInt()+offset.toInt();
		this.contentScrollSize = this.content['scroll'+plain].toInt()+offset.toInt();
		if(this.contentSize >= this.contentScrollSize) {
			this.track.getParent().setStyle('display', 'none');
			this.track.getParent().setStyle('visibility', 'hidden');
		} else {
			this.track.getParent().setStyle('display', 'block');
			this.track.getParent().setStyle('visibility', 'visible');
		}
		this.trackSize = this.track['offset'+plain];
		this.contentRatio = this.contentSize / this.contentScrollSize;
		if (this.options.fixedControlSize === false)
			this.controlSize = (this.trackSize * this.contentRatio).limit(this.options.minControlSize, this.trackSize);
		else this.controlSize = this.options.fixedControlSize;
		if(isNaN(this.controlSize)) return;

		this.scrollRatio = this.contentScrollSize / (this.trackSize - this.controlSize);
		this.control.setStyle(plain.toLowerCase(), this.controlSize);
		this.updateThumbFromContentScroll();
		this.updateContentFromThumbPosition();
	},

	updateContentFromThumbPosition: function(){
		this.content[this.horz?'scrollLeft':'scrollTop'] = this.position.now * this.scrollRatio;
	},

	updateThumbFromContentScroll: function(calc_position){
		if (calc_position !== false) {
			var p = this.content[this.horz?'scrollLeft':'scrollTop'] / this.scrollRatio;
			this.position.now = (p).limit(0, (this.trackSize - this.controlSize));
		}
		this.control.setStyle(this.horz?'left':'top', this.position.now);
	},

	scroll: function(steps){
		this.fireEvent('onScroll');
		steps = steps||this.options.scrollSteps;
		this.content[this.horz?'scrollLeft':'scrollTop'] += steps;
		this.updateThumbFromContentScroll();
	},

	forward: function(steps){
		this.scroll(steps);
	},

	back: function(steps){
		steps = steps||this.options.scrollSteps;
		this.scroll(-steps);
	},

	start: function(event){
		event = new Event(event);
		var axis = this.horz?'x':'y';
		this.mouse.start = event.page[axis];
		this.position.start = this.control.getStyle(this.horz?'left':'top').toInt();
		document.addEvent('mousemove', this.bound.drag);
		document.addEvent('mouseup', this.bound.end);
		this.control.addEvent('mouseup', this.bound.end);
		event.stop();
	},

	end: function(event){
		event = new Event(event);
		document.removeEvent('mousemove', this.bound.drag);
		document.removeEvent('mouseup', this.bound.end);
		this.control.removeEvent('mouseup', this.bound.end);
		event.stop();
	},

	drag: function(event){
		event = new Event(event);
		var axis = this.horz?'x':'y';
		this.mouse.now = event.page[axis];
		this.position.now = (this.position.start + (this.mouse.now - this.mouse.start)).limit(0, (this.trackSize - this.controlSize));
		this.updateContentFromThumbPosition();
		this.updateThumbFromContentScroll(false);
		event.stop();
	},

	wheel: function(event){
		if(!this.options.wheel) return;
		event = new Event(event);
		this.scroll(-(event.wheel * this.options.scrollSteps));
		this.updateThumbFromContentScroll();
		event.stop();
	},

	page: function(event){
		var axis = this.horz?'x':'y';
		event = new Event(event);
		var forward = (event.page[axis] > this.control.getPosition()[axis]);
		this.scroll((((forward?1:-1)*this.content['offset'+(this.horz?'Width':'Height')]).toInt() + ((forward?-1:1)*this.options.pageOffset).toInt()));
		this.updateThumbFromContentScroll();
		this.fireEvent('onPage', forward);
		event.stop();
	}
});

Scroll.implement(new Options, new Events);