define([
	"dojo/_base/array", // array.forEach array.indexOf array.some
	"dojo/cookie", // cookie
	"dojo/_base/declare", // declare
	"dojo/dom-class", // domClass.add domClass.replace
	"dojo/dom-construct",
	"dojo/has", // has("dijit-legacy-requires")
	"dojo/_base/lang", // lang.extend
	"dojo/on",
	"dojo/ready",
	"dojo/topic", // publish
	"dojo/when",
	"../registry", // registry.byId
	"../_WidgetBase",
	"./_LayoutWidget"
], function(array, cookie, declare, domClass, domConstruct, has, lang, on, ready, topic, when, registry, _WidgetBase, _LayoutWidget){

	// module:
	//		dijit/layout/StackContainer

	// Back compat w/1.6, remove for 2.0
	if(has("dijit-legacy-requires")){
		ready(0, function(){
			var requires = ["dijit/layout/StackController"];
			require(requires);	// use indirection so modules not rolled into a build
		});
	}

	var StackContainer = declare("dijit.layout.StackContainer", _LayoutWidget, {
		// summary:
		//		A container that has multiple children, but shows only
		//		one child at a time
		//
		// description:
		//		A container for widgets (ContentPanes, for example) That displays
		//		only one Widget at a time.
		//
		//		Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild
		//
		//		Can be base class for container, Wizard, Show, etc.
		//
		//		See `StackContainer.ChildWidgetProperties` for details on the properties that can be set on
		//		children of a `StackContainer`.

		// doLayout: Boolean
		//		If true, change the size of my currently displayed child to match my size
		doLayout: true,

		// persist: Boolean
		//		Remembers the selected child across sessions
		persist: false,

		baseClass: "dijitStackContainer",

		/*=====
		// selectedChildWidget: [readonly] dijit._Widget
		//		References the currently selected child widget, if any.
		//		Adjust selected child with selectChild() method.
		selectedChildWidget: null,
		=====*/

		buildRendering: function(){
			this.inherited(arguments);
			domClass.add(this.domNode, "dijitLayoutContainer");
		},

		postCreate: function(){
			this.inherited(arguments);
			this.own(
				on(this.domNode, "keydown", lang.hitch(this, "_onKeyDown"))
			);
		},

		startup: function(){
			if(this._started){
				return;
			}

			var children = this.getChildren();

			// Setup each page panel to be initially hidden
			array.forEach(children, this._setupChild, this);

			// Figure out which child to initially display, defaulting to first one
			if(this.persist){
				this.selectedChildWidget = registry.byId(cookie(this.id + "_selectedChild"));
			}else{
				array.some(children, function(child){
					if(child.selected){
						this.selectedChildWidget = child;
					}
					return child.selected;
				}, this);
			}
			var selected = this.selectedChildWidget;
			if(!selected && children[0]){
				selected = this.selectedChildWidget = children[0];
				selected.selected = true;
			}

			// Publish information about myself so any StackControllers can initialize.
			// This needs to happen before this.inherited(arguments) so that for
			// TabContainer, this._contentBox doesn't include the space for the tab labels.
			topic.publish(this.id + "-startup", {children: children, selected: selected, textDir: this.textDir});

			// Startup each child widget, and do initial layout like setting this._contentBox,
			// then calls this.resize() which does the initial sizing on the selected child.
			this.inherited(arguments);
		},

		resize: function(){
			// Overrides _LayoutWidget.resize()
			// Resize is called when we are first made visible (it's called from startup()
			// if we are initially visible). If this is the first time we've been made
			// visible then show our first child.
			if(!this._hasBeenShown){
				this._hasBeenShown = true;
				var selected = this.selectedChildWidget;
				if(selected){
					this._showChild(selected);
				}
			}
			this.inherited(arguments);
		},

		_setupChild: function(/*dijit/_WidgetBase*/ child){
			// Overrides _LayoutWidget._setupChild()

			// For aria support, wrap child widget in a <div role="tabpanel">
			var childNode = child.domNode,
				wrapper = domConstruct.place(
					"<div role='tabpanel' class='" + this.baseClass + "ChildWrapper dijitHidden'>",
					child.domNode,
					"replace"),
				label = child["aria-label"] || child.title || child.label;
			if(label){
				// setAttribute() escapes special chars, and if() statement avoids setting aria-label="undefined"
				wrapper.setAttribute("aria-label", label);
			}
			domConstruct.place(childNode, wrapper);
			child._wrapper = wrapper;	// to set the aria-labelledby in StackController

			this.inherited(arguments);

			// child may have style="display: none" (at least our test cases do), so remove that
			if(childNode.style.display == "none"){
				childNode.style.display = "block";
			}

			// remove the title attribute so it doesn't show up when i hover over a node
			child.domNode.removeAttribute("title");
		},

		addChild: function(/*dijit/_WidgetBase*/ child, /*Integer?*/ insertIndex){
			// Overrides _Container.addChild() to do layout and publish events

			this.inherited(arguments);

			if(this._started){
				topic.publish(this.id + "-addChild", child, insertIndex);	// publish

				// in case the tab titles have overflowed from one line to two lines
				// (or, if this if first child, from zero lines to one line)
				// TODO: w/ScrollingTabController this is no longer necessary, although
				// ScrollTabController.resize() does need to get called to show/hide
				// the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild().
				// If this is updated to not layout [except for initial child added / last child removed], update
				// "childless startup" test in StackContainer.html to check for no resize event after second addChild()
				this.layout();

				// if this is the first child, then select it
				if(!this.selectedChildWidget){
					this.selectChild(child);
				}
			}
		},

		removeChild: function(/*dijit/_WidgetBase*/ page){
			// Overrides _Container.removeChild() to do layout and publish events

			var idx = array.indexOf(this.getChildren(), page);

			this.inherited(arguments);

			// Remove the child widget wrapper we use to set aria roles.  This won't affect the page itself since it's
			// already been detached from page._wrapper via the this.inherited(arguments) call above.
			domConstruct.destroy(page._wrapper);
			delete page._wrapper;

			if(this._started){
				// This will notify any tablists to remove a button; do this first because it may affect sizing.
				topic.publish(this.id + "-removeChild", page);
			}

			// If all our children are being destroyed than don't run the code below (to select another page),
			// because we are deleting every page one by one
			if(this._descendantsBeingDestroyed){
				return;
			}

			// Select new page to display, also updating TabController to show the respective tab.
			// Do this before layout call because it can affect the height of the TabController.
			if(this.selectedChildWidget === page){
				this.selectedChildWidget = undefined;
				if(this._started){
					var children = this.getChildren();
					if(children.length){
						this.selectChild(children[Math.max(idx - 1, 0)]);
					}
				}
			}

			if(this._started){
				// In case the tab titles now take up one line instead of two lines
				// (note though that ScrollingTabController never overflows to multiple lines),
				// or the height has changed slightly because of addition/removal of tab which close icon
				this.layout();
			}
		},

		selectChild: function(/*dijit/_WidgetBase|String*/ page, /*Boolean*/ animate){
			// summary:
			//		Show the given widget (which must be one of my children)
			// page:
			//		Reference to child widget or id of child widget

			var d;

			page = registry.byId(page);

			if(this.selectedChildWidget != page){
				// Deselect old page and select new one
				d = this._transition(page, this.selectedChildWidget, animate);
				this._set("selectedChildWidget", page);
				topic.publish(this.id + "-selectChild", page, this._focused);	// publish

				if(this.persist){
					cookie(this.id + "_selectedChild", this.selectedChildWidget.id);
				}
			}

			// d may be null, or a scalar like true.  Return a promise in all cases
			return when(d || true);		// Promise
		},

		_transition: function(newWidget, oldWidget /*===== ,  animate =====*/){
			// summary:
			//		Hide the old widget and display the new widget.
			//		Subclasses should override this.
			// newWidget: dijit/_WidgetBase
			//		The newly selected widget.
			// oldWidget: dijit/_WidgetBase
			//		The previously selected widget.
			// animate: Boolean
			//		Used by AccordionContainer to turn on/off slide effect.
			// tags:
			//		protected extension
			if(oldWidget){
				this._hideChild(oldWidget);
			}
			var d = this._showChild(newWidget);

			// Size the new widget, in case this is the first time it's being shown,
			// or I have been resized since the last time it was shown.
			// Note that page must be visible for resizing to work.
			if(newWidget.resize){
				if(this.doLayout){
					newWidget.resize(this._containerContentBox || this._contentBox);
				}else{
					// the child should pick it's own size but we still need to call resize()
					// (with no arguments) to let the widget lay itself out
					newWidget.resize();
				}
			}

			return d;	// If child has an href, promise that fires when the child's href finishes loading
		},

		_adjacent: function(/*Boolean*/ forward){
			// summary:
			//		Gets the next/previous child widget in this container from the current selection.

			// TODO: remove for 2.0 if this isn't being used.   Otherwise, fix to skip disabled tabs.

			var children = this.getChildren();
			var index = array.indexOf(children, this.selectedChildWidget);
			index += forward ? 1 : children.length - 1;
			return children[ index % children.length ]; // dijit/_WidgetBase
		},

		forward: function(){
			// summary:
			//		Advance to next page.
			return this.selectChild(this._adjacent(true), true);
		},

		back: function(){
			// summary:
			//		Go back to previous page.
			return this.selectChild(this._adjacent(false), true);
		},

		_onKeyDown: function(e){
			topic.publish(this.id + "-containerKeyDown", { e: e, page: this});	// publish
		},

		layout: function(){
			// Implement _LayoutWidget.layout() virtual method.
			var child = this.selectedChildWidget;
			if(child && child.resize){
				if(this.doLayout){
					child.resize(this._containerContentBox || this._contentBox);
				}else{
					child.resize();
				}
			}
		},

		_showChild: function(/*dijit/_WidgetBase*/ page){
			// summary:
			//		Show the specified child by changing it's CSS, and call _onShow()/onShow() so
			//		it can do any updates it needs regarding loading href's etc.
			// returns:
			//		Promise that fires when page has finished showing, or true if there's no href
			var children = this.getChildren();
			page.isFirstChild = (page == children[0]);
			page.isLastChild = (page == children[children.length - 1]);
			page._set("selected", true);

			if(page._wrapper){	// false if not started yet
				domClass.replace(page._wrapper, "dijitVisible", "dijitHidden");
			}

			return (page._onShow && page._onShow()) || true;
		},

		_hideChild: function(/*dijit/_WidgetBase*/ page){
			// summary:
			//		Hide the specified child by changing it's CSS, and call _onHide() so
			//		it's notified.
			page._set("selected", false);

			if(page._wrapper){	// false if not started yet
				domClass.replace(page._wrapper, "dijitHidden", "dijitVisible");
			}

			page.onHide && page.onHide();
		},

		closeChild: function(/*dijit/_WidgetBase*/ page){
			// summary:
			//		Callback when user clicks the [X] to remove a page.
			//		If onClose() returns true then remove and destroy the child.
			// tags:
			//		private
			var remove = !page.onClose || page.onClose(this, page);
			if(remove){
				this.removeChild(page);
				// makes sure we can clean up executeScripts in ContentPane onUnLoad
				page.destroyRecursive();
			}
		},

		destroyDescendants: function(/*Boolean*/ preserveDom){
			this._descendantsBeingDestroyed = true;
			this.selectedChildWidget = undefined;
			array.forEach(this.getChildren(), function(child){
				if(!preserveDom){
					this.removeChild(child);
				}
				child.destroyRecursive(preserveDom);
			}, this);
			this._descendantsBeingDestroyed = false;
		}
	});

	StackContainer.ChildWidgetProperties = {
		// summary:
		//		These properties can be specified for the children of a StackContainer.

		// selected: Boolean
		//		Specifies that this widget should be the initially displayed pane.
		//		Note: to change the selected child use `dijit/layout/StackContainer.selectChild`
		selected: false,

		// disabled: Boolean
		//		Specifies that the button to select this pane should be disabled.
		//		Doesn't affect programmatic selection of the pane, nor does it deselect the pane if it is currently selected.
		disabled: false,

		// closable: Boolean
		//		True if user can close (destroy) this child, such as (for example) clicking the X on the tab.
		closable: false,

		// iconClass: String
		//		CSS Class specifying icon to use in label associated with this pane.
		iconClass: "dijitNoIcon",

		// showTitle: Boolean
		//		When true, display title of this widget as tab label etc., rather than just using
		//		icon specified in iconClass
		showTitle: true
	};

	// Since any widget can be specified as a StackContainer child, mix them
	// into the base widget class.  (This is a hack, but it's effective.)
	// This is for the benefit of the parser.   Remove for 2.0.  Also, hide from doc viewer.
	lang.extend(_WidgetBase, /*===== {} || =====*/ StackContainer.ChildWidgetProperties);

	return StackContainer;
});
