define([
	"dojo/aspect",
	"dojo/_base/declare", // declare
	"dojo/dom-class", // domClass.add domClass.remove
	"dojo/dom-geometry",
	"dojo/dom-style",
	"dojo/i18n", // i18n.getLocalization
	"dojo/keys", // keys.F11 keys.TAB
	"dojo/_base/lang", // lang.hitch
	"dojo/on", // on()
	"dojo/sniff", // has("ie"), has("quirks")
	"dojo/_base/window", // win.body
	"dojo/window", // winUtils.getBox winUtils.scrollIntoView
	"../../focus", // focus.focus(), focus.curNode
	"../_Plugin",
	"../../form/ToggleButton",
	"../../registry", // registry.getEnclosingWidget()
	"dojo/i18n!../nls/commands"
], function(aspect, declare, domClass, domGeometry, domStyle, i18n, keys, lang, on, has, win, winUtils,
			focus, _Plugin, ToggleButton, registry){

	// module:
	//		dijit/_editor/plugins/FullScreen

	var FullScreen = declare("dijit._editor.plugins.FullScreen", _Plugin, {
		// summary:
		//		This plugin provides FullScreen capability to the editor.  When
		//		toggled on, it will render the editor into the full window and
		//		overlay everything.  It also binds to the hotkey: CTRL-SHIFT-F11
		//		for toggling fullscreen mode.

		// zIndex: [public] Number
		//		zIndex value used for overlaying the full page.
		//		default is 500.
		zIndex: 500,

		// _origState: [private] Object
		//		The original view state of the editor.
		_origState: null,

		// _origiFrameState: [private] Object
		//		The original view state of the iframe of the editor.
		_origiFrameState: null,

		// _resizeHandle: [private] Object
		//		Connection point used for handling resize when window resizes.
		_resizeHandle: null,

		// isFullscreen: [const] boolean
		//		Read-Only variable used to denote of the editor is in fullscreen mode or not.
		isFullscreen: false,

		toggle: function(){
			// summary:
			//		Function to allow programmatic toggling of the view.
			this.button.set("checked", !this.button.get("checked"));
		},

		_initButton: function(){
			// summary:
			//		Over-ride for creation of the resize button.
			var strings = i18n.getLocalization("dijit._editor", "commands"),
				editor = this.editor;
			this.button = new ToggleButton({
				label: strings["fullScreen"],
				ownerDocument: editor.ownerDocument,
				dir: editor.dir,
				lang: editor.lang,
				showLabel: false,
				iconClass: this.iconClassPrefix + " " + this.iconClassPrefix + "FullScreen",
				tabIndex: "-1",
				onChange: lang.hitch(this, "_setFullScreen")
			});
		},

		setEditor: function(editor){
			// summary:
			//		Over-ride for the setting of the editor.
			// editor: Object
			//		The editor to configure for this plugin to use.
			this.editor = editor;
			this._initButton();

			this.editor.addKeyHandler(keys.F11, true, true, lang.hitch(this, function(e){
				// Enable the CTRL-SHIFT-F11 hotkey for fullscreen mode.
				this.toggle();
				e.stopPropagation();
				e.preventDefault();
				this.editor.defer("focus", 250);
				return true;
			}));
			this.own(on(this.editor.domNode, "keydown", lang.hitch(this, "_containFocus")));
		},

		_containFocus: function(e){
			// summary:
			//		When in Full Screen mode, it's good to try and retain focus in the editor
			//		so this function is intended to try and constrain the TAB key.
			// e: Event
			//		The key event.
			// tags:
			//		private
			if(this.isFullscreen){
				var ed = this.editor;
				if(!ed.isTabIndent &&
					ed._fullscreen_oldOnKeyDown &&
					e.keyCode === keys.TAB){
					// If we're in fullscreen mode, we want to take over how tab moves focus a bit.
					// to keep it within the editor since it's hiding the rest of the page.
					// IE hates changing focus IN the event handler, so need to put calls
					// in a timeout.  Gotta love IE.
					// Also need to check for alternate view nodes if present and active.
					var f = focus.curNode;
					var avn = this._getAltViewNode();
					if(f == ed.iframe ||
						(avn && f === avn)){
						setTimeout(lang.hitch(this, function(){
							ed.toolbar.focus();
						}), 10);
					}else{
						if(avn && domStyle.get(ed.iframe, "display") === "none"){
							setTimeout(lang.hitch(this, function(){
								focus.focus(avn);
							}), 10);
						}else{
							setTimeout(lang.hitch(this, function(){
								ed.focus();
							}), 10);
						}
					}
					event.stopPropagation();
					event.preventDefault();
				}else if(ed._fullscreen_oldOnKeyDown){
					// Only call up when it's a different function.  Traps corner case event issue
					// on IE which caused stack overflow on handler cleanup.
					ed._fullscreen_oldOnKeyDown(e);
				}
			}
		},

		_resizeEditor: function(){
			// summary:
			//		Function to handle resizing the editor as the viewport
			//		resizes (window scaled)
			// tags:
			//		private
			var vp = winUtils.getBox(this.editor.ownerDocument);
			domGeometry.setMarginBox(this.editor.domNode, {
				w: vp.w,
				h: vp.h
			});

			//Adjust the internal heights too, as they can be a bit off.
			var hHeight = this.editor.getHeaderHeight();
			var fHeight = this.editor.getFooterHeight();
			var extents = domGeometry.getPadBorderExtents(this.editor.domNode);
			var fcpExtents = domGeometry.getPadBorderExtents(this.editor.iframe.parentNode);
			var fcmExtents = domGeometry.getMarginExtents(this.editor.iframe.parentNode);

			var cHeight = vp.h - (hHeight + extents.h + fHeight);
			domGeometry.setMarginBox(this.editor.iframe.parentNode, {
				h: cHeight,
				w: vp.w
			});
			domGeometry.setMarginBox(this.editor.iframe, {
				h: cHeight - (fcpExtents.h + fcmExtents.h)
			});
		},

		_getAltViewNode: function(){
			// summary:
			//		This function is intended as a hook point for setting an
			//		alternate view node for when in full screen mode and the
			//		editable iframe is hidden.
			// tags:
			//		protected.
		},

		_setFullScreen: function(full){
			// summary:
			//		Function to handle toggling between full screen and
			//		regular view.
			// tags:
			//		private

			//Alias this for shorter code.
			var ed = this.editor;
			var body = ed.ownerDocumentBody;
			var editorParent = ed.domNode.parentNode;

			var vp = winUtils.getBox(ed.ownerDocument);

			this.isFullscreen = full;

			if(full){
				//Parent classes can royally screw up this plugin, so we
				//have to set everything to position static.
				while(editorParent && editorParent !== body){
					domClass.add(editorParent, "dijitForceStatic");
					editorParent = editorParent.parentNode;
				}

				// Save off the resize function.  We want to kill its behavior.
				this._editorResizeHolder = this.editor.resize;
				ed.resize = function(){
				};

				// Try to constrain focus control.
				ed._fullscreen_oldOnKeyDown = ed.onKeyDown;
				ed.onKeyDown = lang.hitch(this, this._containFocus);

				this._origState = {};
				this._origiFrameState = {};

				// Store the basic editor state we have to restore later.
				// Not using domStyle.get here, had problems, didn't
				// give me stuff like 100%, gave me pixel calculated values.
				// Need the exact original values.
				var domNode = ed.domNode,
					rawStyle = domNode && domNode.style || {};
				this._origState = {
					width: rawStyle.width || "",
					height: rawStyle.height || "",
					top: domStyle.get(domNode, "top") || "",
					left: domStyle.get(domNode, "left") || "",
					position: domStyle.get(domNode, "position") || "static",
					marginBox: domGeometry.getMarginBox(ed.domNode)
				};

				// Store the iframe state we have to restore later.
				// Not using domStyle.get here, had problems, didn't
				// give me stuff like 100%, gave me pixel calculated values.
				// Need the exact original values.
				var iframe = ed.iframe,
					iframeStyle = iframe && iframe.style || {};

				var bc = domStyle.get(ed.iframe, "backgroundColor");
				this._origiFrameState = {
					backgroundColor: bc || "transparent",
					width: iframeStyle.width || "auto",
					height: iframeStyle.height || "auto",
					zIndex: iframeStyle.zIndex || ""
				};

				// Okay, size everything.
				domStyle.set(ed.domNode, {
					position: "absolute",
					top: "0px",
					left: "0px",
					zIndex: this.zIndex,
					width: vp.w + "px",
					height: vp.h + "px"
				});

				domStyle.set(ed.iframe, {
					height: "100%",
					width: "100%",
					zIndex: this.zIndex,
					backgroundColor: bc !== "transparent" &&
						bc !== "rgba(0, 0, 0, 0)" ? bc : "white"
				});

				domStyle.set(ed.iframe.parentNode, {
					height: "95%",
					width: "100%"
				});

				// Store the overflow state we have to restore later.
				// IE had issues, so have to check that it's defined.  Ugh.
				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");

				var resizer = function(){
					// function to handle resize events.
					// Will check current VP and only resize if
					// different.
					var vp = winUtils.getBox(ed.ownerDocument);
					if("_prevW" in this && "_prevH" in this){
						// No actual size change, ignore.
						if(vp.w === this._prevW && vp.h === this._prevH){
							return;
						}
					}else{
						this._prevW = vp.w;
						this._prevH = vp.h;
					}
					if(this._resizer){
						clearTimeout(this._resizer);
						delete this._resizer;
					}
					// Timeout it to help avoid spamming resize on IE.
					// Works for all browsers.
					this._resizer = setTimeout(lang.hitch(this, function(){
						delete this._resizer;
						this._resizeEditor();
					}), 10);
				};
				this._resizeHandle = on(window, "resize", lang.hitch(this, resizer));

				// Also monitor for direct calls to resize and adapt editor.
				this._resizeHandle2 = aspect.after(ed, "onResize", lang.hitch(this, function(){
					if(this._resizer){
						clearTimeout(this._resizer);
						delete this._resizer;
					}
					this._resizer = setTimeout(lang.hitch(this, function(){
						delete this._resizer;
						this._resizeEditor();
					}), 10);
				}));

				// Call it once to work around IE glitchiness.  Safe for other browsers too.
				this._resizeEditor();
				var dn = this.editor.toolbar.domNode;
				setTimeout(function(){
					winUtils.scrollIntoView(dn);
				}, 250);
			}else{
				if(this._resizeHandle){
					// Cleanup resizing listeners
					this._resizeHandle.remove();
					this._resizeHandle = null;
				}
				if(this._resizeHandle2){
					// Cleanup resizing listeners
					this._resizeHandle2.remove();
					this._resizeHandle2 = null;
				}
				if(this._rst){
					clearTimeout(this._rst);
					this._rst = null;
				}

				//Remove all position static class assigns.
				while(editorParent && editorParent !== body){
					domClass.remove(editorParent, "dijitForceStatic");
					editorParent = editorParent.parentNode;
				}

				// Restore resize function
				if(this._editorResizeHolder){
					this.editor.resize = this._editorResizeHolder;
				}

				if(!this._origState && !this._origiFrameState){
					// If we actually didn't toggle, then don't do anything.
					return;
				}
				if(ed._fullscreen_oldOnKeyDown){
					ed.onKeyDown = ed._fullscreen_oldOnKeyDown;
					delete ed._fullscreen_oldOnKeyDown;
				}

				// Add a timeout to make sure we don't have a resize firing in the
				// background at the time of minimize.
				var self = this;
				setTimeout(function(){
					// Restore all the editor state.
					var mb = self._origState.marginBox;
					var oh = self._origState.height;
					if(has("ie") && !has("quirks")){
						body.parentNode.style.overflow = self._oldBodyParentOverflow;
						delete self._oldBodyParentOverflow;
					}
					domStyle.set(body, "overflow", self._oldOverflow);
					delete self._oldOverflow;

					domStyle.set(ed.domNode, self._origState);
					domStyle.set(ed.iframe.parentNode, {
						height: "",
						width: ""
					});
					domStyle.set(ed.iframe, self._origiFrameState);
					delete self._origState;
					delete self._origiFrameState;
					// In case it is contained in a layout and the layout changed size,
					// go ahead and call resize.
					var pWidget = registry.getEnclosingWidget(ed.domNode.parentNode);
					if(pWidget && pWidget.resize){
						pWidget.resize();
					}else{
						if(!oh || oh.indexOf("%") < 0){
							// Resize if the original size wasn't set
							// or wasn't in percent.  Timeout is to avoid
							// an IE crash in unit testing.
							setTimeout(lang.hitch(this, function(){
								ed.resize({h: mb.h});
							}), 0);
						}
					}
					winUtils.scrollIntoView(self.editor.toolbar.domNode);
				}, 100);
			}
		},

		updateState: function(){
			// summary:
			//		Over-ride for button state control for disabled to work.
			this.button.set("disabled", this.get("disabled"));
		},

		destroy: function(){
			// summary:
			//		Over-ride to ensure the resize handle gets cleaned up.
			if(this._resizeHandle){
				// Cleanup resizing listeners
				this._resizeHandle.remove();
				this._resizeHandle = null;
			}
			if(this._resizeHandle2){
				// Cleanup resizing listeners
				this._resizeHandle2.remove();
				this._resizeHandle2 = null;
			}
			if(this._resizer){
				clearTimeout(this._resizer);
				this._resizer = null;
			}
			this.inherited(arguments);
		}
	});

	// Register this plugin.
	// For back-compat accept "fullscreen" (all lowercase) too, remove in 2.0
	_Plugin.registry["fullScreen"] = _Plugin.registry["fullscreen"] = function(args){
		return new FullScreen({
			zIndex: ("zIndex" in args) ? args.zIndex : 500
		});
	};

	return FullScreen;
});
