/*--------------------------------------------------------------------------*/
/*	Navigation
/*--------------------------------------------------------------------------*/

// Crad Namespace
if ( !window.Crad )
	var Crad = new Object();

// CursorController
//
// Provides next/prev and indexed control to a cursor.
Crad.CursorController = function(cursor, wrap)
{
	var _this = this;
	var _cursor = cursor;
	var _wrap = (arguments.length < 2) ? true : wrap;	// wrap defaults to true
	
	this.dispose = function()
	{
		_cursor = null;
	}
	
	this.prev = function() { handleNextPrev(false); };
	this.next = function() { handleNextPrev(true); };
	this.select = function (index)
	{
		_cursor.setIndex(index);
	}

	function handleNextPrev(fwd)
	{
		var index = _cursor.getIndex();
		var length = _cursor.length();
		
		index += (fwd) ? 1 : -1;
		if ( index < 0 )
			index = (_wrap) ? length - 1 : 0;
		if ( index >= _cursor.length())
			index = (_wrap) ? 0 : length - 1;
		
		_cursor.setIndex(index);
	}
}

// PanelManager
//
// Selects between several sections (elements) -- showing one and hiding all others.
Crad.PanelManager = function(panelSelectorArray, firstPanel)
{
    var _this = this;
    var _panelSelectors;  // panel array
    var _currentPanel;
    
    // public methods
    this.getCurrentPanel = getCurrentPanel;
    this.setCurrentPanel = setCurrentPanel;
    
    // call init
    init(panelSelectorArray, firstPanel);
    
    // dispose
    this.dispose = function()
    {
		var len = _panelSelectors.length;
        for ( var i=0; i<len; i++ )
        {
			var panelSelector = _panelSelectors[i];
			var delegator = panelSelector.selector.getEventDelegator();
			delegator.unregister('click', selectHandler);
			delegator = null;
			_panelSelectors[i].panel = null;;
			_panelSelectors[i].selector = null;
        }
        _panelSelectors = null;
        _currentPanel = null;
    }
    
    // init([SectionSelector2, SectionSelector2, ...])
    // initialize routine
    function init(panelSelectorArray, firstPanel)
    {
		_panelSelectors = panelSelectorArray;
		// hook up a click event to the select element
		var len = _panelSelectors.length;
        for ( var i=0; i<len; i++ )
        {
			_panelSelectors[i].panel = $(_panelSelectors[i].panel);
			_panelSelectors[i].selector = $(_panelSelectors[i].selector);
			var panelSelector = _panelSelectors[i];
			panelSelector.panel.style.display = 'none'
			var delegator = panelSelector.selector.getEventDelegator();
			delegator.register('click', selectHandler);
        }
        setCurrentPanel(firstPanel || _panelSelectors[0].panel);
    }
            
    function getCurrentPanel()
	{
		return _currentPanel;
	}
    function setCurrentPanel(elem)
    {
		elem = $(elem);
		if ( _currentPanel && _currentPanel != elem )
			_currentPanel.style.display = 'none';
		_currentPanel = elem;
		_currentPanel.style.display = '';
	}
    
    // select
    //
    // show the section corresponding to the clicked element
    // context is the element that fired the event, which is the selector for the
    // section we want to show.
    function selectHandler(e)
    {
		_currentPanel.style.display = 'none';
		_currentPanel = getSectionBySelector(this);
		_currentPanel.style.display = '';
    }
    
    function getSectionBySelector(elem)
    {
		var panel = null;
		for ( var i in _panelSelectors )
		{
			var panelSelector = _panelSelectors[i];
			if ( panelSelector.selector == elem )
			{
				panel = panelSelector.panel;
				break;
			}
		}
		return panel;
    }
}

