dojo.provide("dojox.layout.WindowPane");
dojo.experimental("dojox.layout.WindowPane"); 

dojo.require("dijit.layout.ContentPane");
dojo.require("dijit.layout.ContentPane");
dojo.require("dojo.dnd.Moveable");
dojo.require("dojox.layout.ResizeHandle"); 

dojo.declare("dojox.layout.WindowPane", 
	[ dijit.layout.ContentPane, dijit._Templated ],
	{
	// summary:
	//		A non-modal Floating window.
	//
	// description:
	// 		Makes a `dojox.layout.ContentPane` float and draggable by it's title [similar to TitlePane]
	// 		and over-rides onClick to onDblClick for wipeIn/Out of containerNode
	// 		provides minimize(dock) / show() and hide() methods, and resize [almost] 
	//
	
	// closable: Boolean
	//		Allow closure of this Node
	closable: true,

	// dockable: Boolean
	//		Allow minimizing of pane if true
	dockable: true,

	// resizable: Boolean
	//		Allow resizing of pane true if true
	resizable: false,

	// maxable: Boolean
	//		Horrible param name for "Can you maximize this floating pane?"
	maxable: false,

	// resizeAxis: String
	//		One of: x | xy | y to limit pane's sizing direction
	resizeAxis: "xy",

	// title: String
	//		Title to use in the header
	title: "",

	// dockTo: DomNode?
	//		if empty, will create private layout.Dock that scrolls with viewport
	//		on bottom span of viewport.	
	dockTo: "",

	// duration: Integer
	//		Time is MS to spend toggling in/out node
	duration: 400,

	/*=====
	// iconSrc: String
	//		[not implemented yet] will be either icon in titlepane to left
	//		of Title, and/or icon show when docked in a fisheye-like dock
	//		or maybe dockIcon would be better?
	iconSrc: null,
	=====*/

	// contentClass: String
	// 		The className to give to the inner node which has the content
	contentClass: "dojoxFloatingPaneContent",
	
	baseClass: "dojoxFloatingPane",

	// animation holders for toggle
	_showAnim: null,
	_hideAnim: null, 
	// node in the dock (if docked)
	_dockNode: null,

	// privates:
	_restoreState: {},
	_allFPs: [],
	_startZ: 100,
	

	typematicConnections: [],

	templateString: dojo.cache("dojox.layout","resources/WindowPane.html"),
	
	attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
		title: { type:"innerHTML", node:"titleNode" }
	}),
	
	postCreate: function(){
		this.inherited(arguments);
		
		new dojo.dnd.Moveable(this.domNode,{ handle: this.focusNode });

		if(!this.dockable){ this.dockNode.style.display = "none"; } 
		if(!this.closable){ this.closeNode.style.display = "none"; } 
		if(!this.maxable){
			this.maxNode.style.display = "none";
			this.restoreNode.style.display = "none";
		}
		if(!this.resizable){
			this.resizeHandle.style.display = "none"; 	
		}else{
			this.domNode.style.width = dojo.contentBox(this.domNode).w + "px";
		}
		this._allFPs.push(this);
		this.domNode.style.position = "absolute";
		
		this._naturalState = dojo.coords(this.domNode);
	},
	
	startup: function(){
		if(this._started){ return; }
		
		this.inherited(arguments);

		if(this.resizable){
			if(dojo.isIE){
				this.canvas.style.overflow = "auto";
			}else{
				this.containerNode.style.overflow = "auto";
			}
			
			this._resizeHandle = new dojox.layout.ResizeHandle({ 
				targetId: this.id, 
				resizeAxis: this.resizeAxis 
			},this.resizeHandle);

		}

		if(this.dockable){ 
			// FIXME: argh.
			var tmpName = this.dockTo; 

			if(this.dockTo){
				this.dockTo = dijit.byId(this.dockTo); 
			}else{
				this.dockTo = dijit.byId('dojoxGlobalFloatingDock');
			}

			if(!this.dockTo){
				var tmpId, tmpNode;
				// we need to make our dock node, and position it against
				// .dojoxDockDefault .. this is a lot. either dockto="node"
				// and fail if node doesn't exist or make the global one
				// once, and use it on empty OR invalid dockTo="" node?
				if(tmpName){ 
					tmpId = tmpName;
					tmpNode = dojo.byId(tmpName); 
				}else{
					tmpNode = dojo.create('div', null, dojo.body());
					dojo.addClass(tmpNode,"dojoxFloatingDockDefault");
					tmpId = 'dojoxGlobalFloatingDock';
				}
				this.dockTo = new dojox.layout.Dock({ id: tmpId, autoPosition: "south" }, tmpNode);
				this.dockTo.startup(); 
			}
			
			if((this.domNode.style.display == "none")||(this.domNode.style.visibility == "hidden")){
				// If the FP is created dockable and non-visible, start up docked.
				this.minimize();
			} 
		} 		
		this.connect(this.focusNode,"onmousedown","bringToTop");
		this.connect(this.domNode,	"onmousedown","bringToTop");

		// Initial resize to give child the opportunity to lay itself out
		this.resize(dojo.marginBox(this.domNode));
		
		this._started = true;
	},

	setTitle: function(/* String */ title){
		// summary: Update the Title bar with a new string
		dojo.deprecated("pane.setTitle", "Use pane.attr('title', someTitle)", "2.0");
		this.attr("title", title);
		// this.setTitle = dojo.hitch(this, "setTitle") ?? 
	},
		
	close: function(){
		// summary: Close and destroy this widget
		if(!this.closable){ return; }
		dojo.unsubscribe(this._listener);
		this.hide(dojo.hitch(this,function(){
			this.destroyRecursive();
		})); 
	},

	hide: function(/* Function? */ callback){
		// summary: Close, but do not destroy this FloatingPane
		dojo.fadeOut({
			node:this.domNode,
			duration:this.duration,
			onEnd: dojo.hitch(this,function() { 
				this.domNode.style.display = "none";
				this.domNode.style.visibility = "hidden"; 
				if(this.dockTo && this.dockable){
					this.dockTo._positionDock(null);
				}
				if(callback){
					callback();
				}
			})
		}).play();
	},

	show: function(/* Function? */callback){
		// summary: Show the FloatingPane
		var anim = dojo.fadeIn({node:this.domNode, duration:this.duration,
			beforeBegin: dojo.hitch(this,function(){
				this.domNode.style.display = ""; 
				this.domNode.style.visibility = "visible";
				if (this.dockTo && this.dockable) { this.dockTo._positionDock(null); }
				if (typeof callback == "function") { callback(); }
				this._isDocked = false;
				if (this._dockNode) { 
					this._dockNode.destroy();
					this._dockNode = null;
				}
			})
		}).play();
		this.resize(dojo.coords(this.domNode));
	},

	minimize: function(){
		// summary: Hide and dock the FloatingPane
		if(!this._isDocked){ this.hide(dojo.hitch(this,"_dock")); } 
	},

	maximize: function(){
		// summary: Make this FloatingPane full-screen (viewport)	
		if(this._maximized){ return; }
		this._naturalState = dojo.position(this.domNode);
		if(this._isDocked){
			this.show();
			setTimeout(dojo.hitch(this,"maximize"),this.duration);
		}
		dojo.addClass(this.focusNode, this.baseClass + "Maximized");
		this.resize(dijit.getViewport());
		this._maximized = true;
	},

	_restore: function(){
		if(this._maximized){
			this.resize(this._naturalState);
			dojo.removeClass(this.focusNode, this.baseClass + "Maximized");
			this._maximized = false;
		}	
	},

	_dock: function(){
		if(!this._isDocked && this.dockable){
			this._dockNode = this.dockTo.addNode(this);
			this._isDocked = true;
		}
	},
	
	resize: function(/* Object */dim){
		// summary: Size the FloatingPane and place accordingly
		dim = dim || this._naturalState;
		this._currentState = dim;
		
		/* For some reason, this new (short and sweet) version sometimes prevents mouse drag, 
		 * so we keep the old version.
		 
		var target = dojo.mixin(dojo.contentBox(this.domNode),dim),
			mbCanvas = {w: target.w, h: target.h - this.focusNode.offsetHeight};
		
		dojo.contentBox(this.domNode,target);
		dojo.marginBox(this.canvas,mbCanvas);
		
		*/


		// From the ResizeHandle we only get width and height information
		var dns = this.domNode.style;
		if("t" in dim){ dns.top = dim.t + "px"; }
		if("l" in dim){ dns.left = dim.l + "px"; }
		
		dns.width = dim.w + "px"; 
		dns.height = dim.h + "px";
		
		// Now resize canvas
		var mbCanvas = { l: 0, t: 0, w: dim.w, h: (dim.h - this.focusNode.offsetHeight) };
		dojo.marginBox(this.canvas, mbCanvas);

		// If the single child can resize, forward resize event to it so it can
		// fit itself properly into the content area
		this._checkIfSingleChild();
		if(this._singleChild && this._singleChild.resize){
			this._singleChild.resize(mbCanvas);
		}
	},
	
	bringToTop: function(){
		// summary: bring this FloatingPane above all other panes
		var windows = dojo.filter(
			this._allFPs,
			function(i){
				return i !== this;
			}, 
		this);
		windows.sort(function(a, b){
			return a.domNode.style.zIndex - b.domNode.style.zIndex;
		});
		windows.push(this);
		
		dojo.forEach(windows, function(w, x){
			w.domNode.style.zIndex = this._startZ + (x * 2);
			dojo.removeClass(w.domNode, this.baseClass + "Fg");
		}, this);
		dojo.addClass(this.domNode, this.baseClass + "Fg");
	},
	
	destroy: function(){
		// summary: Destroy this FloatingPane completely
		this._allFPs.splice(dojo.indexOf(this._allFPs, this), 1);
		if(this._resizeHandle){
			this._resizeHandle.destroy();
		}
		this.inherited(arguments);
	},
	
	_onTitleFocus: function(){
		this.bringToTop();
		dojo.addClass(this.focusNode,this.baseClass + 'TitleFocused');
	},
	
	_onTitleBlur: function(){
		this.endMoveMode();
		dojo.removeClass(this.focusNode,this.baseClass + 'TitleFocused');
	},
	
	_handleFocusNodeKey: function(evt){
		if(evt.keyCode === 32){
			dojo.stopEvent(evt);
			this[ (this.moveMode ? "end" : "start") + "MoveMode"]();
		}
		
		if(this.moveMode){
			switch(evt.keyCode){
				case 27:
					this.endMoveMode();
					break;
				case 109:
					if(this.dockable && !this._maximized){
						this.minimize();
					}
					if(this._maximized){
						this._restore();
					}
					break;
				case 107:
					if(this.maxable && !this._maximized){
						this.maximize();
					}
					break;
				case 88:
					if(this.closable){
						this.close();
					}
					break;
			}
		}
	},
	
	startMoveMode: function(){
		if(this.moveMode){
			return;
		}
		this.showMoveOverlay();
		if(this.resizable){
			this.enableTypematicConnections();
		}
		this.moveMode = true;
	},
	
	endMoveMode: function(){
		if(!this.moveMode){
			return;
		}
		this.hideMoveOverlay();
		if(this.resizable){
			this.disableTypematicConnections();
		}
		this.moveMode = false;
	},
	
	showMoveOverlay: function(){
		this._toggleMoveOverlay(true);
	},
	
	hideMoveOverlay: function(){
		this._toggleMoveOverlay(false);
	},
	
	_toggleMoveOverlay: function(isVisible){
		dojo.toggleClass(this.moveOverlay,this.baseClass + "MoveOverlayVisible" ,isVisible);
	},
	
	enableTypematicConnections: function(){
		dojo.forEach([37,38,39,40],function(code){
			this.typematicConnections.push(dijit.typematic.addKeyListener(this.focusNode, {charOrCode: code, shiftKey: false}, this, "_handleKeyMove", 25, 0));
			this.typematicConnections.push(dijit.typematic.addKeyListener(this.focusNode, {charOrCode: code, shiftKey: true}, this, "_handleKeyResize", 25, 0));
		},this);
	},
	
	disableTypematicConnections: function(){
		dojo.forEach(this.typematicConnections,function(connSet){
			dojo.forEach(connSet,function(conn){dojo.disconnect(conn);});
		},this);
	},
	
	_handleKeyMove: function(count, obj, e) {
		if(count === -1){
			return;
		}
		var coords = dojo.marginBox(this.domNode),
			dest = {},
			prop = e.charOrCode % 2 === 0 ? 't' : 'l',
			dir = e.charOrCode > 38 ? 'plus' : 'minus';
		
		dest[prop] = coords[prop] + 5 * ( dir === 'plus' ? 1 : -1 );
		
		/*
		switch(e.charOrCode){
			case 37:
				dest.l = coords.l - 5;
				break;
			case 38:
				dest.t = coords.t - 5;
				break;
			case 39:
				dest.l = coords.l + 5;
				break;
			case 40:
				dest.t = coords.t + 5;
				break;
		}
		*/
		
		dojo.marginBox(this.domNode,dest);
	},
	
	_handleKeyResize: function(count, obj, e){
		if(count === -1){
			return;
		}
		var coords = dojo.contentBox(this.domNode),
			dest = {},
			prop = e.charOrCode % 2 === 0 ? 'h' : 'w',
			dir = e.charOrCode > 38 ? 'plus' : 'minus';
			
		dest[prop] = coords[prop] + 3 * ( dir === 'plus' ? 1 : -1 );
		
		/*
		switch(e.charOrCode){
			case 37:
				dest.w = coords.w - 2;
				break;
			case 38:
				dest.h = coords.h - 2;
				break;
			case 39:
				dest.w = coords.w + 2;
				break;
			case 40:
				dest.h = coords.h + 2;
				break;
		}
		*/
		this.resize(dest);
	}
});


