/*global jQuery */
/*jslint
	white: true, undef: true, nomen: true, eqeqeq: true, plusplus: true,
	bitwise: true, regexp: true, onevar: true, newcap: true,
	browser: true, immed: true
*/


// version 2010-01-04
var Zing = function (settings) {
	var $ = jQuery, // Just in case $ is something else globally
		construct,
		
		// User-definable settings
		duration, autoPlay, startAt, currentClass, transitions, container,
		classZingPane, classZingNav, classZingPlay, classZingPause, classZingNext, classZingPrev,


		// Private instance variables
		index, panes, paneNames, paneIndices, visiblePane, transitioning,
		buttons, playing, interval,
		
		// Public methods
		play, pause, prev, next, goTo, which,
		
		// Private methods
		setupDefaults, setupCss, setupPanes, setupButtons, show, advance;
	

	// Public Methods
	
	/**
	 * The name of the current pane.
	 */
	which = function () {
		return paneNames[index];
	};
	
	/**
	 * Start automatic cycling through panes.
	 * @param  wait  boolean  Delay before advancing.
	 */
	play = function (wait) {
		playing = true;
		buttons.pause.css({display: 'block'});
		buttons.play.css({display: 'none'});
		if (wait !== true) {
			advance();
		}
		interval = setInterval(advance, duration);
	};
	
	/**
	 * Stop automatic cycling through panes.
	 */
	pause = function () {
		playing = false;
		buttons.pause.css({display: 'none'});
		buttons.play.css({display: 'block'});
		clearInterval(interval);
	};
	
	/**
	 * Go to the previous pane.
	 */
	prev = function () {
		goTo(paneNames[(index - 1 + paneNames.length) % paneNames.length]);
	};
	
	/**
	 * Go to the next pane.
	 */
	next = function () {
		goTo(paneNames[(index + 1) % paneNames.length]);
	};

	/**
	 * Show a specific pane.
	 */
	goTo = function (name) {
		if (!transitioning) {
			pause();
			show(name);
		}
	};
	
	
	// Private Methods
	
	/*
	 * Show the next pane.
	 * Private counterpart of next.
	 */
	advance = function () {
		show(paneNames[(index + 1) % paneNames.length]);
	};
	
	/*
	 * Show a specific pane.
	 * Private counterpart of goTo.
	 */
	show = function (name) {
	
		var callback,
			callsToDo,
			callsDone,
			fromPane,
			toPane,
			done,
			i;
		
		fromPane = visiblePane;
		toPane = panes[name];
		
		if (toPane && toPane !== fromPane) {

			callsDone = 0;
			callsToDo = 0;
			
			callsToDo += toPane.transitions.show.length;
			if (fromPane) {
				callsToDo += fromPane.transitions.hide.length;
			}
			
			visiblePane = toPane;
			index = paneIndices[name];
			
			// block controls during the transition
			transitioning = callsToDo > 0;
			
			done = function () {
				if (fromPane) {
					$(fromPane.pane).css({display: 'none'});
				}
			};

			// When both transitions are done, we'll hide the pane we just transitioned from.
			callback = function () {
				callsDone += 1;
				if (callsDone === callsToDo) {
					done();
					transitioning = false;
				}
			};

			// But first, show the destination pane and do its transition.
			$(toPane.pane).css({display: 'block', zIndex: 1});
			for (i = 0; i < toPane.transitions.show.length; i += 1) {
				toPane.transitions.show[i].call(toPane, callback);
			}

			// If there is a current pane, do its transition...
			if (fromPane) {
				$(fromPane.pane).css({zIndex: 0});
				for (i = 0; i < fromPane.transitions.hide.length; i += 1) {
					fromPane.transitions.hide[i].call(fromPane, callback);
				}
			}
			
			// select the current tab
			for (i in panes) {
				if (panes.hasOwnProperty(i)) {
					if (i === name) {
						$(panes[i].tab).addClass(currentClass);
					} else {
						$(panes[i].tab).removeClass(currentClass);
					}
				}
			}
			
			if (callsToDo === 0) {
				done();
			}
			
		}
	};
	
	/*
	 * Grab the options, and fall back on defaults.
	 */
	setupDefaults = function () {
		container = settings.container;
		duration = settings.duration || 10000;
		autoPlay = (typeof settings.autoPlay !== 'undefined') ? settings.autoPlay : true;
		startAt = settings.startAt || null;
		currentClass = settings.currentClass || 'current';
		transitions = settings.transitions || {};
		
		classZingPane = settings.classZingPane || 'zing-pane';
		classZingNav = settings.classZingNav || 'zing-nav';
		classZingPlay = settings.classZingPlay || 'zing-play';
		classZingPause = settings.classZingPause || 'zing-pause';
		classZingNext = settings.classZingNext || 'zing-next';
		classZingPrev = settings.classZingPrev || 'zing-prev';
		
		if (!container) {
			throw 'undefined zing container';
		} else if (container.charAt(0) !== '#') {
			throw 'zing container must be a string that begins with "#" to indicate an id.';
		}
	};
	
	/*
	 * Use JS for the CSS that should only be set when JS is enabled.
	 */
	setupCss = function () {
		$(container + ' .' + classZingPane).css({
			position: 'absolute',
			display: 'none'
		});
		$(container + ' .' + classZingNav).css({
			display: 'block'
		});
	};
	
	/*
	 * Associate tab elements with their pane elements, and assign transitions.
	 */
	setupPanes = function () {
		var i, name, item, click, arrayize;
		
		click = function (name) {
			$(item.tab).click(function () {
				goTo(name);
			});
		};
		
		arrayize = function (a, b) {
			if (typeof a[b] === 'function') {
				a[b] = [a[b]];
			} else if (!a[b]) {
				a[b] = [];
			}
		};
	
		arrayize(transitions, 'show');
		arrayize(transitions, 'hide');
		
		for (name in settings.panes) {
			if (settings.panes.hasOwnProperty(name)) {
				item = settings.panes[name];
				
				// let each item know the name by which it is called
				item.name = name;
				
				paneIndices[name] = paneNames.length;
				paneNames.push(name);
				panes[name] = item;
				
				if (!item.transitions) {
					item.transitions = {};
				}
				
				arrayize(item.transitions, 'show');
				arrayize(item.transitions, 'hide');

				for (i = 0; i < transitions.hide.length; i += 1) {
					item.transitions.hide.push(transitions.hide[i]);
				}
				
				for (i = 0; i < transitions.show.length; i += 1) {
					item.transitions.show.push(transitions.show[i]);
				}
				
				click(name);
			}
		}
		
	};
	
	/*
	 * Set up the play, pause, previous, and next buttons.
	 */
	setupButtons = function () {
		buttons = {
			play: $(container + ' .' + classZingPlay),
			pause: $(container + ' .' + classZingPause),
			next: $(container + ' .' + classZingNext),
			prev: $(container + ' .' + classZingPrev)
		};
		buttons.play.click(play);
		buttons.pause.click(pause);
		buttons.prev.click(prev);
		buttons.next.click(next);
	};
	
	/*
	 * Pseudo-constructor.
	 */
	construct = function () {
	
		panes = {};
		paneNames = [];
		paneIndices = {};
		transitioning = false;
		
		setupDefaults();
		setupCss();
		setupPanes();
		setupButtons();
		
		index = startAt ? paneIndices[startAt] : 0;

		show(paneNames[index]);
		
		if (autoPlay) {
			play(true);
		} else {
			pause();
		}
	};


	// Publicize methods
	this.play = play;
	this.pause = pause;
	this.prev = prev;
	this.next = next;
	this.goTo = goTo;
	this.which = which;
	
	
	// Call that pseudo-constructor.
	construct();
	
};
