text: dojo.provide("dojox.layout.RotatorContainer");
  
  dojo.require("dojo.fx");
  dojo.require("dijit.layout.StackContainer");
  dojo.require("dijit.layout.StackController");
  dojo.require("dijit._Widget");
  dojo.require("dijit._Templated");
  dojo.require("dijit._Contained");
  
  dojo.declare("dojox.layout.RotatorContainer",
  	[dijit.layout.StackContainer, dijit._Templated], {
  	// summary:
  	//		Extends a StackContainer to automatically transition between children
  	//		and display navigation in the form of tabs or a pager.
  	//
  	// description:
  	// 		The RotatorContainer cycles through the children with a transition.
  	//
  	// published topics:
  	// 		[widgetId]-update - Notifies pager(s) that a child has changed.
  	//			Parameters:
  	//				/*boolean*/ playing - true if playing, false if paused
  	//				/*int*/ current     - current selected child
  	//				/*int*/ total       - total number of children
  	//
  	// example:
  	// |	<div dojoType="dojox.layout.RotatorContainer" id="myRotator" showTabs="true" autoStart="true" transitionDelay="5000">
  	// |		<div id="pane1" dojoType="dijit.layout.ContentPane" title="1">
  	// |			Pane 1!
  	// |		</div>
  	// |		<div id="pane2" dojoType="dijit.layout.ContentPane" title="2">
  	// |			Pane 2!
  	// |		</div>
  	// |		<div id="pane3" dojoType="dijit.layout.ContentPane" title="3" transitionDelay="10000">
  	// |			Pane 3 with overrided transitionDelay!
  	// |		</div>
  	// |	</div>
  
  	templateString: '<div class="dojoxRotatorContainer"><div dojoAttachPoint="tabNode"></div><div class="dojoxRotatorPager" dojoAttachPoint="pagerNode"></div><div class="dojoxRotatorContent" dojoAttachPoint="containerNode"></div></div>',
  
  	// showTabs: Boolean
  	//		Sets the display of the tabs.  The tabs are actually a StackController.
  	//		The child's title is used for the tab's label.
  	showTabs: true,
  
  	// transitionDelay: int
  	//		The delay in milliseconds before transitioning to the next child.
  	transitionDelay: 5000,
  
  	// transition: String
  	//		The type of transition to perform when switching children.
  	//		A null transition will transition instantly.
  	transition: "fade",
  
  	// transitionDuration: int
  	//		The duration of the transition in milliseconds.
  	transitionDuration: 1000,
  
  	// autoStart: Boolean
  	//		Starts the timer to transition children upon creation.
  	autoStart: true,
  
  	// suspendOnHover: Boolean
  	//		Pause the rotator when the mouse hovers over it.
  	suspendOnHover: false,
  
  	// pauseOnManualChange: Boolean
  	//		Pause the rotator when the tab is changed or the pager's next/previous
  	//		buttons are clicked.
  	pauseOnManualChange: null,
  
  	// reverse: Boolean
  	//		Causes the rotator to rotate in reverse order.
  	reverse: false,
  
  	// pagerId: String
  	//		ID the pager widget.
  	pagerId: "",
  
  	// cycles: int
  	//		Number of cycles before pausing.
  	cycles: -1,
  
  	// pagerClass: String
  	//		The declared Class of the Pager used for this Widget
   	pagerClass: "dojox.layout.RotatorPager",
  
  	postCreate: function(){
  		// summary: Initializes the DOM nodes, tabs, and transition stuff.
  		this.inherited(arguments);
  
  		// force this DOM node to a relative position and make sure the children are absolute positioned
  		dojo.style(this.domNode, "position", "relative");
  
  		// validate the cycles counter
  		if(this.cycles-0 == this.cycles && this.cycles != -1){
  			// we need to add 1 because we decrement cycles before the animation starts
  			this.cycles++;
  		}else{
  			this.cycles = -1;
  		}
  
  		// if they didn't specify the pauseOnManualChange, then we want it to be the opposite of
  		// the suspendOnHover since it doesn't make sense to do both, unless you really want to
  		if(this.pauseOnManualChange === null){
  			this.pauseOnManualChange = !this.suspendOnHover;
  		}
  
  		// create the stack controller if we are using tabs
  		var id = this.id || "rotator"+(new Date()).getTime(),
  			sc = new dijit.layout.StackController({ containerId:id }, this.tabNode);
  		this.tabNode = sc.domNode;
  		this._stackController = sc;
  		dojo.style(this.tabNode, "display", this.showTabs ? "" : "none");
  
  		// if the controller's tabs are clicked, check if we should pause and reset the cycle counter
  		this.connect(sc, "onButtonClick","_manualChange");
  
  		// set up our topic listeners
  		this._subscriptions = [
  			dojo.subscribe(this.id+"-cycle", this, "_cycle"),
  			dojo.subscribe(this.id+"-state", this, "_state")
  		];
  
  		// make sure the transition duration isn't less than the transition delay
  		var d = Math.round(this.transitionDelay * 0.75);
  		if(d < this.transitionDuration){
  			this.transitionDuration = d;
  		}
  
  		// wire up the mouse hover events
  		if(this.suspendOnHover){
  			this.connect(this.domNode, "onmouseover", "_onMouseOver");
  			this.connect(this.domNode, "onmouseout", "_onMouseOut");
  		}
  	},
  
  	startup: function(){
  		// summary: Initializes the pagers.
  		if(this._started){ return; }
  
  		// check if the pager is defined within the rotator container
  		var c = this.getChildren();
  		for(var i=0, len=c.length; i<len; i++){
  			if(c[i].declaredClass == this.pagerClass){
  				this.pagerNode.appendChild(c[i].domNode);
  				break;
  			}
  		}
  
  		// process the child widgets
  		this.inherited(arguments);
  
  		// check if we should start automatically
  		if(this.autoStart){
  			// start playing
  			setTimeout(dojo.hitch(this, "_play"), 10);
  		}else{
  			// update the pagers with the initial state
  			this._updatePager();
  		}
  	},
  
  	destroy: function(){
  		// summary: Unsubscribe to all of our topics
  		dojo.forEach(this._subscriptions, dojo.unsubscribe);
  		this.inherited(arguments);
  	},
  
  	_setShowTabsAttr: function(/*anything*/value){
  		this.showTabs = value;
  		dojo.style(this.tabNode, "display", value ? "" : "none");
  	},
  
  	_updatePager: function(){
  		// summary: Notify the pager's current and total numbers.
  		var c = this.getChildren();
  		dojo.publish(this.id+"-update", [this._playing, dojo.indexOf(c, this.selectedChildWidget)+1, c.length]);
  	},
  
  	_onMouseOver: function(){
  		// summary: Triggered when the mouse is moved over the rotator container.
  
  		// temporarily suspend the cycling, but don't officially pause it
  		this._resetTimer();
  		this._over = true;
  	},
  
  	_onMouseOut: function(){
  		// summary: Triggered when the mouse is moved off the rotator container.
  		this._over = false;
  
  		// if we were playing, resume playback in 200ms
  		// we need to wait because we may be moused over again right away
  		if(this._playing){
  			clearTimeout(this._timer);
  			this._timer = setTimeout(dojo.hitch(this, "_play", true), 200);
  		}
  	},
  
  	_resetTimer: function(){
  		// summary: Resets the timer used to start the next transition.
  		clearTimeout(this._timer);
  		this._timer = null;
  	},
  
  	_cycle: function(/*boolean or int*/next){
  		// summary: Cycles to the next/previous child.
  
  		// if next is an int, then _cycle() was called via a timer
  		// if next is a boolean, then _cycle() was called via the next/prev buttons, stop playing and reset cycles
  		if(next instanceof Boolean || typeof next == "boolean"){
  			this._manualChange();
  		}
  
  		var c = this.getChildren(),
  			len = c.length,
  			i = dojo.indexOf(c, this.selectedChildWidget) + (next === false || (next !== true && this.reverse) ? -1 : 1);
  		this.selectChild(c[(i < len ? (i < 0 ? len-1 : i) : 0)]);
  		this._updatePager();
  	},
  
  	_manualChange: function(){
  		// summary: This function is only called when a manual change occurs in which
  		//   case we may need to stop playing and we need to reset the cycle counter
  		if(this.pauseOnManualChange){
  			this._playing = false;
  		}
  		this.cycles = -1;
  	},
  
  	_play: function(skip){
  		// summary: Schedules the next transition.
  		this._playing = true;
  		this._resetTimer();
  		if(skip !== true && this.cycles>0){
  			this.cycles--;
  		}
  		if(this.cycles==0){
  			this._pause();
  		}else if((!this.suspendOnHover || !this._over) && this.transitionDelay){
  			// check if current pane has a delay
  			this._timer = setTimeout(dojo.hitch(this, "_cycle"), this.selectedChildWidget.domNode.getAttribute("transitionDelay") || this.transitionDelay);
  		}
  		this._updatePager();
  	},
  
  	_pause: function(){
  		// summary: Clears the transition timer and pauses the rotator.
  		this._playing = false;
  		this._resetTimer();
  	},
  
  	_state: function(playing){
  		// summary: Fired when the play/pause pager button is toggled.
  		if(playing){
  			// since we were manually changed, disable the cycle counter
  			this.cycles = -1;
  			this._play();
  		}else{
  			this._pause();
  		}
  	},
  
  	_transition: function(/*dijit._Widget*/ next, /*dijit._Widget*/ prev){
  		// summary: Dispatches the appropriate transition.
  		this._resetTimer();
  
  		// check if we have anything to transition
  		if(prev && this.transitionDuration){
  			switch(this.transition){
  				case "fade": this._fade(next, prev); return;
  			}
  		}
  
  		this._transitionEnd();
  		this.inherited(arguments);
  	},
  
  	_transitionEnd: function(){
  		if(this._playing){
  			this._play();
  		}else{
  			this._updatePager();
  		}
  	},
  
  	_fade: function(/*dijit._Widget*/ next, /*dijit._Widget*/ prev){
  		// summary: Crossfades two children.
  		this._styleNode(prev.domNode, 1, 1);
  		this._styleNode(next.domNode, 0, 2);
  
  		// show the next child and make sure it's sized properly
  		this._showChild(next);
  		if(this.doLayout && next.resize){
  			next.resize(this._containerContentBox || this._contentBox);
  		}
  
  		// create the crossfade animation
  		var args = { duration:this.transitionDuration },
  			anim = dojo.fx.combine([
  				dojo["fadeOut"](dojo.mixin({node:prev.domNode}, args)),
  				dojo["fadeIn"](dojo.mixin({node:next.domNode}, args))
  			]);
  
  		this.connect(anim, "onEnd", dojo.hitch(this,function(){
  			this._hideChild(prev);
  			this._transitionEnd();
  		}));
  
  		anim.play();
  	},
  
  	_styleNode: function(/*DOMnode*/node, /*number*/opacity, /*int*/zIndex){
  		// summary: Helper function to style the children.
  		dojo.style(node, "opacity", opacity);
  		dojo.style(node, "zIndex", zIndex);
  		dojo.style(node, "position", "absolute");
  	}
  });
  
  dojo.declare("dojox.layout.RotatorPager", [dijit._Widget, dijit._Templated, dijit._Contained], {
  	// summary:
  	//		Defines controls used to manipulate a RotatorContainer
  	//
  	// description:
  	//		A pager can be defined one of two ways:
  	//			* Externally of the RotatorContainer's template and tell the
  	//			RotatorPager the rotatorId of the RotatorContainer
  	//			* As a direct descendant of the RotatorContainer (i.e. inside the
  	//			RotatorContainer's template)
  	//
  	//		The pager can contain the following components:
  	//			* Previous button
  	//				- Must be a dijit.form.Button
  	//				- dojoAttachPoint must be named "previous"
  	//			* Next button
  	//				- Must be a dijit.form.Button
  	//				- dojoAttachPoint must be named "next"
  	//			* Play/Pause toggle button
  	//				- Must be a dijit.form.ToggleButton
  	//				- dojoAttachPoint must be named "playPause"
  	//				- Use iconClass to specify toggled state
  	//			* Current child #
  	//				- dojoAttachPoint must be named "current"
  	//			* Total # of children
  	//				- dojoAttachPoint must be named "total"
  	//
  	//		You can choose to exclude specific controls as well as add elements
  	//		for styling.
  	//
  	//		Should you need a pager, but don't want to use Dijit buttons, you can
  	//		write your own pager widget and just wire it into the topics.  The
  	//		topic names are prefixed with the widget ID of the RotatorContainer.
  	//		Notifications are received from and sent to the RotatorContainer as
  	//		well as other RotatorPagers.
  	//
  	// published topics:
  	//		[widgetId]-cycle - Notify that the next or previous button was pressed.
  	//			Parameters:
  	//				/*boolean*/ next - true if next, false if previous
  	//		[widgetId]-state - Notify that the play/pause button was toggled.
  	//			Parameters:
  	//				/*boolean*/ playing - true if playing, false if paused
  	//
  	// example:
  	//		A pager with the current/total children and previous/next buttons.
  	// |	<div dojoType="dojox.layout.RotatorPager" rotatorId="myRotator">
  	// |		<button dojoType="dijit.form.Button" dojoAttachPoint="previous">Prev</button>
  	// |		<span dojoAttachPoint="current"></span> / <span dojoAttachPoint="total"></span>
  	// |		<button dojoType="dijit.form.Button" dojoAttachPoint="next">Next</button>
  	// |	</div>
  	//
  	// example:
  	//		A pager with only a play/pause toggle button.
  	// |	<div dojoType="dojox.layout.RotatorPager" rotatorId="myRotator">
  	// |		<button dojoType="dijit.form.ToggleButton" dojoAttachPoint="playPause"></button>
  	// |	</div>
  	//
  	// example:
  	//		A pager styled with iconClass.
  	// |	<div dojoType="dojox.layout.RotatorPager" class="rotatorIcons" rotatorId="myRotator">
  	// |		<button dojoType="dijit.form.Button" iconClass="previous" dojoAttachPoint="previous">Prev</button>
  	// |		<button dojoType="dijit.form.ToggleButton" iconClass="playPause" dojoAttachPoint="playPause"></button>
  	// |		<button dojoType="dijit.form.Button" iconClass="next" dojoAttachPoint="next">Next</button>
  	// |		<span dojoAttachPoint="current"></span> / <span dojoAttachPoint="total"></span>
  	// |	</div>
  
  	widgetsInTemplate: true,
  
  	// rotatorId: int
  	//		The ID of the rotator this pager is tied to.
  	//		Only required if defined outside of the RotatorContainer's container.
  	rotatorId: "",
  
  	postMixInProperties: function(){
  		this.templateString = "<div>" + this.srcNodeRef.innerHTML + "</div>";
  	},
  
  	postCreate: function(){
  		var p = dijit.byId(this.rotatorId) || this.getParent();
  		if(p && p.declaredClass == "dojox.layout.RotatorContainer"){
  			if(this.previous){
  				dojo.connect(this.previous, "onClick", function(){
  					dojo.publish(p.id+"-cycle", [false]);
  				});
  			}
  			if(this.next){
  				dojo.connect(this.next, "onClick", function(){
  					dojo.publish(p.id+"-cycle", [true]);
  				});
  			}
  			if(this.playPause){
  				dojo.connect(this.playPause, "onClick", function(){
  					this.set('label', this.checked ? "Pause" : "Play");
  					dojo.publish(p.id+"-state", [this.checked]);
  				});
  			}
  			this._subscriptions = [
  				dojo.subscribe(p.id+"-state", this, "_state"),
  				dojo.subscribe(p.id+"-update", this, "_update")
  			];
  		}
  	},
  
  	destroy: function(){
  		// summary: Unsubscribe to all of our topics
  		dojo.forEach(this._subscriptions, dojo.unsubscribe);
  		this.inherited(arguments);
  	},
  
  	_state: function(/*boolean*/playing){
  		// summary: Updates the display of the play/pause button
  		if(this.playPause && this.playPause.checked != playing){
  			this.playPause.set("label", playing ? "Pause" : "Play");
  			this.playPause.set("checked", playing);
  		}
  	},
  
  	_update: function(/*boolean*/playing, /*int*/current, /*int*/total){
  		// summary: Updates the pager's play/pause button, current child, and total number of children.
  		this._state(playing);
  		if(this.current && current){
  			this.current.innerHTML = current;
  		}
  		if(this.total && total){
  			this.total.innerHTML = total;
  		}
  	}
  });
  