// Scroller
//
// Scrolls an element within a container. Optionally maintains a cursor by updating 
// the cursor's index in response to sub-elements being clicked.
Crad.Scroller = function(containerElem, scrollElem, vertical)
{
	var _options = {
		delay : 20,					// delay for setInterval (ms)
		scrollIncrement : 3,		// deprecated
		scrollAreaBorder : 5,		// fixes an issue with IE
		accelerationFactor : 4		// how fast to accelerate; bigger is slower, and it is exponential
	};
	
	// initialization
	var _containerElem = $(containerElem);
	var _scrollElem = $(scrollElem);
	var _currentMousePos = 0;
	var _interval;
	var _vertical = (arguments.length < 3) ? true : vertical; // defaults to vertical scroller;
	var _timesCalled = 0;
	var _setup = new Object();
	
	// Attach the Scroller to the given element
	var delegator = _containerElem.getEventDelegator();
	delegator.register('mouseover', startScrolling);
	delegator.register('mousemove', getMouseScrollPosition);
	delegator.register('mouseout', stopScrolling);
	delegator = null;
		
	// accessors
	this.getScrolledElement = function() { return _scrollElem; }
	this.getChildElementAt = function(index) { return _scrollElem.getImmediateChildren()[index]; }
	
	// dispose
	this.dispose = function()
	{
		stopScrolling();
		
		delegator = _containerElem.getEventDelegator();
		delegator.unregister('mouseover', startScrolling);
		delegator.unregister('mousemove', getMouseScrollPosition);
		delegator.unregister('mouseout', stopScrolling);
		delegator = null;
		
		_containerElem = null;
		_scrollElem = null;
	}
	
	// scrollToChildElementByIndex
	//
	// scrolls the immediate child element of the given index in the scrolled Element to
	// the middle of the container.
	this.scrollToChildElementByIndex = function( index, animate )
	{
		// get the element in question
		var elem = _scrollElem.getImmediateChildren()[index];
		if ( arguments.length < 2 )
			animate = true;

		// calculate the offset of the element to the scrollElem
		// change the scrollElem top (or left) margin to put the element dead center
		// in the containerElem
		var elemOffset;
		var elemSize;
		var elemMid;
		
		// set up for either kind of scroller (horiz or vert)
		scrollSetupContainerInfo(_setup);
		scrollSetupScrollInfo(_setup);

		// offsetTop/offsetLeft are relative to the offsetParent element,
		// however, offsetParent may differ between IE and FF. Here, we accommodate both:
		var adjustForOffsetParent = ( elem.offsetParent != _scrollElem );
		if ( _vertical )
		{
			elemOffset = elem.offsetTop - ((adjustForOffsetParent) ? _scrollElem.offsetTop : 0 );
			elemSize = parseInt(elem.scrollHeight,10);
		}
		else
		{
			elemOffset = elem.offsetLeft - ((adjustForOffsetParent) ? _scrollElem.offsetLeft : 0 );
			elemSize = parseInt(elem.scrollWidth,10);
		}		
		elemMid = Math.floor(elemSize >> 1);
		
		// Bound the scroll position so that the top & bottom always stay in the right place.
		var scrollPos = Math.max( _setup.containerSize-_setup.scrollMax, _setup.containerMid - ( elemOffset + elemMid ));
		if ( scrollPos > 0 )
			scrollPos = 0;
		
		if ( !animate)
		{
			scrollTo(scrollPos);
		}
		else
		{
			new Crad.Animator(
				_scrollElem,	// element to animate
				(_vertical) ? 'marginTop' : 'marginLeft',	// property to animate
				true,			// property is within style property
				_setup.scrollPos,	// start
				scrollPos,		// end
				"",				// before text
				"px",			// after text
				15,				// step size
				20 );			// delay
		}
	}
	
	// adjust the top margin of the scrollElem based on the position of the mouse inside the containerElem
	// mouse above middle: scroll up (faster as it goes towards the top)
	// mouse at middle: no scroll
	// mouse below middle: scroll down (faster as it goes towards the bottom)
	// context is the containerElement
	function startScrolling(e)
	{
		// may get called as we re-enter the container from an element contained
		// within the container; if so, we will already have an interval timer, so
		// we just return
		if ( _interval == null )
			_interval = setInterval(scroll, _options.delay);
	}
	
	// retrieve the position of the mouse relative to the container element and store it.
	function getMouseScrollPosition(e)
	{
		// get x,y coords relative to container element.
		_currentMousePos = eventPositionWithinElementXY(e, _containerElem );
	}
	
	function scrollTo(offset)
	{
		// change margin
		if ( _vertical )
		{
			_scrollElem.style.marginTop = offset + "px";
		}
		else
		{
			_scrollElem.style.marginLeft = offset + "px";			
		}
	}
	
	function scrollSetupScrollInfo(setup)
	{
		// set up for either kind of scroller (horiz or vert)
		var margin;
		if ( _vertical ) // vertical scroller
		{
			setup.currentMousePos = _currentMousePos.y;
			margin = _scrollElem.style.marginTop;
		}
		else // horizontal scroller
		{
			setup.currentMousePos = _currentMousePos.x;
			margin = _scrollElem.style.marginLeft;
		}
		setup.scrollPos = (margin == "") ? 0 : parseInt(margin,10);
	}
	
	function scrollSetupContainerInfo(setup)
	{
		// set up for either kind of scroller (horiz or vert)
		if ( _vertical ) // vertical scroller
		{
			setup.containerSize = parseInt(_containerElem.clientHeight,10); // displayed height of the container element
			setup.scrollMax = parseInt(_scrollElem.scrollHeight,10);	// max height the scrolling element
		}
		else // horizontal scroller
		{
			setup.containerSize = parseInt(_containerElem.clientWidth,10); // displayed width of the container element
			setup.scrollMax = parseInt(_scrollElem.scrollWidth,10); // max width of the scrolling element
		}
		setup.containerMid = setup.containerSize >> 1;
	}
	
	function scroll()
	{
		// set up for either kind of scroller (horiz or vert)
		scrollSetupContainerInfo(_setup);
		scrollSetupScrollInfo(_setup);
		
		// compute new margin position based on scroll direction & amount
		var scrollAmt = Math.floor((_setup.currentMousePos - _setup.containerMid ) >> _options.accelerationFactor);

		// change the scroll position -- bound it so that the top doesn't go below the top of the container
		// and the bottom doesn't go above the bottom of the container.
		var scrollPos = Math.max(_setup.containerSize-_setup.scrollMax, _setup.scrollPos - scrollAmt);
		if ( scrollPos > 0 )
			scrollPos = 0;
		
		// change margin
		if ( scrollPos != _setup.scrollPos )
		{
			scrollTo(scrollPos);
		}
	}
		
	function stopScrolling(e)
	{
		// check to see if the mouse position is still inside the _containerElem; if so, just return
		// note: this doesn't always work in IE -- we don't seem to get the mouseout with the right mouse coords
		
		if ( e && _containerElem.mouseWithin(e, _options.scrollAreaBorder))
			return;
		
		// clear the interval timer to stop scrolling
		if ( _interval )
		{
			clearInterval(_interval);
			_interval = null;
		}
	}
}

