/**
 * Javascript that handles the scrolling container 
 */
var ScrollingContainer = Class.create({

	/**
	 * Constructor
	 * 
	 * @param Element scrollingcontainer The container element that should be fixed according to view port
	 * @param Element boundElement Element that specifies scrolling bounds for the container. Null to use body.
	 */
	initialize: function(scrollingcontainer, boundelement) {
		// Containers to handle scrolling for
		this.container = scrollingcontainer;
		this.origPos = scrollingcontainer.cumulativeOffset();
		this.boundelement = (!Object.isUndefined(boundelement) && boundelement != null) ? boundelement : $$('body').first();

		// Set position fixed to container
		this.container.setStyle({ position: 'fixed'});
		
		// Determine bounds
		var boundelementTop = this.boundelement.cumulativeOffset().top;
		this.bounds = {top: boundelementTop, bottom: boundelementTop + this.boundelement.getHeight()};
		
		// Prepare for alignment bindings
		this.scrollAlignmentBindings = new Hash({});
		
		// Create scroll listener
		var selfref = this;
		Event.observe(window, 'scroll', function(event) {
			selfref.onScroll(event);
		});
	},

	/**
	 * Callback method that handles scrolling events
	 */
	onScroll: function(event) {
		
		var viewportOffset = document.viewport.getScrollOffsets();
		
		// Determine new position to adjust element to.
		var isFixedPos = this.container.getStyle('position') == 'fixed';
		
		// Determine the max position of the container
		var maxPos = this.bounds.bottom - this.container.getHeight();
		
		// We need to adjust the margin-top value in order to position the element correctly.
		var newMargin = 0;
		var pos = this.origPos.top - (this.origPos.top - viewportOffset.top);
		if (isFixedPos) {
			newMargin = (pos > this.origPos.top ? this.origPos.top : pos);
		}
		else {
			newMargin = pos;
		}
		if (pos > maxPos) {
			newMargin += (pos - maxPos)
		}
		//console.log(maxPos, pos, newMargin);
		
		newMargin = (0 - newMargin);
		this.container.style.marginTop = newMargin + 'px';
		
		// Check if container is present in scroll alignment bindings.
		var containerBindings = this.scrollAlignmentBindings.get(this.container);
		if (Object.isUndefined(containerBindings) || containerBindings == null) {
			return;
		}
		
		var sourcePos = this.origPos.top + (pos + newMargin);
		var sourceHeight = this.container.getHeight();
		//console.log(containerPos, containerHeight);
		
		// Iterate the bindings to find the closest one...
		var foundBindingSource = null, maxCoverage = 0;
		for (var j = 0, len2 = containerBindings['bindings'].length; j < len2; j++) {
			var currBinding = containerBindings['bindings'][j];
			var targetPos = currBinding['target'].cumulativeOffset().top;
			var targetHeight = currBinding['target'].getHeight();

			// Calculate percentage of coverage
			var coverage = 0;
			if (sourcePos + sourceHeight > targetPos && sourcePos < targetPos + targetHeight) {

				var minHeight = Math.min(sourceHeight, targetHeight);
				var coverage = Math.min((sourcePos + sourceHeight) - targetPos, (targetPos + targetHeight) - sourcePos, minHeight) * 100 / minHeight;
				
				if (coverage > maxCoverage) {
					maxCoverage = coverage;
					foundBindingSource = currBinding['source'];
				}
			}
		}
		
		// Exist if we did not find a new bind
		if (foundBindingSource != null) {
			if (containerBindings['lastEventSource'] != foundBindingSource) {
				// Fire an event for anyone interested in the alignment event
				Event.fire(foundBindingSource, 'scrollhandler:alignment', {container: this, lastElement: containerBindings['lastEventSource']});
				containerBindings['lastEventSource'] = foundBindingSource;
			}
		}
	},

	
	/**
	 * Adds a scroll event binding from a source to a target element
	 * 
	 * This means that an event is fired when the source element aligns with the
	 * the target element. The event fired can be observed by the following code:
	 * 
	 * Event.observe(sourceElement, 'scrollhandler:alignment', ..function...);
	 * 
	 * @param source The source element to observe
	 * @param target The target element to observe
	 */
	addScrollAlignmentBinding: function(source, target) {
		if (source == null || target == null) {
			return;
		}
		var sourceContainer = this.container;
		if (!Object.isUndefined(sourceContainer) && sourceContainer != null) {
			var containerBindings = this.scrollAlignmentBindings.get(sourceContainer);
			if (Object.isUndefined(containerBindings) || containerBindings == null) {
				containerBindings = {'bindings': new Array(), 'lastEventSource': null };
				this.scrollAlignmentBindings.set(sourceContainer, containerBindings);
			}
			containerBindings['bindings'][containerBindings['bindings'].length] = {'source': source, 'target': target};
		}
	},
	
	/**
	 * Returns related bidings given a source elements.
	 * 
	 * This is used to determine all elements belonging to a scroll alignment binding,
	 * given one of the elements registered in the binding.
	 * 
	 * @return array Of related bindings, or null if element is not present in any binding
	 */
	getRelatedBindings: function(source) {
		var sourceContainer = this.container;
		if (!Object.isUndefined(sourceContainer) && sourceContainer != null) {
			var containerBindings = this.scrollAlignmentBindings.get(sourceContainer);
			if (!Object.isUndefined(containerBindings) && containerBindings != null) {
				var result = new Array();
				for (var i=0, len = containerBindings['bindings'].length; i < len; i++) {
					result[result.length] = containerBindings['bindings'][i].source;
				}
				return result;
			}
		}		
		return null;
	}
});