dojo.declare("dojox.layout.Dock",
	[dijit._Widget,dijit._Templated],
	{
	// summary:
	//		A widget that attaches to a node and keeps track of incoming / outgoing FloatingPanes
	// 		and handles layout

	templateString: '<div class="dojoxDock"><ul dojoAttachPoint="containerNode" class="dojoxDockList"></ul></div>',

	// private _docked: array of panes currently in our dock
	_docked: [],
	
	_inPositioning: false,
	
	autoPosition: false,
	
	addNode: function(refNode){
		// summary: Instert a dockNode refernce into the dock
		
		var div = dojo.create('li', null, this.containerNode),
			node = new dojox.layout._DockNode({ 
				title: refNode.title,
				paneRef: refNode 
			}, div)
		;
		node.startup();
		return node;
	},

	startup: function(){
				
		if (this.id == "dojoxGlobalFloatingDock" || this.isFixedDock) {
			// attach window.onScroll, and a position like in presentation/dialog
			this.connect(window, 'onresize', "_positionDock");
			this.connect(window, 'onscroll', "_positionDock");
			if(dojo.isIE){
				this.connect(this.domNode, "onresize", "_positionDock");
			}
		}
		this._positionDock(null);
		this.inherited(arguments);

	},
	
	_positionDock: function(/* Event? */e){
		if(!this._inPositioning){	
			if(this.autoPosition == "south"){
				// Give some time for scrollbars to appear/disappear
				setTimeout(dojo.hitch(this, function() {
					this._inPositiononing = true;
					var viewport = dijit.getViewport();
					var s = this.domNode.style;
					s.left = viewport.l + "px";
					s.width = (viewport.w-2) + "px";
					s.top = (viewport.h + viewport.t) - this.domNode.offsetHeight + "px";
					this._inPositioning = false;
				}), 125);
			}
		}
	}


});