// ScrollerController
//
// Updates a cursor based on click-selection of an element inside the scroller
Crad.ScrollerController = function(scroller, cursor)
{
	var _scrolledElem = scroller.getScrolledElement();
	var _cursor = cursor;
	
	// Add an onclick handler to each immediate child of the scrolled element
	var nodes = _scrolledElem.getImmediateChildren();
	for ( var i=0; i<nodes.length; i++ )
	{
		nodes[i].getEventDelegator().register('click', handleThumbClick);
	}
	
	this.dispose = function()
	{
		var nodes = _scrolledElem.getImmediateChildren();
		for ( var i=0; i<nodes.length; i++ )
		{
			nodes[i].getEventDelegator().unregister('click', handleThumbClick);
		}
		_scrolledElem = null;
		_cursor = null;
	}
	
	function handleThumbClick(e)
	{
		// figure out our index
		var index = this.getIndexInParent();
		// set the cursor
		_cursor.setIndex(index);
	}
}

// ScrollerView
//
// Responds to changes in the cursor by scrolling the sub-element corresponding
// to the current cursor index into view.
Crad.ScrollerView = function( scroller, cursor )
{
	var _scroller = scroller;
	var _cursor = cursor;
	
	// register as an observer of the cursor
	_cursor.register(scrollerObserver);

	// when the cursor changes, scroll the corresponding sub-element into view.
	function scrollerObserver()
	{
		_scroller.scrollToChildElementByIndex(_cursor.getIndex());
	}
}


