/**
 * MoonBats - Sliding Nestable Tabs with Ajax
 *
 * @semantics:  (MOO)-(N)estable-(TABS) + some artistic licence with respect to the ordering of letters
 *
 * Based on 'SimpleTabs' by Harald Kirschner and various sliding tab thingies that aren't nestable,
 * don't work with MooTools 1.2 and don't work with IE6.
 *
 * Tested on Opera, FF, Safari, Chrome and IE6,7,8
 *
 * @example
 *
 *	var tabs = new MoonBats($('tab-element'), {
 * 		selector: 'h2.tab-tab'
 *	});
 *
 * @version		1.0
 *
 * @license		MIT License
 * @author		Mike Beggs
 * @copyright	2009 Author
 */
var MoonBats = new Class({

	Implements: [Events, Options],

	/**
	 * Options
	 */
	options: {
		id:0,
		show: null,
		inverted: false, // Puts the tabs after the content. Why? Dunno
		selector: '.tab-tab',
		headingLevel: 'h2', // Insert a heading for each tab. Useful to maintain doc structure and for print layout
		classWrapper: 'tab-wrapper',
		classScroller: 'tab-scroller',
		classMenu: 'tab-menu',
		classContainer: 'tab-container',
		hashCookie: false,
		onSelect: function(toggle, container, index) {

		},
		onDeselect: function(toggle, container, index) {

		},
		onRequest: function(toggle, container, index) {
			container.addClass('tab-ajax-loading');
		},
		onComplete: function(toggle, container, index) {
			container.removeClass('tab-ajax-loading');
		},
		onFailure: function(toggle, container, index) {
			container.removeClass('tab-ajax-loading');
		},
		onAdded: Class.empty,
		getContent: null,
		ajaxOptions: {},
		cache: true,
		slideEffect: { transition: Fx.Transitions.Expo.easeInOut,
		  duration: 500
		},
		onBuilt: function(moonbat) {

		},
		fx: null,
		animate: true,
		useHistoryManager: false // TODO: Possibly a bit flakey. Can't exactly pin down what isn't right. YMMV
	},

	/**
	 * Constructor
	 *
	 * @param {Element} The parent Element that holds the tab elements
	 * @param {Object} Options
	 */
	initialize: function(element, options) {
		this.doingSync = false;
		this.element = $(element);
		this.setOptions(options);
		var selectedTab = (this.options.hashCookie ? moonbatsHashCookie.get(this.options.id) : Cookie.read('tab_'+this.options.options.id) );
		if (!selectedTab) { selectedTab = 0; }
		if(this.options.show == null) {
			this.options.show = selectedTab;
		}
		this.selected = null;
		if(this.options.useHistoryManager){
			this.historyKey = 'tab_'+this.options.id;
			this.history = new History.Route({
				defaults: [0],
				pattern: this.historyKey + '\\((\\d+)\\)',
				generate: function(values) {
					return [this.historyKey, '(', values[0], ')'].join('')
				}.bind(this),
				onMatch: function(values, defaults) {
						this.select(parseInt(values[0]));
				}.bind(this)
			});
		}

		this.build();
	},

	build: function() {
		this.tabs = [];

		this.fragmentMap = new Hash;

		this.menu = new Element('ul', {'id':'tabmenu_'+this.options.id,'class': this.options.classMenu});
		this.wrapper = new Element('div', {'class': this.options.classWrapper});
		this.scroller = new Element('div', {'class': this.options.classScroller});

		this.element.getElements(this.options.selector).each(function(el) {
			var content = el.get('href') || (this.options.getContent ? this.options.getContent.call(this, el) : el.getNext());
			this.addTab(el.innerHTML, el.title || el.innerHTML, content, el.id);
		}, this);
		this.wrapper.empty().adopt(this.scroller);
		if(this.options.inverted) {
			this.element.empty().adopt(this.wrapper, this.menu);
		} else {
			this.element.empty().adopt(this.menu, this.wrapper);
		}

		if(this.options.animate) this.fx = new Fx.Scroll(this.wrapper, this.options.slideEffect);

		this.tabWidth = this.tabs[0].container.getSize().x + this.tabs[0].container.getStyle('margin-right').toInt() + this.tabs[0].container.getStyle('margin-left').toInt();

		var hashpos = location.href.indexOf('#');
		var fragmentID = (hashpos != -1 ? location.href.substr(hashpos+1) : 0);

		if($type(fragmentID) == 'string') {
			var whut = fragmentID.split('.');
			var woot = this.fragmentMap.keyOf(0).split('.');
			while(whut.length > woot.length) {
				whut.pop();
			}
			var goToFragment = whut.join('.');
			var toot = this.fragmentMap.get(goToFragment);
			if(toot !== null) {
				this.options.show = toot;
				if(fragmentID == goToFragment) {
					var elTargetToRename = $(fragmentID);
					var renamedTargetID = elTargetToRename.id;
					elTargetToRename.set('id', '_'+renamedTargetID);
					window.setTimeout(function() {	elTargetToRename.set('id', renamedTargetID); } , 1000);
				}
			}
		}
		
		this.options.show = this.options.show % this.tabs.length;

		var save = this.options.animate;
			this.options.animate = false;
			if (this.tabs.length) this.select(this.options.show);
		this.options.animate = save;
		this.fireEvent('onBuilt', this);
	},

	/**
	 * Add a new tab at the end of the tab menu
	 *
	 * @param {String} inner Text
	 * @param {String} Title
	 * @param {Element|String} Content Element or URL for Ajax
	 */
	addTab: function(text, title, content, elid) {
		var grab = $(content);

		if(!elid) elid = 'tab-'+this.options.id+'-'+this.tabs.length;

		this.fragmentMap.set(elid, this.tabs.length)

		new Element(this.options.headingLevel, {id: elid, html: text}).addClass('tab-hiddenheading').inject(this.scroller);

		var container = (grab || new Element('div'))
			.addClass(this.options.classContainer)
			.inject(this.scroller);
			
		var pos = this.tabs.length;
		
		var evt = (this.options.hover) ? 'mouseenter' : 'click';
		var tab = {
			container: container,
			toggle: new Element('li').grab(new Element('a', {
				href: '#'+elid,
				title: title
			}).grab(
				new Element('span', {html: text})
			)).addEvent(evt, this.onClick.bindWithEvent(this, [pos])).inject(this.menu)
		};
		if (!grab && $type(content) == 'string') tab.url = content;
		this.tabs.push(tab);
		return this.fireEvent('onAdded', [tab.toggle, tab.container, pos]);
	},

	onClick: function(evt, index) {
		this.select(index);
		return false;
	},
	sync: function(index){
		if(index == this.selected) return;
		this.doingSync = true; // Don't fire onSelect event
		this.select(index);
		this.doingSync = false;
	},
	/**
	 * Select the tab via tab-index
	 *
	 * @param {Number} Tab-index
	 */
	select: function(index) {
		if (this.selected === index || !this.tabs[index]) return this;
		if (this.ajax) this.ajax.cancel().removeEvents();
		var tab = this.tabs[index];
		if(this.options.hashCookie) {
			moonbatsHashCookie.set(this.options.id, index);
		} else {
			Cookie.write('tab_'+this.options.id, index, {duration: false, path: '/'})
		}
		
		var params = [tab.toggle, tab.container, index];
		if (this.selected !== null) {
			var current = this.tabs[this.selected];
			if (this.ajax && this.ajax.running) this.ajax.cancel();
			params.extend([current.toggle, current.container, this.selected]);

			current.toggle.removeClass('tab-selected');
			if(!this.doingSync) this.fireEvent('onDeselect', [current.toggle, current.container, this.selected]);
		}

		tab.toggle.addClass('tab-selected');
		if(this.options.animate) {
			this.fx.cancel();
			this.fx.toElement(tab.container);
		} else {
			this.wrapper.scrollTo(index * this.tabWidth, 0);
		}

		if(this.options.useHistoryManager){

			this.history.setValue(0, index);
			this.history.defaults=[index];
		}

		if(!this.doingSync) this.fireEvent('onSelect', params);
		if (tab.url && (!tab.loaded || !this.options.cache)) {
			this.ajax = this.ajax || new Request.HTML();
			this.ajax.setOptions({
				url: tab.url,
				method: 'get',
				update: tab.container,
				onFailure: this.fireEvent.pass(['onFailure', params], this),
				onComplete: function(resp) {
					tab.loaded = true;
					this.fireEvent('onComplete', params);
				}.bind(this)
			}).setOptions(this.options.ajaxOptions);
			this.ajax.send();
			this.fireEvent('onRequest', params);
		}
		this.selected = index;
		return this;
	},

	next: function() {

		this.select((this.selected + 1) % this.tabs.length);
	},

	previous: function() {

		this.select((this.selected - 1 + this.tabs.length) % this.tabs.length); // Stupid javascript and its broken modulo
	}

});