/**
 * @author dustin hansen <http://maveno.us>
 * @version 0.5.0
 * @license Dual licensed under MIT and GPL
 *
 * TODO: layout could be betta. don't think current implementation will allow for left/right/top overlays, etc.
 * 
*/

var SquaredMotion = new Class({
	Implements: [Options, Events],
	options: {
/* 
		onEnter: $empty(),
		onExit: $empty(),
 */
		'autoPlay': false,						// auto start the pages
		'autoTimer': 7.5,						// time in seconds to rotate pages
		'backText': 'Back',						// text for the back button
		'crumbElem': null,						// predefined elem for the crumb list
		'crumbElemClass': 'square-crumbs',		// class for crumb elem if built by code
		'crumbPos': '',							// '', top, bottom
		'crumbs': 'numeric',					// numeric, empty, []
		'crumbSelectClass': 'crumb-select',		// selected crumb elem class
		'elem': null,							// container elemenet
		'height': 'auto',						// preset height for all pages to conform to
		'hideOverlay': false,					// show the overlay only on mouseover onEnter
		'navButtonClass': 'sm-nav-buttons',		// base class for navigation buttons
		'navElem': null,						// navigation containing element (contains crumbs and navigation)
		'navElemClass': 'square-navigation',	// css class for navigation containing element
		'nextText': 'Next',						// text for the next button
		'navY': 'middle', 						// top, middle, bottom
		'navX': 'center', 						// inside, outside, center
		'pageElem': null,						// should be book elem, contains all the pages
		'pageWrap': true,						// should the next/back buttons wrap around to the other pages?
		'uniformHeight': false,					// set all pages to the height of the tallest element?
		'useCrumbs': true,						// even need to use crumbs?
		'useNav': true,							// do we want next/back buttons?
		'width': 'auto'							// preset width for all pages to conform to
	},

	biggest: 0,
	book: null,
	crumbElem: null,
	currentPage: 0,
	elem: null,
	elemID: null,
	navElem: null,
	nextNav: null,
	overlayElem: null,
	overlays: [],
	pageCount: 0,
	pages: null,
	prevNav: null,
	rotator: null,
	focused: null,

	/** 
	 * @returns SquaredMotion Object
	 * @arg Array _opts
	 * 
	 * instantiates a SquaredMotion object
	 */
	initialize: function(_opts) {
		if (!$(_opts.elem)) { return; }
		this.setOptions(_opts);

		// set our primary DOM elements and cache
		this.elem = $(this.options.elem);
		this.elemID = this.elem.get('id');
		this.pageElem = ($(this.options.pageElem) || this.elem.getElement('div'));

		// get secondary elements and cache
		this.book = this.pageElem.getElement('ul');
		this.pages = $$('#' + this.pageElem.get('id') + '>ul>li');
		this.crumbElem = ($(this.options.crumbElem) || null);
		this.navElem = ($(this.options.navElem) || null);

		// aiight then, lets do this...
		this.buildBook();
		if (this.options.autoPlay) { this.rotatePages(); }
	},

	/** 
	 * @returns null 
	 * 
	 * Used by the autoPlay to set the pages rotating periodically
	 */
	rotatePages: function() {
		var self = this;
		this.rotator = this.navigatePage.periodical((this.options.autoTimer * 1000), this, 1);
		this.elem.addEvent('click', function() {
			$clear(self.rotator);
		});
	},

	/** 
	 * @returns null
	 * 
	 * sets all the necessary parameters and builds the elements needed or requested
	 * by the user
	 */
	buildBook: function() {
		this.options.width = (this.options.width == 'auto' ? this.pageElem.getSize().x : this.options.width);
		this.options.height = (this.options.height == 'auto' ? $$('#' + this.pageElem.get('id') + ' > ul > li')[0].getSize().y : this.options.height);

		// first lets set the dimensions of the pages and get some data
		this.pageCount = this.pages.length;

		// generate overlay parent element
		this.overlayElem = new Element('div', {'id':'sm_overlays'}).inject(this.pageElem);

		// lets get some info about our pages
		this.pages.each(function(_elem) {
			// lets get the height and track the tallest
			var tHeight = _elem.getSize().y;
			this.biggest = (tHeight > this.biggest ? tHeight : this.biggest);

			// uniform widths, please
			var pageWidth = this.options.width - ((_elem.getStyle('padding')).toInt() * 2);
			_elem.setStyle('width', pageWidth);

			// ok, now the overlays... does it have one?
			if (_elem.getElement('.sm-page-overlay')) {
				// yes? lets move it and setup a tween fx
				var el = _elem.getElement('.sm-page-overlay');
				this.overlayElem.adopt(el);
				el.set({'styles': {'width': pageWidth}, 'morph': {'duration': 250, 'transition': 'sine:inOut', 'link':'cancel'}});
			} else {
				// no? still need a stub element for alignment
				new Element('div', {'class':'sm-page-overlay sm-page-overlay-empty'}).inject(this.overlayElem);
			}
		}, this);

		// lets cache the overlays in a var
		this.overlays = this.overlayElem.getElements('.sm-page-overlay');

		// next lets make sure our container (book) has the proper dimensions as well and set Fx.morph
		this.book.setStyle('width', (this.options.width * this.pageCount));
		this.book.set({'morph': {'duration': 800, 'transition': 'quad:out','link':'cancel'}});

		// does the user want the heights uniform?
		if (this.options.uniformHeight) {
			this.pages.setStyle('height', (this.options.height || this.biggest + 'px'));
		}

		// set the Fx.morph for height animation and set the proper dimensions of the parent element
		this.pageElem.set({
			'styles': {
				'width': this.options.width,
				'height': (this.options.height != 'auto' ? this.options.height : this.pages[0].getSize().y)
			},
			'morph': {'duration': 500, 'transition': 'sine:inOut', 'link': 'ignore'}
		});
		
		// does user want to use our navigation?
		if (this.options.useNav) { this.buildNav(); }

		// does user want crumb navigation?
		if (this.options.useCrumbs) { this.buildCrumbs(); }

		if (this.options.hideOverlay) {
			var self = this;
			this.elem.addEvents({
				'mouseenter': function() {
					self.focused = true;
					self.toggleOverlay(true);
				},
				'mouseleave': function() {
					self.focused = false;
					self.toggleOverlay(false);
				}
			});
		} 
		else if (this.overlays.length) {
			// we're set to go, show the first overlay
			this.overlays[0].setStyle('display', 'block');
			this.overlays[0].get('morph').start({'margin-top': '-' + (this.overlays[0].getSize()).y});
		}
	},

	/**
	 * @returns null
	 * 
	 * Builds the crumb parent element and child elements if needed
	 */
	buildCrumbs: function() {
		// do we need to create the crumb parent element and children?
		if (!$(this.crumbElem)) {
			// crumb parent
			this.crumbElem = new Element('ul', {'class':this.options.crumbElemClass});

			if (this.options.crumbPos != '') {
				this.crumbElem.inject(this.elem, (this.options.crumbPos == 'bottom' ? 'bottom' : 'top'));
			} else { 
				this.crumbElem.inject(this.navElem);
			}

			var crumbType = $type(this.options.crumbs);
			for(var i=0; i<this.pageCount; i++) {
				new Element('li').inject(this.crumbElem);
				if ($type(this.options.crumbs[i]) != 'element') {
					var crumbHTML = (crumbType == 'array' ? this.options.crumbs[i] : (this.options.crumbs == 'numeric' ? (i+1) : '&nbsp;'));
					new Element('a', {'html': crumbHTML}).inject(this.crumbElem.getElement('li:last-child'));
				} else {
					this.crumbElem.getElement('li:last-child').adopt(this.options.crumbs[i]);
				}
			}
		}

		// the crumb navigation needs to be an ul element. lets add the actions
		$$('.' + this.options.crumbElemClass + '>li').each(function(_el, _idx) {
			if (_idx == 0) { _el.addClass(this.options.crumbSelectClass); }
			_el.addEvent('click', this.changePage.bind(this, _idx));
		}, this);
	},

	/**
	 * @returns null
	 * 
	 * Builds the navigation links
	 */
	buildNav: function() {
		// check for a navElem first. maybe user wants to do their own navigation
		if (!$(this.navElem)) {
			this.navElem = new Element('div', {'class':this.options.navElemClass}).inject(this.elem, (this.navPos == 'top' ? 'top' : 'bottom'));
		}

		if (this.options.useNav) {
			if (!$('sm_previous')) {
				new Element('a', {'class': this.options.navButtonClass + ' back', 'href': 'javascript:void(0)', 'id':'sm_previous', 'onclick':'return false', 'html': this.options.backText}).inject(this.navElem);
			}
			this.prevNav = $('sm_previous');
	
			if (!$('sm_next')) {
				new Element('a', {'class': this.options.navButtonClass + ' next', 'href': 'javascript:void(0)', 'id':'sm_next', 'onclick':'return false', 'html': this.options.nextText}).inject(this.navElem);
			}
			this.nextNav = $('sm_next');
	
			// add click events for page changign
			this.prevNav.addEvent('click', this.navigatePage.bind(this, false));
			this.nextNav.addEvent('click', this.navigatePage.bind(this, true));
		}
	},

	/**
	 * @returns null
	 * @arg Int _dir
	 * 
	 * event for determining which direction to go
	 */
	navigatePage: function(_dir) {
		var newPage = (_dir ? ((this.currentPage+1) >= this.pageCount ? (this.options.pageWrap ? 0 : this.pageCount-1) : this.currentPage+1) : 
								   (this.currentPage-1) < 0 ? (this.options.pageWrap ? this.pageCount-1 : 0) : (this.currentPage-1));

		new Fx.Tween(this.pages[this.currentPage], {'property':'opacity'}).start(0);

		this.changePage(newPage);
	},

	/**
	 * @returns null
	 * @arg Int _idx
	 * 
	 * event method for changing the viewing page
	 */
	changePage: function(_idx) {
		if (_idx != this.currentPage) {
			var self = this, delayFor = (this.options.uniformHeight ? 0 : this.options.heightDelay);
			this.overlays[this.currentPage].setStyles({'display': 'none', 'margin-top': 0});

			if (this.options.useCrumbs && this.crumbElem) {
				this.crumbElem.getElements('li').each(function(_el, _i) {
					if (_i == _idx) {
						_el.addClass(this.options.crumbSelectClass);
					} else {
						_el.removeClass(this.options.crumbSelectClass);
					}
				}, this);
			}

			if (this.options.uniformHeight !== true) {
				this.pageElem.get('morph').start({ 'height': self.pages[_idx].getSize().y });
			}
			
			(function() {
				new Fx.Tween(self.pages[_idx], {'property':'opacity'}).set('opacity', 0).start(1);

				self.book.get('morph').start({
					'margin-left': '-' + (self.options.width * _idx)
				})
				.chain(function() {
					if (self.options.hideOverlay == true) { return; }
					else self.toggleOverlay(true);
				});
			}).delay(delayFor);
		}

		this.currentPage = _idx;
	},

	/**
	 * @returns null
	 * @arg Int _on
	 * 
	 * toggle the overlay if required
	 */
	toggleOverlay: function(_on) {
		var oElem = this.overlays[this.currentPage];
		oElem.setStyle('display', 'block');
		oElem.morph({'margin-top': '-' + (_on ? (oElem.getSize()).y : 0)});
	}
});