// ExpandCollapse
//
// Performs the Expand/Collapse function for showing/hiding elements.
// Limitations:
Crad.ExpandCollapse = function(elementArray)
{
	var _options = {
		incDelay: 3,
		navPix: 3
	};
    
    // member variables
    var _currentElem;
	var _expandElem;
	var _collapseElem;
	var _expandCurrHeight;
	var _collapseCurrHeight;
    var _explandFinalHeight;
    var _collapseFinalHeight;
    var _inProgress = false;
    
    // public methods
    this.getExpandFunction = getExpandFunction;
    
    // call init for constructor
    init(elementArray);
    
    // Init( [elem1, elem2, elem3, ...] )
    // Initialze the Navigation object with an array of elements, the first of which is the initially expanded element.
    function init(elementArray)
    {
		for ( var i in elementArray )
		{
			var elem = elementArray[i];
			elem.getEventDelegator().register('click', expand);
			elem = null;
		}
        _currentElem = elementArray[0];
    }
    
    // getExpandFunction(elem)
    // return the Expand function to be executed in the context of the given element.
    function getExpandFunction(elem)
    {
		return expand.bind(elem);
    }    
    
    // Expand
    // Expand the given element by setting its pixelHeight to its scrollHeight and
    // setting the currently shown element's height to a collapsed state using animation.
    // The animation simply makes several delayed calls to SetElemHeights() to change the heights
    // of the elements by a small amount. The delayed calls are made using setTimeout().
    function expand(event)
    {
		// bail if the element to be shown is already shown or if an expand/collapse is in progress
        if ( this == _currentElem || _inProgress )
        {
			return;
		}
        
        _inProgress = true;

		// Set everything up to animate between two states
        _expandElem = this;
        _collapseElem = _currentElem;
        
        _explandFinalHeight = parseInt(_expandElem.scrollHeight,10);
        _collapseFinalHeight = parseInt(_expandElem.clientHeight,10);
                
        _expandCurrHeight = parseInt(_expandElem.clientHeight,10);
        _collapseCurrHeight = parseInt(_collapseElem.clientHeight,10);
        
		// calculate how many times we need to make to call SetElemHeight and fire off
		// setTimeout calls to SetElemHeights with appropriate delays.
        var delay = 0;
        var iterations = Math.ceil(Math.max(
			(_explandFinalHeight-_expandCurrHeight)/_options.navPix,
			(_collapseCurrHeight-_collapseFinalHeight)/_options.navPix));
        for ( var i=0; i<iterations; i++ )
        {
            setTimeout(updateElemHeights, delay);
            delay += _options.incDelay;
        }

        // set the currently expanded element to the newly expanded element
		_currentElem = _expandElem;
    }
	
	// UpdateElemHeights
	function updateElemHeights()
	{
		_expandCurrHeight = Math.min(_expandCurrHeight + _options.navPix, _explandFinalHeight);
		_collapseCurrHeight = Math.max(_collapseCurrHeight - _options.navPix, _collapseFinalHeight);

		_expandElem.style.height = _expandCurrHeight + "px";
		_collapseElem.style.height = _collapseCurrHeight + "px";
		
		if ( _expandCurrHeight == _explandFinalHeight && _collapseCurrHeight == _collapseFinalHeight )
			_inProgress = false;
	}	
}