dojo.declare("dojox.layout._DockNode",
	[dijit._Widget,dijit._Templated],
	{
	// summary:
	//		dojox.layout._DockNode is a private widget used to keep track of
	//		which pane is docked.
	//
	// title: String
	// 		Shown in dock icon. should read parent iconSrc?	
	title: "",

	// paneRef: Widget
	//		reference to the FloatingPane we reprasent in any given dock
	paneRef: null,

	templateString:
		'<li dojoAttachEvent="onclick:restore,onkeydown:handleKey" class="dojoxDockNode" tabIndex="0">'+
			'<span dojoAttachPoint="restoreNode" class="dojoxDockRestoreButton" dojoAttachEvent="onclick: restore"></span>'+
			'<span class="dojoxDockTitleNode" dojoAttachPoint="titleNode">${title}</span>'+
		'</li>',
		
	postCreate: function(){
		dojo.connect(this,'onFocus',dojo.hitch(this,function(){dojo.addClass(this.domNode,'dojoxDockNodeFocused');}));
		dojo.connect(this,'onBlur',dojo.hitch(this,function(){dojo.removeClass(this.domNode,'dojoxDockNodeFocused');}));
	},

	restore: function(){
		// summary: remove this dock item from parent dock, and call show() on reffed floatingpane
		this.paneRef.show();
		this.paneRef.bringToTop();
		if(!this.paneRef.isLoaded){ this.paneRef.refresh(); }
		this.destroy();
	},
	
	handleKey: function(evt){
		if(evt.keyCode === 32){
			this.restore();
		}
	}

});
