define(["dojo/_base/kernel",
	"dojo/_base/declare",
	"dojo/_base/array",
	"dojo/_base/event",
	"dojo/_base/sniff",
	"dojo/dom",
	"dojo/dom-attr",
	"dojo/dom-construct",
	"dojo/dom-geometry",
	"dojo/dom-style",
	"dojo/window",
	"dojo/_base/window",
	"dojo/_base/fx",
	"dojo/fx",
	"dijit/_Widget",
	"dijit/_TemplatedMixin",
	"dijit/registry"],

function(kernel,
		declare,
		array,
		event,
		has,
		dom,
		attr,
		construct,
		geometry,
		domStyle,
		window,
		baseWindow,
		baseFx,
		fx,
		_Widget,
		_TemplatedMixin,
		registry) {

kernel.experimental("dojox.widget.Standby");

return declare("dojox.widget.Standby", [_Widget, _TemplatedMixin],{
	// summary:
	//		A widget designed to act as a Standby/Busy/Disable/Blocking widget to indicate a
	//		particular DOM node is processing and cannot be clicked on at this time.
	//		This widget uses absolute positioning to apply the overlay and image.

	// image: String
	//		A URL to an image to center within the blocking overlay.
	//		The default is a basic spinner.
	image: require.toUrl("dojox/widget/Standby/images/loading.gif").toString(),

	// imageText: String
	//		Text to set on the ALT tag of the image.
	//		The default is 'Please wait...'
	imageText: "Please Wait...", // TODO: i18n

	// text: String
	//		Text/HTML to display in the center of the overlay
	//		This is used if image center is disabled.
	//		Defaults to 'Please Wait...'
	text: "Please wait...",

	// centerIndicator: String
	//		Property to define if the image and its alt text should be used, or
	//		a simple Text/HTML node should be used.  Allowable values are 'image'
	//		and 'text'.
	//		Default is 'image'.
	centerIndicator: "image",

	// target: DOMNode||DOMID(String)||WidgetID(String)
	//		The target to overlay when active.  Can be a widget id, a
	//		dom id, or a direct node reference.
	target: "",

	// color:	String
	//		The color to set the overlay.  Should be in #XXXXXX form.
	//		Default color for the translucent overlay is light gray.
	color: "#C0C0C0",

	// duration: Integer
	//		Integer defining how long the show and hide effects should take in milliseconds.
	//		Defaults to 500
	duration: 500,

	// zIndex: String
	//		Control that lets you specify if the zIndex for the overlay
	//		should be auto-computed based off parent zIndex, or should be set
	//		to a particular value.  This is useful when you want to overlay
	//		things in digit.Dialogs, you can specify a base zIndex to append from.
	zIndex: "auto",

	// opacity: float
	//		The opacity to make the overlay when it is displayed/faded in.
	//		The default is 0.75.  This does not affect the image opacity, only the
	//		overlay.
	opacity: 0.75,

	// templateString: [protected] String
	//		The template string defining out the basics of the widget.  No need for an external
	//		file.
	templateString:
		"<div>" +
			"<div style=\"display: none; opacity: 0; z-index: 9999; " +
				"position: absolute; cursor:wait;\" dojoAttachPoint=\"_underlayNode\"></div>" +
			"<img src=\"${image}\" style=\"opacity: 0; display: none; z-index: -10000; " +
				"position: absolute; top: 0px; left: 0px; cursor:wait;\" "+
				"dojoAttachPoint=\"_imageNode\">" +
			"<div style=\"opacity: 0; display: none; z-index: -10000; position: absolute; " +
				"top: 0px;\" dojoAttachPoint=\"_textNode\"></div>" +
		"</div>",

	// _underlayNode: [private] DOMNode
	//		The node that is the translucent underlay for the
	//		image that blocks access to the target.
	_underlayNode: null,

	// _imageNode: [private] DOMNode
	//		The image node where we attach and define the image to display.
	_imageNode: null,

	// _textNode: [private] DOMNode
	//		The div to attach text/HTML in the overlay center item.
	_textNode: null,

	// _centerNode: [private] DOMNode
	//		Which node to use as the center node, the image or the text node.
	_centerNode: null,

	// _displayed: [private] Boolean
	//		Flag to indicate if the overlay is displayed or not.
	_displayed: false,

	// _resizeCheck: [private] Object
	//		Handle to interval function that checks the target for changes.
	_resizeCheck: null,

	// _started: [private] Boolean
	//		Trap flag to ensure startup only processes once.
	_started: false,

	// _parent: [private] DOMNode
	//		Wrapping div for the widget, also used for IE 7 in dealing with the
	//		zoom issue.
	_parent: null,

	startup: function(args){
		// summary:
		//		Over-ride of the basic widget startup function.
		//		Configures the target node and sets the image to use.
		if(!this._started){
			if(typeof this.target === "string"){
				var w = registry.byId(this.target);
				this.target = w ? w.domNode : dom.byId(this.target);
			}

			if(this.text){
				this._textNode.innerHTML = this.text;
			}
			if(this.centerIndicator === "image"){
				this._centerNode = this._imageNode;
				attr.set(this._imageNode, "src", this.image);
				attr.set(this._imageNode, "alt", this.imageText);
			}else{
				this._centerNode = this._textNode;
			}
			domStyle.set(this._underlayNode, {
				display: "none",
				backgroundColor: this.color
			});
			domStyle.set(this._centerNode, "display", "none");
			this.connect(this._underlayNode, "onclick", "_ignore");

			//Last thing to do is move the widgets parent, if any, to the current document body.
			//Avoids having to deal with parent relative/absolute mess.  Otherwise positioning
			//tends to go goofy.
			if(this.domNode.parentNode && this.domNode.parentNode != baseWindow.body()){
				baseWindow.body().appendChild(this.domNode);
			}

			//IE 7 has a horrible bug with zoom, so we have to create this node
			//to cross-check later.  Sigh.
			if(has("ie") == 7){
				this._ieFixNode = construct.create("div");
				domStyle.set(this._ieFixNode, {
					opacity: "0",
					zIndex: "-1000",
					position: "absolute",
					top: "-1000px"
				});
				baseWindow.body().appendChild(this._ieFixNode);
			}
			this.inherited(arguments);
		}
	},

	show: function(){
		// summary:
		//		Function to display the blocking overlay and busy/status icon or text.
		if(!this._displayed){
			if(this._anim){
				this._anim.stop();
				delete this._anim;
			}
			this._displayed = true;
			this._size();
			this._disableOverflow();
			this._fadeIn();
		}
	},

	hide: function(){
		// summary:
		//		Function to hide the blocking overlay and status icon or text.
		if(this._displayed){
			// Ideally would come up with something better than try/catch,
			// but don't see another simple workaround for
			// https://bugs.dojotoolkit.org/ticket/18196 and
			// https://bugs.dojotoolkit.org/ticket/14984
			try{
				if(this._anim){
					this._anim.stop();
					delete this._anim;
				}
				this._size();
			}catch(e){
				console.error(e);
			}finally{
				this._fadeOut();
				this._displayed = false;
				if(this._resizeCheck !== null){
					clearInterval(this._resizeCheck);
					this._resizeCheck = null;
				}
			}
		}
	},

	isVisible: function(){
		// summary:
		//		Helper function so you can test if the widget is already visible or not.
		// returns:
		//		boolean indicating if the widget is in 'show' state or not.
		return this._displayed; // boolean
	},

	onShow: function(){
		// summary:
		//		Event that fires when the display of the Standby completes.
	},

	onHide: function(){
		// summary:
		//		Event that fires when the display of the Standby completes.
	},

	uninitialize: function(){
		// summary:
		//		Over-ride to hide the widget, which clears intervals, before cleanup.
		this._displayed = false;
		if(this._resizeCheck){
			clearInterval(this._resizeCheck);
		}
		domStyle.set(this._centerNode, "display", "none");
		domStyle.set(this._underlayNode, "display", "none");
		if(has("ie") == 7 && this._ieFixNode){
			baseWindow.body().removeChild(this._ieFixNode);
			delete this._ieFixNode;
		}
		if(this._anim){
			this._anim.stop();
			delete this._anim;
		}
		this.target = null;
		this._imageNode = null;
		this._textNode = null;
		this._centerNode = null;
		this.inherited(arguments);
	},

	_size: function(){
		// summary:
		//		Internal function that handles resizing the overlay and
		//		centering of the image on window resizing.
		// tags:
		//		private
		if(this._displayed){
			var dir = attr.get(baseWindow.body(), "dir");
			if(dir){dir = dir.toLowerCase();}
			var _ie7zoom;
			var scrollers = this._scrollerWidths();

			var target = this.target;

			//Show the image and make sure the zIndex is set high.
			var curStyle = domStyle.get(this._centerNode, "display");
			domStyle.set(this._centerNode, "display", "block");
			var box = geometry.position(target, true);
			if(target === baseWindow.body() || target === baseWindow.doc){
				// Target is the whole doc, so scale to viewport.
				box = window.getBox();
				box.x = box.l;
				box.y = box.t;
			}

			var cntrIndicator = geometry.getMarginBox(this._centerNode);
			domStyle.set(this._centerNode, "display", curStyle);

			//IE has a horrible zoom bug.  So, we have to try and account for
			//it and fix up the scaling.
			if(this._ieFixNode){
				_ie7zoom = -this._ieFixNode.offsetTop / 1000;
				box.x = Math.floor((box.x + 0.9) / _ie7zoom);
				box.y = Math.floor((box.y + 0.9) / _ie7zoom);
				box.w = Math.floor((box.w + 0.9) / _ie7zoom);
				box.h = Math.floor((box.h + 0.9) / _ie7zoom);
			}

			//Figure out how to zIndex this thing over the target.
			var zi = domStyle.get(target, "zIndex");
			var ziUl = zi;
			var ziIn = zi;

			if(this.zIndex === "auto"){
				if(zi != "auto"){
					// honor user-defined z-index
					ziUl = parseInt(ziUl, 10);
					ziIn = parseInt(ziIn, 10);
				}else{
					//We need to search up the chain to see if there
					//are any parent zIndexs to overlay.
					var cNode = target;
					if(cNode && cNode !== baseWindow.body() && cNode !== baseWindow.doc){
						cNode = target.parentNode;
						var oldZi = -100000;
						while(cNode && cNode !== baseWindow.body()){
							zi = domStyle.get(cNode, "zIndex");
							if(!zi || zi === "auto"){
								cNode = cNode.parentNode;
							}else{
								var newZi = parseInt(zi, 10);
								if(oldZi < newZi){
									oldZi = newZi;
									ziUl = newZi + 1;
									ziIn = newZi + 1;
								}
								// Keep looking until we run out, we want the highest zIndex.
								cNode = cNode.parentNode;
							}
						}
					}
				}
			}else{
				// honor user-defined z-index
				ziUl = parseInt(this.zIndex, 10);
				ziIn = parseInt(this.zIndex, 10);
			}

			domStyle.set(this._centerNode, "zIndex", ziIn);
			domStyle.set(this._underlayNode, "zIndex", ziUl);


			var pn = target.parentNode;
			if(pn && pn !== baseWindow.body() &&
				target !== baseWindow.body() &&
				target !== baseWindow.doc){

				// If the parent is the body tag itself,
				// we can avoid all this, the body takes
				// care of overflow for me.  Besides, browser
				// weirdness with height and width on body causes
				// problems with this sort of intersect testing
				// anyway.
				var obh = box.h;
				var obw = box.w;
				var pnBox = geometry.position(pn, true);

				//More IE zoom corrections.  Grr.
				if(this._ieFixNode){
					_ie7zoom = -this._ieFixNode.offsetTop / 1000;
					pnBox.x = Math.floor((pnBox.x + 0.9) / _ie7zoom);
					pnBox.y = Math.floor((pnBox.y + 0.9) / _ie7zoom);
					pnBox.w = Math.floor((pnBox.w + 0.9) / _ie7zoom);
					pnBox.h = Math.floor((pnBox.h + 0.9) / _ie7zoom);
				}

				//Shift the parent width/height a bit if scollers are present.
				pnBox.w -= pn.scrollHeight > pn.clientHeight &&
					pn.clientHeight > 0 ? scrollers.v: 0;
				pnBox.h -= pn.scrollWidth > pn.clientWidth &&
					pn.clientWidth > 0 ? scrollers.h: 0;

				//RTL requires a bit of massaging in some cases
				//(and differently depending on browser, ugh!)
				//WebKit and others still need work.
				if(dir === "rtl"){
					if(has("opera")){
						box.x += pn.scrollHeight > pn.clientHeight &&
							pn.clientHeight > 0 ? scrollers.v: 0;
						pnBox.x += pn.scrollHeight > pn.clientHeight &&
							pn.clientHeight > 0 ? scrollers.v: 0;
					}else if(has("ie")){
						pnBox.x += pn.scrollHeight > pn.clientHeight &&
							pn.clientHeight > 0 ? scrollers.v: 0;
					}else if(has("webkit")){
						//TODO:  FIX THIS!
					}
				}

				//Figure out if we need to adjust the overlay to fit a viewable
				//area, then resize it, we saved the original height/width above.
				//This is causing issues on IE.  Argh!
				if(pnBox.w < box.w){
					//Scale down the width if necessary.
					box.w = box.w - pnBox.w;
				}
				if(pnBox.h < box.h){
					//Scale down the width if necessary.
					box.h = box.h - pnBox.h;
				}

				//Look at the y positions and see if we intersect with the
				//viewport borders.  Will have to do computations off it.
				var vpTop = pnBox.y;
				var vpBottom = pnBox.y + pnBox.h;
				var bTop = box.y;
				var bBottom = box.y + obh;
				var vpLeft = pnBox.x;
				var vpRight = pnBox.x + pnBox.w;
				var bLeft = box.x;
				var bRight = box.x + obw;
				var delta;
				//Adjust the height now
				if(bBottom > vpTop &&
					bTop < vpTop){
					box.y = pnBox.y;
					//intersecting top, need to do some shifting.
					delta = vpTop - bTop;
					var visHeight = obh - delta;
					//If the visible height < viewport height,
					//We need to shift it.
					if(visHeight < pnBox.h){
						box.h = visHeight;
					}else{
						//Deal with horizontal scrollbars if necessary.
						box.h -= 2*(pn.scrollWidth > pn.clientWidth &&
							pn.clientWidth > 0? scrollers.h: 0);
					}
				}else if(bTop < vpBottom && bBottom > vpBottom){
					//Intersecting bottom, just figure out how much
					//overlay to show.
					box.h = vpBottom - bTop;
				}else if(bBottom <= vpTop || bTop >= vpBottom){
					//Outside view, hide it.
					box.h = 0;
				}

				//adjust width
				if(bRight > vpLeft && bLeft < vpLeft){
					box.x = pnBox.x;
					//intersecting left, need to do some shifting.
					delta = vpLeft - bLeft;
					var visWidth = obw - delta;
					//If the visible width < viewport width,
					//We need to shift it.
					if(visWidth < pnBox.w){
						box.w = visWidth;
					}else{
						//Deal with horizontal scrollbars if necessary.
						box.w -= 2*(pn.scrollHeight > pn.clientHeight &&
							pn.clientHeight > 0? scrollers.w:0);
					}
				}else if(bLeft < vpRight && bRight > vpRight){
					//Intersecting right, just figure out how much
					//overlay to show.
					box.w = vpRight - bLeft;
				}else if(bRight <= vpLeft || bLeft >= vpRight){
					//Outside view, hide it.
					box.w = 0;
				}
			}

			if(box.h > 0 && box.w > 0){
				//Set position and size of the blocking div overlay.
				domStyle.set(this._underlayNode, {
					display: "block",
					width: box.w + "px",
					height: box.h + "px",
					top: box.y + "px",
					left: box.x + "px"
				});

				var styles = ["borderRadius", "borderTopLeftRadius",
					"borderTopRightRadius","borderBottomLeftRadius",
					"borderBottomRightRadius"];
				this._cloneStyles(styles);
				if(!has("ie")){
					//Browser specific styles to try and clone if non-IE.
					styles = ["MozBorderRadius", "MozBorderRadiusTopleft",
						"MozBorderRadiusTopright","MozBorderRadiusBottomleft",
						"MozBorderRadiusBottomright","WebkitBorderRadius",
						"WebkitBorderTopLeftRadius", "WebkitBorderTopRightRadius",
						"WebkitBorderBottomLeftRadius","WebkitBorderBottomRightRadius"
					];
					this._cloneStyles(styles, this);
				}
				var cntrIndicatorTop = (box.h/2) - (cntrIndicator.h/2);
				var cntrIndicatorLeft = (box.w/2) - (cntrIndicator.w/2);
				//Only show the image if there is height and width room.
				if(box.h >= cntrIndicator.h && box.w >= cntrIndicator.w){
					domStyle.set(this._centerNode, {
						top: (cntrIndicatorTop + box.y) + "px",
						left: (cntrIndicatorLeft + box.x) + "px",
						display: "block"
					});
				}else{
					domStyle.set(this._centerNode, "display", "none");
				}
			}else{
				//Target has no size, display nothing on it!
				domStyle.set(this._underlayNode, "display", "none");
				domStyle.set(this._centerNode, "display", "none");
			}
			if(this._resizeCheck === null){
				//Set an interval timer that checks the target size and scales as needed.
				//Checking every 10th of a second seems to generate a fairly smooth update.
				var self = this;
				this._resizeCheck = setInterval(function(){self._size();}, 100);
			}
		}
	},

	_cloneStyles: function(list){
		// summary:
		//		Internal function to clone a set of styles from the target to
		//		the underlay.
		// list: Array
		//		An array of style names to clone.
		//
		// tags:
		//		private
		array.forEach(list, function(s){
			domStyle.set(this._underlayNode, s, domStyle.get(this.target, s));
		}, this);
	},

	_fadeIn: function(){
		// summary:
		//		Internal function that does the opacity style fade in animation.
		// tags:
		//		private
		var self = this;
		var underlayNodeAnim = baseFx.animateProperty({
			duration: self.duration,
			node: self._underlayNode,
			properties: {opacity: {start: 0, end: self.opacity}}
		});
		var imageAnim = baseFx.animateProperty({
			duration: self.duration,
			node: self._centerNode,
			properties: {opacity: {start: 0, end: 1}},
			onEnd: function(){
				self.onShow();
				delete self._anim;
			}
		});
		this._anim = fx.combine([underlayNodeAnim,imageAnim]);
		this._anim.play();
	},

	_fadeOut: function(){
		// summary:
		//		Internal function that does the opacity style fade out animation.
		// tags:
		//		private
		var self = this;
		var underlayNodeAnim = baseFx.animateProperty({
			duration: self.duration,
			node: self._underlayNode,
			properties: {opacity: {start: self.opacity, end: 0}},
			onEnd: function(){
				domStyle.set(this.node,{"display":"none", "zIndex": "-1000"});
			}
		});
		var imageAnim = baseFx.animateProperty({
			duration: self.duration,
			node: self._centerNode,
			properties: {opacity: {start: 1, end: 0}},
			onEnd: function(){
				domStyle.set(this.node,{"display":"none", "zIndex": "-1000"});
				self.onHide();
				self._enableOverflow();
				delete self._anim;
			}
		});
		this._anim = fx.combine([underlayNodeAnim,imageAnim]);
		this._anim.play();
	},

	_ignore: function(e){
		// summary:
		//		Function to ignore events that occur on the overlay.
		// event: Event
		//		The event to halt
		// tags:
		//		private
		if(e){
			event.stop(e);
		}
	},

	_scrollerWidths: function(){
		// summary:
		//		This function will calculate the size of the vertical and
		//		horizontaol scrollbars.
		// returns:
		//		Object of form: {v: Number, h: Number} where v is vertical scrollbar width
		//		and h is horizontal scrollbar width.
		// tags:
		//		private
		var div = construct.create("div");
		domStyle.set(div, {
			position: "absolute",
			opacity: 0,
			overflow: "hidden",
			width: "50px",
			height: "50px",
			zIndex: "-100",
			top: "-200px",
			padding: "0px",
			margin: "0px"
		});
		var iDiv = construct.create("div");
		domStyle.set(iDiv, {
			width: "200px",
			height: "10px"
		});
		div.appendChild(iDiv);
		baseWindow.body().appendChild(div);

		//Figure out content size before and after
		//scrollbars are there, then just subtract to
		//get width.
		var b = geometry.getContentBox(div);
		domStyle.set(div, "overflow", "scroll");
		var a = geometry.getContentBox(div);
		baseWindow.body().removeChild(div);
		return { v: b.w - a.w, h: b.h - a.h };
	},

	/* The following are functions that tie into _Widget.attr() */

	_setTextAttr: function(text){
		// summary:
		//		Function to allow widget.attr to set the text displayed in center
		//		if using text display.
		// text: String
		//		The text to set.
		this._textNode.innerHTML = text;
		this.text = text;
	},

	_setColorAttr: function(c){
		// summary:
		//		Function to allow widget.attr to set the color used for the translucent
		//		div overlay.
		// c: String
		//		The color to set the background underlay to in #XXXXXX format..
		domStyle.set(this._underlayNode, "backgroundColor", c);
		this.color = c;
	},

	_setImageTextAttr: function(text){
		// summary:
		//		Function to allow widget.attr to set the ALT text text displayed for
		//		the image (if using image center display).
		// text: String
		//		The text to set.
		attr.set(this._imageNode, "alt", text);
		this.imageText = text;
	},

	_setImageAttr: function(url){
		// summary:
		//		Function to allow widget.attr to set the url source for the center image
		// text: String
		//		The url to set for the image.
		attr.set(this._imageNode, "src", url);
		this.image = url;
	},

	_setCenterIndicatorAttr: function(indicator){
		// summary:
		//		Function to allow widget.attr to set the node used for the center indicator,
		//		either the image or the text.
		// indicator: String
		//		The indicator to use, either 'image' or 'text'.
		this.centerIndicator = indicator;
		if(indicator === "image"){
			this._centerNode = this._imageNode;
			domStyle.set(this._textNode, "display", "none");
		}else{
			this._centerNode = this._textNode;
			domStyle.set(this._imageNode, "display", "none");
		}
	},

	_setTargetAttr:function(target){
		// summary:
		//		Function to allow widget.attr to set the target used
		// target: String
		//		The target to use.
	    if(typeof target === "string"){
	        var w = registry.byId(target);
	        this._set("target", w ? w.domNode : dom.byId(target));
	    }
	    else {
			this._set("target", target);
		}
	},

	_disableOverflow: function(){
		 // summary:
		 //		Function to disable scrollbars on the body.  Only used if the overlay
		 //		targets the body or the document.
		 if(this.target === baseWindow.body() || this.target === baseWindow.doc){
			 // Store the overflow state we have to restore later.
			 // IE had issues, so have to check that it's defined.  Ugh.
			 this._overflowDisabled = true;
			 var body = baseWindow.body();
			 if(body.style && body.style.overflow){
				 this._oldOverflow = domStyle.get(body, "overflow");
			 }else{
				 this._oldOverflow = "";
			 }
			 if(has("ie") && !has("quirks")){
				 // IE will put scrollbars in anyway, html (parent of body)
				 // also controls them in standards mode, so we have to
				 // remove them, argh.
				 if(body.parentNode &&
					body.parentNode.style &&
					body.parentNode.style.overflow){
					 this._oldBodyParentOverflow = body.parentNode.style.overflow;
				 }else{
					 try{
						this._oldBodyParentOverflow = domStyle.get(body.parentNode, "overflow");
					 }catch(e){
						 this._oldBodyParentOverflow = "scroll";
					 }
				 }
				 domStyle.set(body.parentNode, "overflow", "hidden");
			 }
			 domStyle.set(body, "overflow", "hidden");
		 }
	},

	_enableOverflow: function(){
		 // summary:
		 //		Function to restore scrollbars on the body.  Only used if the overlay
		 //		targets the body or the document.
		 if(this._overflowDisabled){
			delete this._overflowDisabled;
			var body = baseWindow.body();
			// Restore all the overflow.
			if(has("ie") && !has("quirks")){
				body.parentNode.style.overflow = this._oldBodyParentOverflow;
				delete this._oldBodyParentOverflow;
			}
			domStyle.set(body, "overflow", this._oldOverflow);
			if(has("webkit")){
				//Gotta poke WebKit, or scrollers don't come back. :-(
				var div = construct.create("div", { style: {
						height: "2px"
					}
				});
				body.appendChild(div);
				setTimeout(function(){
					body.removeChild(div);
				}, 0);
			}
			delete this._oldOverflow;
		}
	}
});

});
