/*
ScatterPage.js
copyright 2008 Rob Whelan
js [at] robwhelan [dot] com

Important notice: if you want to use this code for your own website, please contact me first.
It's still very much under development and is not at all cleaned up for redistribution.
I'm also fairly new to MooTools development, so it's probably not the best example code!

Options (likely out of date at any given point...):
  tabActiveClass
  tabInactiveClass
  tabMouseoverClass
  defaultIndex: the initially active tab index
  container: div that will be scrolled to show the content divs.
  tabs: selector for nav tabs (change class based on selection and mouseover)
  links: selector for nav links in tabs (for click events)
  contentDivs: selector for content divs
  contentBgDivs:            content bg divs
     these are faded out before the scrolling happens
  ..and the fx: only used internally.

for adding history support for clicking links:
  http://digitarald.de/project/historymanager/
    (& can see how smoothgallery did it)
*/

var ScatterPage = new Class({
	options: {
		// the defaults...
		tabActiveClass: 'tabOn',
		tabInactiveClass: 'tabOff',
		tabMouseoverClass: 'tabOver',
		defaultIndex: 0,
		container: Class.empty,
		tabs: [],
		links: [],
		contentDivs: [],
		bgDivs: []
	},
	
	
	initialize: function(options){
		// merges defaults & passed-in
		this.setOptions(options);
	
		var opt = this.options;
		// pull out of options
		this.container = $(opt.container);
		this.tabs = $$(opt.tabs);
		this.links = $$(opt.links);
		this.contentDivs = $$(opt.contentDivs);
		this.bgDivs = $$(opt.bgDivs);
		// define here
		this.contentFx = [];
		this.previousIndex = -1;
		this.currentIndex = -1;
		
		this.tweakViewportSize();

		// set initial location to upper right before pan...
		
		var defaultPosition = this.bgDivs[this.options.defaultIndex].getPosition(this.container);
		
		this.container.scrollTo(0, defaultPosition.y);
		
		this.setupLinksAndFx();

		// TODO: extract link from URL here? else use default....
		this.scrollToContent(this.options.defaultIndex);
	},
	
	
	/*
		setupLinksAndFx()
	*/
	setupLinksAndFx: function(){		
		this.scrollFx = new Fx.Scroll(this.container, {
			wait: false,
			duration: 3000,
			//fps: 50,  default... tweak?
			overflown: [],
			offset: {'x': 0, 'y': 0},  // tweak x offset on the fly below later
			wheelStops: false,
			transition: Fx.Transitions.Sine.easeInOut
		});

		this.links.each( function(link, index){
			this.setupLink(link, this.tabs[index], index);
			
			// construct effects
			this.contentFx[index] = new Fx.Elements([this.contentDivs[index], this.bgDivs[index]], {duration: 200, transition: Fx.Transitions.linear});
			
			// vanish all to start with
			this.contentDivs[index].setStyles({display: "none", opacity: 0});
			this.bgDivs[index].setStyles({display: "none", opacity: 0});
		}, this);
	},
	
	/*
		setupLink()
		
		Arguments:  link, tab, index
					tab is optional
	*/
	setupLink: function(link, tab, index)
	{
		if( tab ) {
			tab.addEvent('mouseover', function(){
				this.tabs[index].addClass(this.options.tabMouseoverClass);
			}.bind(this));
			tab.addEvent('mouseout',function(){
				this.tabs[index].removeClass(this.options.tabMouseoverClass);
			}.bind(this));
		}

		link.addEvent('click', function(event){
			event = new Event(event).stop(); // needed to stop anchor jump...
			this.links[index].blur();
			this.scrollToContent(index); // this.links.indexOf(link));
		}.bind(this));

		return;
	},
	
	
	/*
		manage it, if the tabs are going to cut into the viewport
		is there a CSS-only way to handle this?
	*/
	tweakViewportSize: function()
	{
		// shrink down center pane to manageable height if we're going to have overlap
		if( window.getHeight() < 680 ) {
		    var height = window.getHeight() - 80;
		    var marginTop = -1 * height / 2;
		    var contentHeight = height - 20;
		    
		    this.container.setStyles({"height":height, "margin-top":marginTop});

		    this.contentDivs.each( function(contDiv, index){
				contDiv.setStyle("height", contentHeight);
				this.bgDivs[index].setStyle("height", contentHeight);
			}, this);
		}
	},
	
	/*	scrollToContent
		Scrolls the view from one content to another.
		
		Arguments:
		index - (integer) the index of the content to show (from link index).
	*/
	scrollToContent: function(toIndex)
	{		
		if( toIndex == this.currentIndex ) {
			this.contentFx[this.currentIndex].start({	'0': {'opacity': [1]},
														'1': {'opacity': [0.4]}  });
			return;
		}
		
		// otherwise, gotta move
		this.previousIndex = this.currentIndex;
		this.currentIndex = toIndex;
		
		// tweak the scroll offset in case window has been resized
		var newXOffset = (window.getWidth() - 600) / -2;
		this.scrollFx.options.offset.x = newXOffset;
		
		// make new bg div available so we can scroll to it (can't get location if display: none)
		this.bgDivs[this.currentIndex].setStyle("display", "block");
		
		this.updateTabs();
		
		var parentPos = this.container.getScroll(); // getPosition();
		var targetPos = this.bgDivs[this.currentIndex].getPosition(this.container);
		var toX = targetPos.x;// - parentPos.x;
		var toY = targetPos.y;// - parentPos.y;

		var oldX = this.container.getScroll().x - newXOffset; // back out the offset
		var oldY = this.container.getScroll().y;
		
		// short chain: scroll to new, show it
		if( this.previousIndex < 0 ) {
			//console.log("short chain to " + this.currentIndex );
			
			// chain in scrollFx object
			this.scrollFx.chain(function(){
					this.scrollFx.options.duration = 3000;
					this.scrollFx.start( toX, toY );
				}.bind(this)
			);
			this.scrollFx.chain(function(){
					this.contentDivs[this.currentIndex].setStyle("display", "block");
					this.contentFx[this.currentIndex].start({	'0': {'opacity': [0,1]},
																'1': {'opacity': [0,0.4]}  });
				}.bind(this)
			);
			this.scrollFx.callChain();
		}
		else { // full chain: hide old, scroll to new, show new.
			// cancel any ongoing move
			this.contentFx[this.previousIndex].cancel();
			this.scrollFx.cancel();
			
			// two chains: contentFx and scrollFx
			
			this.contentFx[this.previousIndex].chain(
				function(){
					this.contentFx[this.previousIndex].start({	'0': {'opacity': [0]},
																'1': {'opacity': [0]}  } );
				}.bind(this)
			);
			this.contentFx[this.previousIndex].chain(
				function(){
					this.contentDivs[this.previousIndex].setStyle("display", "none");
					this.bgDivs[this.previousIndex].setStyle("display", "none");
					this.scrollFx.callChain(); // pass to chain #2
				}.bind(this)
			);

			// avoid diagonal scrolling in IE... it can't handle it.  If IE, scroll x first, THEN y.
			
			this.scrollFx.chain(
				function(){
					this.contentDivs[this.previousIndex].setStyle("display", "none");
					this.bgDivs[this.previousIndex].setStyle("display", "none");
					if( Browser.Engine.trident ) {
					 	// don't change Y yet for IE
						this.scrollFx.options.duration = this.calcScrollTiming(oldX, oldY, toX, oldY);
						this.scrollFx.start( toX, false );
					}
					else {
						this.scrollFx.options.duration = this.calcScrollTiming(	oldX, oldY, toX, toY);
						this.scrollFx.start( toX, toY );
					}
				}.bind(this)
			);
			
			if( Browser.Engine.trident )
			{
				this.scrollFx.chain(
					function(){
						// now scrolling Y only
						this.scrollFx.options.duration = this.calcScrollTiming(toX, oldY, toX, toY);
						this.scrollFx.start( toX, toY );
					}.bind(this)
				);
			}
			
			// last step in chain #2: show the new content
			this.scrollFx.chain(
				function(){
					this.contentDivs[this.currentIndex].setStyle("display", "block");
					this.contentFx[this.currentIndex].start({	'0': {'opacity': [1]},
																'1': {'opacity': [0.4]}  });
				}.bind(this)
			);
			
			// kick it off
			this.contentFx[this.previousIndex].callChain();
		}
		
		// set the history & address bar in last chain step?
	},
	
	/*
	
	*/
	calcScrollTiming: function(x1, y1, x2, y2)
	{		
		var shiftX = Math.abs( x2 - x1 );
		var shiftY = Math.abs( y2 - y1 );
		
		if( shiftX == 0 && shiftY == 0 )
			return 0;
		
		// pythogoras!
		var distance = Math.sqrt( Math.pow(shiftX,2) + Math.pow(shiftY,2) );
		
		var timing = distance*2;
		if( timing > 0 && timing < 1500 )
			timing = 1500;
		
		return timing;
	},
	
	/*
		update each tab based on currentIndex and tabActiveClass/tabInactiveClass
	*/
	updateTabs: function()
	{
		this.tabs.each( function(tab, index){
			if( index == this.currentIndex ) {
				tab.removeClass(this.options.tabInactiveClass).addClass(this.options.tabActiveClass);
			}
			else {
				tab.removeClass(this.options.tabActiveClass).addClass(this.options.tabInactiveClass);
			}
		}, this);
	}
});

/* moo 1.11 */
ScatterPage.implement(new Options, new Events);